当前位置: 首页 > news >正文

网站制作专业的公司做poster的网站

网站制作专业的公司,做poster的网站,网站建设方案情况汇报,站酷网海报素材图片进程概念 一、环境变量1. 命令行参数2. 常见的环境变量#xff08;1#xff09;PATH#xff08;2#xff09;PWD#xff08;3#xff09;HOME#xff08;4#xff09;env 查看所有的环境变量 3. 获取环境变量#xff08;1#xff09;通过代码获取环境变量#xff08… 进程概念 一、环境变量1. 命令行参数2. 常见的环境变量1PATH2PWD3HOME4env 查看所有的环境变量 3. 获取环境变量1通过代码获取环境变量2通过命令行参数获取3通过 extern char** environ 获取 4. 本地变量和环境变量5. Linux 命令的分类6. 环境变量相关的命令 二、程序地址空间1. 程序地址空间分布2. 进程地址空间3. 地址空间和区域划分1地址空间2区域划分3管理地址空间 4. 为什么要有地址空间5. 空间分配扩展 一、环境变量 1. 命令行参数 什么是命令行参数呢首先我们得先知道主函数是可以传参的而这个传给主函数的参数就是命令行参数。 我们可以创建一个主函数并在主函数中接收命令行参数把命令行参数打印出来观察一下如下 图中的 argc 和 argv 就是接收命令行参数的形参我们观察一下打印出来的数据 我们看到打印出来的数据竟然是我们可执行程序的名字那么 0 又代表什么呢我们尝试在可执行程序后面加多一些数据如下 最后我们得出结论我们输入的指令中以空格作为分隔符被分割成了4个子串而这4个子串最终会传入主函数中被接收argc 就是命令行参数的个数argv 就是被分割的子串其中 0 号子串一定是我们的可执行程序后面带的可以说是选项为什么说是选项呢因为我们可以通过不同的子串执行不同的代码例如 1 #include stdio.h2 #include string.h3 4 int main(int argc, char* argv[])5 {6 if(argc ! 2)7 {8 printf(error!\n);9 return 1; 10 }11 if(strcmp(argv[1], -a) 0)12 printf(aaa\n);13 else if(strcmp(argv[1], -b) 0)14 printf(bbb\n);15 // ........16 17 return 0;18 }如以上代码我们可以通过命令行参数支持各种指令级别的命令行选项的设置 2. 常见的环境变量 环境变量一般是指在操作系统中用来指定操作系统运行环境的一些参数例如我们在编写C/C代码的时候在链接的时候从来不知道我们的所链接的动态静态库在哪里但是照样可以链接成功生成可执行程序原因就是有相关环境变量帮助编译器进行查找环境变量通常具有某些特殊用途还有在系统当中通常具有全局特性 常见的环境变量有 PATH : 指定命令的搜索路径HOME : 指定用户的主工作目录(即用户登陆到Linux系统中时,默认的目录)SHELL : 当前Shell,它的值通常是/bin/bash 1PATH 什么是 PATH 呢我们平时在 Linux 中写一份代码想要运行起来首先需要找到这个可执行程序的路径所以如果这个可执行程序在当前路径下就需要在前面加上 ./ 例如下图 那么通过上面命令行参数的学习我们知道Linux 中的指令也是可执行程序那么为什么它们的指令不用加 ./ 就能正常运行呢这就和我们的环境变量 PATH 有关了PATH 是系统默认的搜索路径只要将我们程序的路径添加到 PATH 中我们的程序也不需要加 ./ 就能跑啦 其中我们可以使用指令 which 指令 可以查看这个指令所在的路径例如我们需要查看 ll 指令所处的路径 接下来我们查看一下 PATH 中的内容使用指令 echo $PATH 查看其中 $ 相当于解引用查看的含义这是 shell 的语法如下 如果我们想将我们的当前路径添加到 PATH 中呢我们首先使用 pwd 查看我们当前的路径 然后我们将我们的路径复制然后放到以下的位置可以使用如下指令 PATH$PATH:/home/lmy/.mygitee/Linux_Study/study9此时我们的路径也就添加到 PATH 中了我们可以查看一下 如上图我们确实将路径添加到了 PATH 中那么我们现在执行当前路径下的可执行程序时就不用在前面加上 ./ 了如下图 如果我们想删除当前路径呢也很简单只需要将不需要的部分去掉就行了假设我们将当前路径在 PATH 中去掉可以复制除了当前路径的其它路径然后执行以下指令 PATH/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/lmy/.local/bin:/home/lmy/bin就把当前路径去掉了注意 PATH 中是以 作为分隔符将各个不同的路径分割开来的。 上面是一种方法还有另外一种方法就是将我们的可执行程序直接使用 cp 指令拷贝到 PATH 中的某个路径的文件下也可实现注意这里拷贝需要提升权限使用 sudo 即可这里将不再演示。这种方法就叫做程序安装本质就是将可执行程序拷贝到系统可以找得到的路径下程序卸载即是将这个可执行程序从路径下删除即可。 如果我们把 PATH 设置为空串看看会发生什么情况如下图 我们可以看到大部分的指令都不能用了此时我们可以重新启动 Xshell 即可恢复。所以我们得出默认更改环境变量只限于本次登录重新登录环境变量自动被恢复这是为什么呢我们后面再讲。 2PWD 我们在使用 pwd 指令的时候系统怎么知道我们所在的路径在哪里呢原因是因为 Linux 中也会存在一个环境变量PWD. PWD 是给我们当前对应的 bash 内置的一个专门用来记录我们当前所处的路径的一个环境变量。 例如我们先查看一下我们的当前路径 我们 cd 到上一层再查看 PWD 环境变量 如上图PWD 环境变量也会跟着变化所以 pwd 的指令其实就是读取 PWD 环境变量中的内容打印出来即可。 3HOME 当我们默认登录 Xshell 时我们所处的路径/目录是 /home/xxx例如 但是如果我们是以 root 用户登录我们所处的默认目录将会是 /root这是为什么呢 这是因为当我们登录的时候首先我们需要输入用户名和密码等待系统认证认证完毕后会形成环境变量此时肯定不止一个环境变量(PATH, PWD, HOME 等)然后会根据用户名初始化 HOME即 HOME /rootHOME /home/xxx最后只需要执行 cd $HOME 即可。 4env 查看所有的环境变量 我们可以使用 env 指令查看所有的环境变量Linux 中的环境变量非常多大家可以自行查看。而每一个环境变量都有它自己的特殊用途用来完成特定的系统功能。 3. 获取环境变量 1通过代码获取环境变量 我们有一个接口可以通过代码直接获取环境变量就是 getenv我们可以通过 man 指令查看一下 我们可以使用一下 getenv 查看一下 PATH 环境变量如下代码 1 #include stdio.h2 #include stdlib.h3 4 int main()5 {6 printf(%s\n, getenv(PATH));7 return 0; 8 } 执行结果如下 我们上面所说每一个环境变量都有它自己的特殊用途如何体现出来呢下面我们结合 getenv 简单使用一下如下代码 #include stdio.h#include stdlib.h#include string.hint main(){char* s getenv(USER);if(strcmp(s, root) ! 0){printf(%s 是非法用户!\n, s);return 1;}printf(Hello!\n);printf(Hello!\n);printf(Hello!\n);return 0;}我们使用环境变量 USER 去判断当前用户是否是 root我们只让root 执行相应的代码如下运行结果 当我们是 root 用户 当我们是普通用户 2通过命令行参数获取 1.通过我们上面所学的命令参数我们知道命令行参数可以有两个但是其实还可以有第三个命令行参数那就是 env env 其实就是一张环境变量表系统启动我们的程序时可以选择给我们的进程(main)提供两张表1. 命令行参数表 2. 环境变量表其中这个第三个参数 env 就是环境变量表env 是一个指针指向的是一个函数指针数组可以参考下图理解 其中右边一大串的是环境变量通常 env 的最后一个元素是指向空的 我们也可以通过代码打印出来观察我们可以打印当前程序运行的 pid如下段代码 1 #include stdio.h2 #include stdlib.h3 #include string.h4 #include unistd.h5 #include sys/types.h6 7 int main(int argc, char* argv[], char* env[])8 {9 int i 0;10 for(; env[i]; i)11 {12 printf(pid: %d, env[%d]: %s\n, getpid(), i, env[i]); 13 }14 15 return 0;16 }运行的结果大家可以自行运行后查看那么我们为什么要打印出它的 pid 呢那么我们不妨想想这个进程的 env 是谁传给我们的呢就是 bash我们命令行启动的进程都是 shell(bash) 的子进程子进程的命令行参数和环境变量都是父进程 bash 传递的 那么问题又来了父进程 bash 的环境变量又从哪里来呢其实环境变量是以脚本配置文件的形式存在的我们也可以找到这个文件例如我们回到我们的家目录下有一个隐藏文件叫做 .bash_profile如下图 每一次登录的时候我们的 bash 进程都会读取 .bash_profile 这个文件中的内容为我们 bash 进程形成一张新的环境变量表信息这也就能解释了我们上面的一个问题我们将 PATH 的路径改成空重启 Xshell 之后就会恢复正常了。 2.另一个问题我们也可以创建属于自己的环境变量如下图直接在命令行中输入即可 此时我们在环境变量表中查看一下 发现并没有导入到我们的环境变量表中或许我们可以直接在我们的可执行程序中查找 也一样没有因为我们还没有导入到环境变量表中我们需要把它导入到环境变量表中使用命令 export MYENV_LMY 即可如下 我们也可以直接在创建环境变量时导入使用指令 export 环境变量名称内容如下图 我们可以看到它们两个都出现在了环境变量表中如果此时我们退出再重新登录它们两个还存在吗答案是不存在了因为以上这两个环境变量并没有写入配置文件中改变的只是当前 bash 内部的环境变量表当我们退出重新登录后bash 会重新读取 .bash_profile 文件从而重新获取环境变量表上面这种只在 bash 进程内部有效的叫做本地变量 所以我们想要我们自己的环境变量永远生效我们需要把它添加到 .bash_profile 配置文件中如下图所示 此时我们保存退出重新登陆后就可以查到我们对应的环境变量 这种通过子进程继承的方式继承环境变量表的就叫做环境变量 我们知道了命令行参数表和环境变量表都可以通过继承给子进程的方式让子进程继承这就表明系统环境变量具有全局属性 3通过 extern char** environ 获取 第二种方式获取环境变量需要我们写命令行参数传入也有一种方式不需要写命令行参数就可以获取环境变量表就是通过系统给我们提供的 environ 指针这个指针我们使用的时候需要使用 extern 声明一下因为不是我们自己定义的是系统给我们提供的其实 environ 就是指向 env 的指针它们的关系如下图 我们也可以通过 environ 打印出环境变量表如下段代码 1 #include stdio.h2 #include stdlib.h3 #include string.h4 #include unistd.h5 #include sys/types.h6 7 int main()8 {9 extern char** environ;10 int i 0;11 for(; environ[i]; i)12 {13 printf(pid: %d, environ[%d]: %s\n, getpid(), i, environ[i]); 14 }15 16 return 0;17 }运行的结果大家可以自行尝试去观察结果和第二种方法是一样的。 4. 本地变量和环境变量 上面我们也简单地介绍了一下本地变量和环境变量接下来我们进一步分析它们之间的区别 本地变量本地变量只在 bash 进程内部有效不会被子进程继承下去。环境变量环境变量通过让所有的子进程继承的方式实现自身的全局性环境变量是天然让所有子进程继承下去的 5. Linux 命令的分类 我们首先回忆起当我们把 PATH 设为空时是不是有一些命令能跑有一些命令不能跑呢我们再次尝试一下如下图 我们可以看到确实有些命令是跑不了了但为什么诸如 pwd、echo 这样的命令还能跑呢原因是因为 Linux 中的命令可分为两类 常规命令 常规命令是 shell 命令行解释器进行 fork 让子进程执行的。 内建命令 内建命令是 shell 命令行的一个函数建立在 shell 的内部所以可以直接读取 shell 内部定义的本地变量 6. 环境变量相关的命令 echo echo 是显示某个环境变量值这个我们在上面也用过例如需要查看 PATH 的环境变量值 export export 是将本地变量导入到环境变量表中即设置一个新的环境变量我们在上面也使用过。例如我们先设置一个本地变量此时是可以用 echo 查看的因为 echo 是内建命令但是在环境变量 env 中没有这个本地变量如下图 我们可以将该本地变量导入环境变量中就能在 env 中查看到 unset unset 是清除环境变量删除某个环境变量。例如我们要删除上面我们自己定义的环境变量 MYENV如下 此时本地变量和环境变量中都没有了。 env env 是显示所有环境变量这个我们上面也介绍过。 set set 是显示本地定义的 shell 变量和环境变量。例如我们定义一个本地变量 local_env 和一个环境变量 myenv此时我们在环境变量中查看的时候只有 myenv如下图 但是我们使用 set 查看的时候两个都能看见如下图 二、程序地址空间 1. 程序地址空间分布 我们以前大概了解过在 C/C 中诸如下图的空间分布图 其中堆和栈是相对而生的堆区往上增长栈区往下增长其实堆区和栈区中间还有其它空间我们后面在学静态数据变量也存在于数据段中其实静态变量会被编译器修饰成全局变量所以它会被放到数据段中。 如何证明我们程序的地址是按照以上的空间分布呢下面我们使用代码验证一下如下段代码 1 #include stdio.h2 #include stdlib.h3 4 int init_val 1;5 int uninit_val;6 7 int main()8 {9 char* str Hello;10 char* heap1 (char*)malloc(10);11 char* heap2 (char*)malloc(10);12 char* heap3 (char*)malloc(10);13 char* heap4 (char*)malloc(10);14 static int static_val1 2;15 static int static_val2;16 17 printf(栈区地址1:%p\n, heap1);18 printf(栈区地址2:%p\n, heap2); 19 printf(栈区地址3:%p\n, heap3);20 printf(栈区地址4:%p\n, heap4);21 22 printf(堆区地址4:%p\n, heap4);23 printf(堆区地址3:%p\n, heap3);24 printf(堆区地址2:%p\n, heap2); 25 printf(堆区地址1:%p\n, heap1);26 27 printf(未初始化静态变量地址:%p\n, static_val2);28 printf(未初始化全局数据区:%p\n, uninit_val);29 printf(已初始化静态变量地址:%p\n, static_val1);30 printf(已初始化全局数据区:%p\n, init_val);31 printf(字符常量区地址:%p\n, str);32 printf(代码区地址:%p\n, main);33 34 return 0;35 }运行结果如下结果确实是这样的 我们单独拿栈区出来分析我们在局部创建的数组、结构体都是在栈区中向下增长开辟空间的假设我们有一个 a[10] 的数组一个 struct A {x, y, z} 结构体那么 a[0] 和 a[9] 的地址谁大呢A obj 中obj.x 和 obj.z 谁的地址大呢下面我们写个程序验证一下如下代码 #include stdio.h#include stdlib.htypedef struct A {int x;int y;int z;}A;int main(){int a[10];A obj;printf(%p\n, a[0]);printf(%p\n, a[9]);printf(%p\n, obj.x);printf(%p\n, obj.y);printf(%p\n, obj.z);return 0;}结果如下 所以我们得出结论栈区是往下开辟申请空间但是使用的时候是局部往上使用的可以用下图来概括 如上图如果我们在栈上定义了一个变量 int b那么我们知道 int 是占四个字节的而我们上面的空间中每个空间占一个字节那么我们 b 拿的是哪个地址呢其实是拿最低的地址然后是通过起始地址 偏移量的方式进行访问。 其实除了上面空间分布中的区域外还有一些我们还没学所以我们以后再介绍但是在栈区上面有两个是我们刚学习的区域就是命令行参数和环境变量。 所以我们上面学的空间分布它到底是什么呢它是内存吗我们下面开始学习。 2. 进程地址空间 首先我们回顾一下我们以前学习 fork 的时候父子进程之间是怎么运行的我们这时候想起来还有一个问题还没解决那就是当子进程修改代码时会发生写时拷贝但是一个变量不同的值为什么会有相同的地址呢这就是我们接下来需要学习的首先我们把代码再敲出来如下段代码 1 #include stdio.h2 #include stdlib.h3 #include unistd.h4 5 int g_val 100;6 7 int main()8 {9 pid_t id fork();10 if(id 0)11 {12 //child13 int cnt 5;14 while(1)15 {16 printf(i am child, g_val: %d, g_val%p\n, g_val, g_val);17 sleep(1);18 if(cnt 0)19 {20 g_val200;21 printf(child change g_val: 100-200\n);22 }23 cnt--;24 }25 }26 else27 { 28 //father29 while(1)30 {31 printf(i am father, g_val: %d, g_val%p\n, g_val, g_val);32 sleep(1);33 }34 }35 sleep(100);36 return 0;37 }我们定义了一个全局变量 g_val当子进程对它进行修改时父进程还是原来的 g_val因为父子进程之间的数据互不影响具有独立性我们观察运行的结果会发现它们的地址竟然是一样的如下图 那么为什么对同一个地址进行读取会得出不同的内容呢所以通过上图我们得出的结论是我们在C/C平常所见到的地址绝对不是物理地址其实都是虚拟地址/线性地址 其实我们上面所学的空间分布的那张图就是进程地址空间里面的地址全都是虚拟地址如下图 但是我们的进程需要被cpu调度进程中的数据要被cpu读取识别就必须加载到内存中即物理内存中。那么这个过程到底是怎样的呢其实我们上面的代码所打印出来的地址全部都是它的进程地址空间的地址也就是虚拟地址而这个可执行程序是 bash 的子进程啊而这个父进程在代码中又创建自己的子进程也有它自己的进程地址空间所以我们认为每一个程序运行之后都会有一个进程地址空间的存在 那么我们现在已经有三个重要的角色了分别是 pcb(task_struct)、进程地址空间、物理内存其中 task_struct 中肯定有一些字段是指向属于自己的进程地址空间的而物理内存中也一定需要存储该进程的一些数据的在物理内存中存储这个数据的地址才叫做物理地址如下面的 0x11111111所以它们现在的关系如下图 那么进程地址空间和物理内存之间是如何联系起来的呢在操作系统中为了让进程找到物理内存中自己的数据会为每个进程维护一张映射表它要为进程地址空间和物理内存之间构建一种映射关系而这个表叫做页表。它的左侧放的是虚拟地址右侧是数据在物理内存中的地址它们两者之间是一种映射关系。可以结合下图理解 如上就是页表的简单结构页表中还有其它字段我们后面再介绍其中进程通过进程地址空间中的地址可以查找页表找到对应在物理内存中的物理地址。 我们从上面知道进程在运行之后都会有一个进程地址空间的存在而在系统层面它们都要有自己的页表映射结构因为我们的上面的父进程也会有子进程所以操作系统会将父进程的 pcb 拷贝一份给子进程所以子进程也会有自己的进程地址空间和页表都是从父进程那获取的它们俩互相独立互不影响例如下图 当我们的子进程对数据进行修改时通过页表找到相应的数据但是操作系统发现父进程正在使用这个数据所以子进程不能直接对该数据进行修改因为它们具有独立性所以操作系统会将物理内存中的值进行写时拷贝生成一块新的物理地址然后将子进程想要修改的数据覆盖之间的数据即可这时候子进程中的物理地址就发生变化结合下图理解 所以我们就理解了为什么它们会有相同的地址而值却不一样因为它们两个进程都有之间独立的进程地址空间和页表写时拷贝发生在物理内存中改变的也是物理地址虚拟地址并没有改变所以相同的虚拟地址并不互相影响。 3. 地址空间和区域划分 1地址空间 我们从上面知道物理内存是操作系统直接管理的。当我们的进程需要使用空间时操作系统会给进程一个虚拟地址空间这个虚拟地址空间就是地址空间。当我们的进程多起来的时候既然操作系统要把进程管理起来那么操作系统也要把地址空间管理起来因为如果不管理起来就会乱套了。那么如何管理起来呢我们下面再说。 2区域划分 所谓的区域划分就是用 start 和 end 这样的 int 或者 long long 变量来区分一个线性空间的开始和结束空间大小可以调整调整时只需要将 start 和 end 的值进行扩大或缩小即可不要只看到空间的范围空间范围内的地址我们都可以使用。 3管理地址空间 地址空间要被操作系统管理起来因为每一个进程都有一个地址空间系统中一定要把地址空间做管理。如何管理呢我们以前学过先描述在组织所以地址空间最终一定是一个内核的数据结构对象就是一个内核的结构体 我们发现进程地址空间中的内容全都是区域划分所以我们就可以进行区域划分进行管理 在 Linux 中这个进程/虚拟地址空间叫做 struct mm_struct其中它大概就长下面这个样子 struct mm_struct{long code_start;long code_end;long data_start;long data_end;long heap_start;long heap_end;long stack_start;long stack_end;......}其中 struct mm_struct 这个结构体是由一个叫做 struct mm_struct* mm 的指针指向的而这个指针存在于 task_struct 中所以每个进程被创建的时候都会创建一个 struct mm_struct* mm也就有了一个进程地址空间所以我们的进程地址空间为什么不是内存呢原因就是因为它只是一个内核数据结构。 4. 为什么要有地址空间 让进程以统一的视角看待内存所以任意一个进程都可以通过地址空间 页表将乱序的内存数据变成有序分门别类的规划好 为什么这么说呢我们思考一下当我们的程序加载到内存中时它是有顺序地加载的吗并不是的程序加载到内存是乱序的但是通过地址空间和页表就可以做到将乱序的内存数据变为有序让进程认为这些数据就是按照它的方式进行分类的 存在虚拟地址空间可以有效的进行进程访问内存的安全检查 首先我们再要了解一下在页表中还有一列叫做访问权限字段的东西它的结构就如下 访问权限字段有什么用呢它会在访问物理内存的时候检查对应的权限是否满足或者检查这个地址是否合法如果权限不满足或者地址非法就会在页表中直接拦截就不允许访问物理内存中的地址。 例如以下代码 int main(){char* s hello;*s h;return 0;}上面这段代码是不能正常运行的因为我们知道 “hello” 是常量不可被修改那么这是为什么呢这时候我们就知道了s 的地址是在进程地址空间中的字符常量区的而通过字符常量区映射的页表中的访问权限字段是只读的即 r所以当我们需要写入修改时在页表就直接被拦截了。 另外进程进行各种转换、各种访问这个进程一定是正在运行所以在 cpu 中有一个寄存器叫做 CR3会存放页表的地址这个页表的地址是物理地址。因为这个进程正在cpu 内运行所以CR3中的内容本质是在该进程的硬件上下文内容当中所以当该进程切换出去的时候本质这个CR3寄存器的内容会保存到当前进程的上下文里。所以每个进程都有自己的页表。 将进程管理和内存管理进行解耦 在操作系统层面当我们在磁盘中有程序需要加载到内存时首先需要在内存中申请内存然后填充内容和页表然后建立映射关系其中页表中还有一列内容是专门判断某个地址是否在内存中有分配内存和是否有内容的里面可能是两个比特位例如 1/0 表示是/否分配有内存1/0 表示 是否在内存中有内容例如下图 当我们的进程拿着一个虚拟地址来找物理地址的时候假设这时候内存还没有给它分配物理地址此时操作系统就会把该进程暂停并从磁盘中加载相对应的程序到内存中然后再填充页表建立映射关系这个过程叫做缺页中断这个概念我们以后还会介绍现在先了解一下。 当我们上面在内存中申请内存然后填充内容和页表然后建立映射关系这一套流程叫做内存管理而这个过程进程是不需要理会的而且进程也不知道这个过程进程需要做的只是该调度的就调度该访问的就访问这一套叫做进程管理进程管理不关心内存管理所以进程管理和内存管理因为有了地址空间和页表的存在实现了操作系统层面上的模块的解耦 最后通过页表还可以让进程映射到不同的物理内存中从而实现进程的独立性 5. 空间分配扩展 我们当前所能用的地址空间都是在用户空间中使用的在地址空间中还有一部分空间是要留给操作系统自身用的其中我们用户用到的空间叫做用户空间有 3GB操作系统自身用的空间叫做内核空间有 1GB. 如下图所示 在 struct mm_struct 结构体中其实还有一个指针它指向的是一个 vm_area_struct 的结构体它有什么用呢在我们的地址空间中被划分成了很多区域但是总有一些区域还没有被使用的所以当我们想要划分自己的区域的时候可以申请一个 vm_area_struct 的对象这个对象中有 start 、end 两个值分别是空间的开始和结束还有一个 next 的指针指向下一个结构体的对象它们之间构成一个链表的结构所以有了这样一个结构体我们就能根据自己的需求划分属于自己的空间了可以根据下图进行理解 其中我们的 mm_struct 结构体其实真正叫做内存描述符而 vm_area_struct 叫做线性空间这两个概念合起来才叫做地址空间但是由于方便我们认为 mm_struct 才是地址空间
http://www.hkea.cn/news/14516038/

相关文章:

  • 在线网站建设价格多少网站正则表达式怎么做
  • WordPress添加CA关键词优化除了做网站还有什么方法
  • 电商网站开发公司网站建设投标书服务方案范本
  • 河北省城乡和住房建设厅网站微信小程序怎么写
  • 江西网站建设公司联系方式企业微信scrm系统源码
  • 织梦 我的网站wordpress怎么做表格
  • 如何制作网站和网页校园网站建设的必要性论文
  • 商城网站源码免费vs2017网站开发教程
  • 临沂集团网站建设wordpress注册激活码
  • 一个vps主机放两个网站 速度仿制别人的竞价网站做竞价犯法吗
  • 滕州网站建设网站行吗做门户网站建设多少钱
  • 高端品牌网站建设(杭州)wordpress安装到虚拟主机
  • 毕设网站开发需要做什么做网站要会哪些技术
  • 崇文网站开发孝义做网站
  • 网站建设哪些分类网页背景怎么设置
  • 网站空间商盗取数据天津站内关键词优化
  • 纪实摄影网站推荐微信网站设计
  • 网站 设计风格表白网站制作
  • 男女做暧暧试看网站49仙桃做网站找谁
  • 网站建设ktv有专业做网站
  • 珠海电脑自己建网站做视频解析网站要什么服务器
  • 重庆自助建站软件西安建立公司网站的步骤
  • 湘潭手机网站电商设计网站哪个好
  • 网站建设基本流程图片成都电子商务网站建站
  • 常见的网络营销模式济南seo外包公司
  • 上海网站建设sheji021全中文软件开发工具
  • 网站建设与管理课程设计专门做海外服装购的网站有哪些
  • 村镇建设年度报表登录网站专门做网站需要敲代码么
  • 凯里网站建设公司天津网站建设代理商
  • 网站qq一键登录美食门户类网站模版