网站建设优化服务信息,招商外包公司,超链接html代码,网站网页设计费用文章目录1. 程序替换1.创建子进程的目的是什么#xff1f;2.了解程序是如何进行替换的3. 程序替换的基本原理当创建进程的时候#xff0c;先有进程数据结构#xff0c;还是先加载代码和数据#xff1f;程序替换是整体替换#xff0c;不是局部替换execl 返回值4. 替换函数1…
文章目录1. 程序替换1.创建子进程的目的是什么2.了解程序是如何进行替换的3. 程序替换的基本原理当创建进程的时候先有进程数据结构还是先加载代码和数据程序替换是整体替换不是局部替换execl 返回值4. 替换函数1. execl2. execv3. execlp4. execvp5. execle2. 自定义shell缓冲区问题fgets 使用出现空格问题完整代码动图演示1. 程序替换
1.创建子进程的目的是什么
目标为了让子进程帮父进程执行特定的任务
具体做法1. 让子进程执行父进程的一部分代码 红框中的代码实际上是父进程的代码在没有执行fork之前代码就有了在没有创建子进程之前父进程的代码加载到内存了子进程被创建出来是没有独立的代码这个代码是父进程的代码父进程通过if判断分流让子进程去跑了
2.创建一个子进程不执行父进程的代码而是让子进程在磁盘当中执行全新的程序这种操作称之为进程的程序替换
2.了解程序是如何进行替换的
程序替换函数 execl 输入 man execl 查看程序替换接口 int execl(const char *path, const char *arg, …); 括号内部的 . . . 称为 可变参数列表可以给c函数传递任意个数的参数 第一个参数为 要执行什么命令 第二个参数 为 要怎样执行程序 最后以NULL结尾表示参数传完了 创建test.c文件并输入以下内容
#includestdio.h#includestdlib.h#includeunistd.hint 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文件内容如下
#includestdio.h#includestdlib.h#includeunistd.hint 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程序执行起来了 [yzqVM-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文件为以下内容
#includestdio.h2 #includestdlib.h3 #includeunistd.h4 int main()5 {6 execl(/bin/ls, ls, -a, -l, NULL); 7 }
此时运行可执行程序自己就写了一个ls命令 创建子进程让子进程调用execl在调用execl把代码和数据加载到内存 所以当创建进程的时候先有进程数据结构再加载代码和数据
程序替换是整体替换不是局部替换
修改test.c文件内容如下 #includestdio.h2 #includestdlib.h3 #includeunistd.h4 #includesys/wait.h5 int main()6 {7 pid_t idfork();8 if(id0)9 {10 //child11 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文件内容如下
#includestdio.h2 #includestdlib.h3 #includeunistd.h4 #includesys/wait.h5 int main()6 {7 pid_t idfork();8 if(id0)9 {10 //child11 printf(我是子进程:%d\n,getpid());12 int nexecl(/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 #includestdio.h2 #includestdlib.h3 #includeunistd.h4 #includesys/wait.h5 int main()6 {7 pid_t idfork();8 if(id0)9 {10 //child11 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结尾 #includestdio.h 2 #includestdlib.h 3 #includeunistd.h 4 #includesys/wait.h 5 int main() 6 { 7 pid_t idfork(); 8 if(id0) 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 容器 #includestdio.h #includestdlib.h #includeunistd.h #includesys/wait.h int main() { pid_t idfork(); if(id0) { //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 iostream
#include unistd.h
#includestdlib.h
using namespace std; int main()
{ for(int i 0; i 5; i) { cout 我是另一个程序我的pid是: getpid()endl; cout MYENV: (getenv(MYENV)NULL?NULL:getenv(MYENV)) endl; sleep(1); } return 0;
} 修改test.c文件为以下内容 #includestdio.h #includestdlib.h #includeunistd.h #includesys/wait.h int main() { pid_t idfork(); if(id0) { //child printf(我是子进程:%d\n,getpid());
W char*const myenv[]{MYENVYouCanSeeMe, NULL}; execle(/home/mydir/my/mm/myother, myother, NULL,myenv); exit(0); } sleep(1); printf(我是父进程:%d\n,getpid()); int status0; 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 #includestdio.h#includeunistd.h#includestring.h#includeassert.h#includesys/types.h#includesys/wait.h#includestdlib.h#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 i1;while(1){argv[i]strtok(NULL,SEP);//继续切割空格if(argv[i]NULL){break; }i; }
W}void print(char*argv[]){int i0;for(i0;argv[i];i){ printf(%d:%s\n,i,argv[i]);}}int main(){ while(1){char commandstr[MAX]{0};char*argv[ARGC]{NULL};printf([yzqmymachine 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 idfork();assert(id0);(void)id;if(id0){//childexecvp(argv[0],argv);exit(1);}int status0;waitpid(id,status,0);}}
动图演示