帮助文档
专业提供香港服务器、香港云服务器、香港高防服务器租用、香港云主机、台湾服务器、美国服务器、美国云服务器vps租用、韩国高防服务器租用、新加坡服务器、日本服务器租用 一站式全球网络解决方案提供商!专业运营维护IDC数据中心,提供高质量的服务器托管,服务器机房租用,服务器机柜租用,IDC机房机柜租用等服务,稳定、安全、高性能的云端计算服务,实时满足您的多样性业务需求。 香港大带宽稳定可靠,高级工程师提供基于服务器硬件、操作系统、网络、应用环境、安全的免费技术支持。
服务器资讯 / 香港服务器租用 / 香港VPS租用 / 香港云服务器 / 美国服务器租用 / 台湾服务器租用 / 日本服务器租用 / 官方公告 / 帮助文档
【Linux】进程的程序替换
发布时间:2024-02-27 20:53:47   分类:帮助文档
【Linux】进程的程序替换 文章目录 1. 程序替换1.创建子进程的目的是什么?2.了解程序是如何进行替换的3. 程序替换的基本原理当创建进程的时候,先有进程数据结构,还是先加载代码和数据?程序替换是整体替换,不是局部替换execl 返回值 4. 替换函数1. execl2. execv3. execlp4. execvp5. execle 2. 自定义shell缓冲区问题fgets 使用出现空格问题完整代码动图演示 1. 程序替换 1.创建子进程的目的是什么? 目标:为了让子进程帮父进程执行特定的任务 具体做法:1. 让子进程执行父进程的一部分代码 红框中的代码实际上是父进程的代码,在没有执行fork之前代码就有了,在没有创建子进程之前,父进程的代码加载到内存了,子进程被创建出来是没有独立的代码,这个代码是父进程的代码,父进程通过if判断分流让子进程去跑了 2.创建一个子进程不执行父进程的代码,而是让子进程在磁盘当中执行全新的程序,这种操作称之为进程的程序替换 2.了解程序是如何进行替换的 程序替换函数 execl 输入 man execl 查看程序替换接口 int execl(const char *path, const char *arg, …); 括号内部的 . . . 称为 可变参数列表,可以给c函数传递任意个数的参数 第一个参数为 要执行什么命令 第二个参数 为 要怎样执行程序 最后以NULL结尾表示参数传完了 创建test.c文件并输入以下内容 #include #include #include int main() { printf("begin......\n"); printf("begin......\n"); printf("begin......\n"); printf("begin......\n"); execl("/bin/ls", "ls", "-a", "-l", NULL); printf("end........\n"); printf("end........\n"); printf("end........\n"); printf("end........\n"); } 运行可执行程序发现,只有begin 以及执行 ls -l -a显示的指令 再次修改test.c文件内容如下 #include #include #include int main() { printf("begin......\n"); printf("begin......\n"); printf("begin......\n"); printf("begin......\n"); printf("我已经是一个进程啦,我的PID:%d\n",getpid()); execl("/bin/ls", "ls", "-a", "-l", NULL); printf("end........\n"); printf("end........\n"); printf("end........\n"); printf("end........\n"); } test.c 经过编译形成mytest可执行程序,./可执行程序就变成进程了,CPU调度进程 ,打印出代码中的打印语句,同时调用程序替换execl,将ls程序执行起来了 [yzq@VM-8-8-centos nn]$ file /bin/ls /bin/ls: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, BuildID[sha1]=c8ada1f7095f6b2bb7ddc848e088c2d615c3743e, stripped 使用的 /bin/ls 命令 实际上是一个可执行程序,所以ls程序是在磁盘上的 前面执行的是自己代码的一部分,当调用execl时,将磁盘中可执行程序替换当前进程的代码和数据 后半部分就不执行自己的代码了,执行ls所对应的代码 ,这个现象就叫做程序替换 程序替换就是让一个进程去执行另一个在磁盘中的程序,让一个进程把一个新的程序运行起来 3. 程序替换的基本原理 当前的进程执行当前代码时,如果执行了函数execl等接口,就会根据你所传入的程序的路径以及你要执行的名称及选项,把磁盘当中的一个其他的程序加载到对应的内存, 用新程序的代码替换当前进程的代码段,用当前进程的数据替换老进程的数据段 站在进程的角度 进程的程序替换有没有创建新的进程呢? 没有,只是将新的程序加载到当前进程的代码段和数据段,用CPU去调度当前进程就可以跑起来了 站在程序的角度 程序被加载了内存中,就可以称程序替换的接口(execl) 为加载器 当创建进程的时候,先有进程数据结构,还是先加载代码和数据? 修改test.c文件为以下内容 #include 2 #include 3 #include 4 int main() 5 { 6 execl("/bin/ls", "ls", "-a", "-l", NULL); 7 } 此时运行可执行程序,自己就写了一个ls命令 创建子进程,让子进程调用execl,在调用execl把代码和数据加载到内存 所以当创建进程的时候,先有进程数据结构,再加载代码和数据 程序替换是整体替换,不是局部替换 修改test.c文件内容如下 #include 2 #include 3 #include 4 #include 5 int main() 6 { 7 pid_t id=fork(); 8 if(id==0) 9 { 10 //child 11 printf("我是子进程:%d\n",getpid()); 12 execl("/bin/ls", "ls", "-a", "-l", NULL); 13 } sleep(5); 14 printf("我是父进程:%d\n",getpid()); 15 waitpid(id,NULL,0); 16 } 查看子进程完成替换后会不会影响父进程,如果影响父进程,就不应该打印父进程的这句话了 过了5秒钟,父进程结果打印出来,说明父进程不受子进程影响 程序替换只会影响调用进程,进程具有独立性 父子进程都有自己独立的PCB 地址空间 页表 也会自己的映射关系 虽然代码有可能是跟父进程共享,当子进程进行程序替换的时候,子进程会加载新进程的代码和数据 操作系统会发生写时拷贝,将代码和数据进行区分 ,使子进程形成新的映射关系,从而使子进程不会影响到父进程 execl 返回值 如果出错了,execl返回值为-1 修改test.c文件内容如下 #include 2 #include 3 #include 4 #include 5 int main() 6 { 7 pid_t id=fork(); 8 if(id==0) 9 { 10 //child 11 printf("我是子进程:%d\n",getpid()); 12 int n=execl("/bin/lsss", "lsss", "-a", "-l", NULL);//lsss命令不存在 13 printf("you can see me:%d\n",n); 14 exit(0); 15 } 16 sleep(5); 17 printf("我是父进程:%d\n",getpid()); 18 waitpid(id,NULL,0); 19 } 20 输入的lsss命令不存在,查询报错后的execl的返回值 程序替换只要成功,就会跑去执行新程序,失败了就会继续向后运行 所以execl程序替换成功不会有返回值——>如果替换失败,一定有返回值——>如果失败了,必定返回——>只要有返回值就失败了 说明不用对execl函数的返回值进行判断,只要继续向后运行一定失败 4. 替换函数 1. execl int execl(const char *path, const char *arg, …); l 代表 list 链表 path:代表你想执行谁 (需要带路径) 执行一个程序最基本的原则为:找到它,加载执行它 arg:你想怎么执行它(若想执行ls指令,是只执行ls,还是执行ls- l 、ls -l -a指令 在命令行怎么执行这个命令,就把参数一个一个的传递给execl就可以了 最终以NULL结尾 具体的实现以及返回值问题上面在演示程序替换时已经使用过啦 2. execv int execv(const char *path, char *const argv[]); v代表vector 容器 path:代表你想执行谁 (需要带路径) 把原来需要一个一个传的参数放在argv[]数组中 修改test.c文件内容 1 #include 2 #include 3 #include 4 #include 5 int main() 6 { 7 pid_t id=fork(); 8 if(id==0) 9 { 10 //child 11 printf("我是子进程:%d\n",getpid()); 12 char *const myargv[]={"ls", "-l", "-a",NULL}; 13 execv("/bin/ls",myargv); 14 exit(0); 15 } 16 sleep(5); 17 printf("我是父进程:%d\n",getpid()); 18 waitpid(id,NULL,0); 19 } 20 执行可执行程序,依旧可以执行ls指令 3. execlp int execlp(const char *file, const char *arg, …); 带p:代表当执行程序的时候,只需要指定程序名即可,系统会自动在PATH环境变量中查找 file: 不需要传路径,只需要把在PATH环境变量的指令传过来 最后以NULL结尾 #include 2 #include 3 #include 4 #include 5 int main() 6 { 7 pid_t id=fork(); 8 if(id==0) 9 { 10 //child 11 printf("我是子进程:%d\n",getpid()); 12 execlp("ls", "ls", "-l", "-a",NULL); 13 exit(0); 14 } 15 sleep(5); 16 printf("我是父进程:%d\n",getpid()); 17 waitpid(id,NULL,0); 18 } 执行可执行程序,依旧可以执行ls指令 4. execvp int execvp(const char *file, char *const argv[]); 带p:代表当执行程序的时候,只需要指定程序名即可,系统会自动在PATH环境变量中查找 v代表vector 容器 #include #include #include #include int main() { pid_t id=fork(); if(id==0) { //child printf("我是子进程:%d\n",getpid()); char* const myargv[]={"ls", "-l", "-a",NULL}; execvp("ls", myargv); exit(0); } sleep(5); printf("我是父进程:%d\n",getpid()); waitpid(id,NULL,0); } 5. execle int execle(const char *path, const char *arg, …, char * const envp[]); path:代表你想执行谁 (需要带路径) envp[]:代表自定义环境变量 如果调用程序替换时,若不想让子进程使用父进程的环境列表,想自定义环境变量,就可以自己传一个环境变量 在另一个目录中创建other.cc (以cc为结尾说明是一个c++程序),并输入以下内容 #include #include #include using namespace std; int main() { for(int i = 0; i < 5; i++) { cout<< "我是另一个程序,我的pid是:"<< getpid()< #include #include #include int main() { pid_t id=fork(); if(id==0) { //child printf("我是子进程:%d\n",getpid()); W> char*const myenv[]={"MYENV=YouCanSeeMe", NULL}; execle("/home/mydir/my/mm/myother", "myother", NULL,myenv); exit(0); } sleep(1); printf("我是父进程:%d\n",getpid()); int status=0; waitpid(id,&status,0); return 0; } 第一个路径为other.cc生成的可执行程序 myother 所在的 绝对路径 2. 自定义shell 编写极简版本的shell(bash) 目标:为了深刻的理解shell的运行原理 输入 ps ajx |grep bash ,发现bash就是一个进程 由于shell是一个进程,所以用while死循环 缓冲区问题 正常来说,运行可执行程序会显示命令行,但是由于没有\n刷新缓冲区,也没有使用相关的刷新库函数,所以命令行会一直在缓冲区中 直到 程序结束才显示,但是这是个死循环,所以什么都不会显示 执行可执行程序后即可显示命令行 fgets 使用出现空格问题 fgets 标准输入 按行获取 char *fgets(char *s, int size, FILE *stream); 从特定的标准输入当中获取对应的命令行输入,把对应的数据放在缓冲区中 执行可执行程序后,发现在命令行中输入 ls -a ,就会打印出 ls -a,但是输入时会多出一个空行 正常来说,都会使用回车来到下一行,而这个回车被fgets读取到了 将最后的回车符替换成’\0’ 此时就没有空格出现了 完整代码 : mybash.c ? ? ?? buffers #include #include #include #include #include #include #include #define MAX 1024 #define ARGC 64 #define SEP " " int split (char*commandstr,char*argv[]) { assert(commandstr); assert(argv); argv[0]=strtok(commandstr,SEP);//在commandstr以空格作为分割符 if(argv[0]==NULL)//切割失败 { return -1; } int i=1; while(1) { argv[i]=strtok(NULL,SEP);//继续切割空格 if(argv[i]==NULL) { break; } i++; } W>} void print(char*argv[]) { int i=0; for(i=0;argv[i];i++) { printf("%d:%s\n",i,argv[i]); } } int main() { while(1) { char commandstr[MAX]={0}; char*argv[ARGC]={NULL}; printf("[yzq@mymachine currpath]# "); fflush(stdout); char*s= fgets(commandstr,sizeof(commandstr),stdin); assert(s); (void)s;//保证在release方式发布的时候,因为去掉assert了,所以s就没有被使用,而带来的编译告警, 什么都没做,但是充当一次使用 commandstr[strlen(commandstr)-1]='\0';//将最后的回车符用'\0'覆盖掉 int n= split(commandstr,argv); //从命令行中获取的字符串传入argv中 if(n!=0) continue;//切割失败就什么都不做 // print(argv);//打印字符串 pid_t id=fork(); assert(id>=0); (void)id; if(id==0) { //child execvp(argv[0],argv); exit(1); } int status=0; waitpid(id,&status,0); } } 动图演示
香港云服务器租用推荐
服务器租用资讯
·广东云服务有限公司怎么样
·广东云服务器怎么样
·广东锐讯网络有限公司怎么样
·广东佛山的蜗牛怎么那么大
·广东单位电话主机号怎么填写
·管家婆 花生壳怎么用
·官网域名过期要怎么办
·官网邮箱一般怎么命名
·官网网站被篡改怎么办
服务器租用推荐
·美国服务器租用
·台湾服务器租用
·香港云服务器租用
·香港裸金属服务器
·香港高防服务器租用
·香港服务器租用特价