网站不稳定有什么影响,连云港市网站设计,深圳找网站建设公司哪家好,做网站常用图标先前fork说创建子进程执行代码#xff0c;如何让子进程执行和父进程完全不一样的代码?程序替换。
一 单进程替换演示 1 execl函数使用 最近转到在vs code下写代码#xff0c;之前也在xhell下用过execl函数#xff0c;所以才想写篇博客总结总结#xff0c;没想到在vs code… 先前fork说创建子进程执行代码如何让子进程执行和父进程完全不一样的代码?程序替换。
一 单进程替换演示 1 execl函数使用 最近转到在vs code下写代码之前也在xhell下用过execl函数所以才想写篇博客总结总结没想到在vs code下写了一句简单的execl却总是替换失败。想来想去也不知为何感觉翻车了。 后来才知道execl的路径没给对usr前面要带/因为usr是根目录下的路径少了个根目录系统也不知道去哪找usr因为可能有很多目录都叫usr可是当我改了后。 嗯?还是失败了我记得早上就是加了/没反应我才奇怪的临近中午我才意识到vs code的一个坑人的点你修改了代码不保存编译的还是旧代码只有ctrl s保存一下再编译才是新程序。最最重要的是这个ctrl s不能在输入终端的时候保存必须得在代码处保存。
哪里是终端呢下面这就是终端ctrl~可进入。 二 替换原理 替换原理 单进程下是直接把原程序的所有代码替换那如何从头开始执行cpu上的寄存器是有保存下一句指令的地址的现在代码要更换了你说这个地址还有用吗那如何拿到新代码的地址呢先来聊聊替换时做了什么如上图execl函数的其中一个参数是/usr/bin/ls此时系统会把这个ls可执行文件的代码加载到内存。 由编译原理的内容可得代码在编译的时候就已经分段了因为要方便操作系统拿去分段映射也已经给每条数据代码生成了地址这个地址是逻辑地址和虚拟地址几乎没区别应该可以直接填入页表左侧而且还会生成一个表头让系统知道各段的起始位置在哪而这样cpu拿到表头就可以从代码段开始执行了此时页表将新程序的物理地址填入替换成功后先前的代码和数据占的内存按理说是要被释放的会不会没成功然后也被释放了os应该不会不会干出还没替换完就断自己后路的事。
三 多进程替换 当我们了解了单进程的替换工作后我们就知道此时父子进程一定会发生写时拷贝因为父子进程代码和数据是独立的我们还发现pid一直不变这说明进程替换没有创建新进程。
补充 1 为什么子进程进程内的下一句代码为啥不执行因为已经被替换了替换成功就不执行失败了呢?继续往后执行。 补充 2 exec函数总结 第一个参数是为了找到程序要么写全路径要么带P去PATH变量里存的路径去找这个PATH变量也是怪怪的有时候我把自己的可执行程序的路径写入了然后我第一个参数就不传具体路径了结果还是说没找到。 第二个参数是和选项有关我之前好奇明明我第一个参数/usr/bin/ls不是已经传了ls吗(那为什么第二个参数还要传ls呢不知道啊大佬就是这么设计的我们直接用就好了)如果是带l的给main函数(这个main函数就是替换程序ls的main函数)传的参数就像命令行参数一个一个传以NULL结尾带v的则是把这些命令行参数归成数组传参。所以说exec函数带l和带v是冲突的。带e的则与环境变量有关 exec函数还可以用自己写的程序甚至是其它语言的代码来替换。 先前说了替换本质是修改页表和加载代码文件到内存这个工作肯定是调用了系统调用完成的而且任意语言写的代码本质都是01数据那就可以用统一的方式加载到内存修改页表不就是填地址吗这和语言一点关系都没有所以在同一平台下execl函数可以调用任意语言写的可执行程序使其变成进程的代码。所以在execl函数看来都是一样的文件都是先加载到内存然后修改页表即可。
补充 3 exec函数导环境变量 既然exec系列函数是可以调用任意的可执行文件那如果我要传环境变量如何传呢? 也就是说exec系列函数如何传环境变量给另一个可执行文件。由于程序替换不改变环境变量这些数据所以执行另一个可执行文件还是能拿到原先的环境变量。如下代码打印显示即可。 注:test.cpp就是被编译成了test2这个可执行文件。 exec系列函数中只要是带e的函数就是要导入新的环境变量经过测试这个新的环境变量表会覆盖当前进程原有的环境变量。 四 xshell实现 在xhell下也用了不少的命令而随着进程的学习我们也就可以初步实现xhell了。每次在linux下下的vim写代码都要包含好多头文件下面代码为了简洁就不显示头文件了大家到时候一个个man熟悉熟悉man的使用以及提高看手册的能力。 先从主函数入手。第一步是一些变量的初始化在3 解析指令分割字符串再解释变量保存的什么的我们直接看2。
int main()
{while(1){//1 初始化rdir 0;filename NULL;2: 人机互动,接收指令 int argc interact();if(!argc)//argc为零,无指令{continue;}//3: 分割字符串splitstring();//4 内建命令int ret BuildCommand(argc); //5 执行普通指令if(!ret)NormalExecute();}return 0;
}2 人机互动 接收指令 当我们在xshell下输入指令总会显示这一行然后就阻塞着等待输入指令。 所以我们的第一步是先打印bash命令行[USR主机名路径]$ 这个路径的获取有点绕我是用getcwd获取放入数组再打印的因为如果是直接获取环境变量这个路径是一直不变的至于为什么不变呢?如下测试。 printf(%s\n,getenv(PWD)); chdir(/home);printf(%s\n,getenv(PWD));execl(/usr/bin/pwd,pwd,NULL); 如下环境变量PWD并不会改变所以我猜测chdir改变的目录是当前进程的工作目录-cwd在进程运行时用ls /proc/pid才可以显示。 还有就是PWD要显示当前目录ls显示目录下的文件信息这两个命令用的cwd变量存的目录根本就不用环境变量PWD所以我猜测cd命令改变的也是工作目录只是顺便更新了pwd环境变量。代码里我就没改这个环境变量了就直接用个数组来保存当前路径了。
#define LEFT [
#define RIGHT ]
#define LABLE #
#define LINE_SIZE 1024
char pwd[LINE_SIZE];
const char*getusr()
{return getenv(USER);
}
const char*gethost()
{return getenv(HOSTNAME);
}
void getpwd()
{getcwd(pwd,LINE_SIZE);getcwd是获取当前工作目录并拷贝到pwd数组中数组长度为1024
}
int interact()
{getpwd();封装成三个函数分别获取USR,Host和Pwdprintf(LEFT%s%s %sRIGHT LABLE,getusr(),gethost(),pwd);接收指令,保存到command数组中用于后面解析指令fgets(command,LINE_SIZE,stdin);//ls -a\n去除\n字符command[strlen(command) - 1] \0;检查重定向符号 check_command(command); 这个函数我放在3 解析指令一同说明return strlen(command)-1;
}
3 解析指令,分割字符串 因为我们输入的ls -a -l被当成“ls -a -l”整个字符串了所以我们需要用空格分离成一个一个字符后面要用于execl的传参。
#define DELIM \t 分隔符的宏定义包括空格和Tab键
#define OUT_RDIR 1
#define IN_RDIR 2
#define APPEND_RDIR 3
#define LINE_SIZE 1024
#define OPTION_SIZE 32char* filename;
int rdir 0;
char command[LINE_SIZE];
char* argv[OPTION_SIZE];void check_command(char * pos)
{while(*pos) 遍历指令数组{if(*pos )//输入重定向{rdir IN_RDIR;*pos \0;while(isspace(*pos)) pos; 跳过空格 cat test.txtfilename pos;break;}else if((*pos) ) 输出重定向 cat test.txt{*pos \0; if((*pos) )追加重定向 cat test.txt{ rdir APPEND_RDIR;*pos \0;while(isspace(*pos)) pos;//跳过filename pos;break;}rdir OUT_RDIR;while(isspace(*pos)) pos;//跳过filename pos; break;}else {;}pos;}}void splitstring()
{//ls -a -lint i 0;strtok函数的使用有些奇怪第一次要传字符数组之后的解析就传NULL空指针第二个参数是DELIM这个参数是分隔符集合strtok只要碰到 \t内的字符
一次切割完成然后赋值给argv数组。argv[i] strtok(command,DELIM); while(argv[i] strtok(NULL,DELIM));} 如上rdir和filename记录了重定向的类型以及重定向的文件名,所以每次循环是一次指令输入到处理下一次输入新指令这两个变量的信息就要清空。
4 执行内建命令 此时我们终于可以解开内建命令的面纱了还是那句话真的就只是一个函数而已。
#define LINE_SIZE 1024
#define OPTION_SIZE 32int lastcode 0; 保存的是退出码char command[LINE_SIZE];
char* argv[OPTION_SIZE];
char penv[OPTION_SIZE];int BuildCommand(int argc)
{if(strcmp(argv[0],ls)0 ) 在ls -a -l命令中加上颜色选项,我们平时输入的ls -a都是bash默认加上去的{argv[argc] --color;argv[argc] NULL;}if(strcmp(argv[0],cd) 0) chdir改进程的工作目录,当然不能创建子进程执行了。{chdir(argv[1]);改工作目录并保存到pwd数组中getpwd();return 1;}else if(strcmp(argv[0],echo) 0) {if(strcmp(argv[1],$?)0) 输出退出码{printf(%d\n, lastcode);打印完后退出码归零所以多次echo第二次为0lastcode 0; return 1;}echo $PATHelse if(argv[1][0] $) 输出环境变量{printf(%s,getenv(argv[1]1));获取环境变量并打印return 1;}}export MYVALUE10000else if(strcmp(argv[0],export) 0) 设置环境变量{memcpy(penv,argv[1],strlen(argv[1]));//putenv(argv[1]);不能直接put argv存的是指向command字符数组的指针下一次输入命令就被覆盖了那环境变量就不存在了。putenv(penv); //这种保存在一个数组内的方式,只能存一个环境变量,懒得再实现了实现主要是为了理解内建命令不太想完完整整地造一遍 }return 0;
}5 执行普通命令
void NormalExecute()
{//创建子进程int id fork();if(id 0){perror(fork:);}else if(id 0)//子进程{if(rdir OUT_RDIR) 用dup2函数做重定向{int fd open(filename,O_WRONLY|O_CREAT|O_TRUNC,0660);dup2(fd,1);}else if(rdir IN_RDIR){int fd open(filename,O_RDONLY,0660);dup2(fd,0);}else if(rdir APPEND_RDIR){int fd open(filename,O_WRONLY|O_CREAT|O_APPEND,0660);dup2(fd,1);}//程序替换execvp(argv[0],argv); 这一步才是真正的执行指令argv[0]是指令名称,选项都在argv数组中直接传入即可这就是exec函数中带p的传的是选项数组exit(2);}else//父进程{int status 0;int ret waitpid(id,status,0);//阻塞等待lastcode WIFEXITED(status);}
}