Linux 进程替换深剖
创始人
2024-04-01 03:42:22
0

目录

    • 传统艺能😎
    • 概念🤔
    • 细则🤔
    • 原理🤔
    • exec 函数🤔
      • execl😋
      • execlp😋
      • execle😋
      • execv😋
      • execvp😋
      • execve😋
    • 实现简易 shell🤔

传统艺能😎

小编是双非本科大二菜鸟不赘述,欢迎米娜桑来指点江山哦
在这里插入图片描述
1319365055

🎉🎉非科班转码社区诚邀您入驻🎉🎉
小伙伴们,满怀希望,所向披靡,打码一路向北
一个人的单打独斗不如一群人的砥砺前行
这是和梦想合伙人组建的社区,诚邀各位有志之士的加入!!
社区用户好文均加精(“标兵”文章字数2000+加精,“达人”文章字数1500+加精)
直达: 社区链接点我


在这里插入图片描述

概念🤔

你是否遇到过这样一个场景,**在一个多人项目中,有人用 Java 实现部分功能,有人用 C++ 实现部分功能,有人用 PHP,go,Python……**那该如何将这些不同的逻辑语言统一进来呢?

当我们 fork() 生成子进程后,子进程的代码与数据可以来自其他可执行程序。把磁盘上其他程序的数据以覆盖的形式给子进程。这样子进程就可以执行全新的程序了,这种现象称为程序替换\color{red} {程序替换}程序替换

细则🤔

首先需要知道进程替换是有原则的:

  1. 进程替换不会创建新进程,因为他只是将该进程数据替换为指定的可执行程序。而进程 PCB 没有改变,所以不是新的进程,进程替换后不会改变 pid

  2. 替换成功后,替换函数后的代码不会执行,因为进程替换是覆盖式的,替换成功后进程原来的代码就消失了,同理替换失败会执行替换函数后的代码

  3. 进程替换函数在进程替换成功后不返回,函数的返回值只会表示替换失败;进程替换成功后,退出码为替换后进程的退出码

原理🤔

替换是用的是替换函数:exec函数\color{red} {exec 函数}exec函数

该函数类型使用头文件:
函数原型:int execl(const char *path,const char *arg,…)

path:可执行程序的路径
arg:如何执行可执行程序
… :可变参数,是给执行程序携带的参数,在参数末尾加 NULL 表示参数结束

返回值:替换失败返回-1,替换成功不返回

当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,并从新程序的启动例程开始执行,本质上和虚拟内存和写时拷贝机制有点类似,通过在页表上进行映射操作进行替换:

在这里插入图片描述

那么子进程程序替换后,会对父进程产生印象吗?

毫无疑问,子进程被创建时是与父进程共享代码和数据,但当程序替换时,也就意味着需要进行写入操作,这时便需要将父子进程共享的代码和数据进行写时拷贝,此后父子进程的代码和数据也就分离了,因此进行程序替换后不会影响父进程的代码和数据!

exec 函数🤔

进程替换有六种替换函数,他们是以 exec 开头的函数,统称为 exec 函数:
在这里插入图片描述

execl😋

int execl(const char *path, const char *arg, ...);

第一个参数是要执行程序的路径,第二个参数是可变参数列表,代表我们需要以 list 的形式处理各种操作,内容是各个指令选项,并以NULL结尾

以 ls 命令为例:

execl("/usr/bin/ls", "ls", "-a", "-i", "-l", NULL);

execlp😋

int execlp(const char *file, const char *arg, ...);

第一个参数是要执行程序的名字,第二个参数是可变参数列表,代表我们需要以 list 的形式处理各种操作,内容是各个指令选项,并以NULL结尾

同样以 ls 命令为例:

execlp("ls", "ls", "-a", "-i", "-l", NULL);

execle😋

int execle(const char *path, const char *arg, ..., char *const envp[]);

第一个参数是要执行程序的路径,第二个参数是可变参数列表,代表我们需要以 list 的形式处理各种操作,内容是各个指令选项,并以NULL结尾,第三个参数是你自己设置的环境变量,比如我们设置了自己的 MYVAL 环境变量,在 mypro 程序内就可以使用该环境变量:

char* myenvp[] = { "MYVAL=2021", NULL };
execle("./mypro", "mypro", NULL, myenvp);

execv😋

int execv(const char *path, char *const argv[]);

第一个参数是要执行程序的路径,第二个参数是一个指针数组,代表我们需要以 vector 的形式处理各种操作,数组当中的内容是各个指令选项,数组以 NULL 结尾

还是以 ls 命令为例:

char* myargv[] = { "ls", "-a", "-i", "-l", NULL };
execv("/usr/bin/ls", myargv);

execvp😋

int execvp(const char *file, char *const argv[]);

第一个参数是要执行程序的名字,第二个参数是一个指针数组,代表我们需要以 vector 的形式处理各种操作,数组当中的内容是各个指令选项,数组以 NULL 结尾

例如,要执行的是ls程序:

char* myargv[] = { "ls", "-a", "-i", "-l", NULL };
execvp("ls", myargv);

execve😋

int execve(const char *path, char *const argv[], char *const envp[]);

第一个参数是要执行程序的路径,第二个参数是一个指针数组,代表我们需要以 vector 的形式处理各种操作,数组以NULL结尾,第三个参数是你自己设置的环境变量,比如我们设置了自己的 MYVAL 环境变量,在 mypro 程序内就可以使用该环境变量:

char* myargv[] = { "mypro", NULL };
char* myenvp[] = { "MYVAL=2021", NULL };
execve("./mypro", myargv, myenvp);

为了方便记忆,这里根据各个函数的后缀进行了归纳:

l:表示参数采用 list 列表的形式
v:表示参数采用 vector 数组的形式
p:表示能自动搜索环境变量 PATH 进行程序查找,即不需要列举程序路径
e:表示可以传入自己设置的环境(env)变量

但是事实上,只有execve才是真正的系统调用\color{red} {只有 execve 才是真正的系统调用}只有execve才是真正的系统调用,其它 5 个函数都是调用的execve,也就是说其他 5 个函数实际上是对 execve 的系统调用进行了封装,以满足不同用户的不同调用场景,下图就是各成员间的关系:
在这里插入图片描述

实现简易 shell🤔

shell 也就是之前说的命令行解释器,运行原理就是:当有命令需要执行时,shell 创建子进程,让子进程执行命令,而 shell 只需等待子进程退出即可

在这里插入图片描述
我们将 shell 的运行逻辑分为下面的五步:

  1. 获取命令行
  2. 解析命令行
  3. 创建子进程
  4. 替换子进程
  5. 等待子进程退出

我们之前学习了 fork 函数可以进行进程创建,exec系列函数可以进行子进程替换,wait 或者 waitpid 函数可以等待子进程,那么基础框架就勾勒出来了:

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#define LEN 1024 //命令最大长度
#define NUM 32 //命令拆分后最大个数
#define SEP " "char commend_line[NUM];
char* commend_args[SIZE];
char env_buffer[128];
char pwd_buffer[128];//实现改变当前工作目录的chdir
int ChangeDir(const char* new_path)
{chdir(new_path);return 0; //调用成功
}//增添自义定环境变量
void PutEnvInMyShell(char* new_env)
{putenv(new_env);
}int main()
{//shell 本质上就是一个死循环while(1){//显示提示符getcwd(pwd_buffer,128);printf("yuanlai45@Centos %s# ",pwd_buffer);fflush(stdout);//获取用户输入memset(commend_line,'\0',sizeof(commend_line)*sizeof(char)); //初始化为 \0fgets(commend_line,NUM,stdin);//获取到的是c风格的字符串 '\0'结尾commend_line[strlen(commend_line)-1]='\0';//清除\n //字符串分割,比如:"ls -a -l" ->  "ls" "-a" "-l"commend_args[0]=strtok(commend_line,SEP);int index=1;//给指令添加颜色if(strcmp(commend_args[0],"ls")==0){commend_args[index++]=(char*)"--color=auto";}while(commend_args[index++]=strtok(NULL,SEP));//内建命令,因为是想改变父进程所处的工作目录,所以并不需要去创建子进程if(strcmp(commend_args[0],"cd")==0 && commend_args[1]!=NULL){ChangeDir(commend_args[1]);continue;}//同理,我们想给父进程添加环境变量,以继承的方式去给子进程if(strcmp(commend_args[0],"export")==0 && commend_args[1]!=NULL){//目前我们环境变量的信息在commmand_line里面,每次会被清空//此处需要自己保存一下环境变量的内容//binPutEnvInMyShell(commend_args[1]);strcpy(env_buffer,commend_args[1]);PutEnvInMyShell(env_buffer);continue;}//创建进程,执行pid_t id = fork();if(id==0)//子进程{//程序替换execvp(commend_args[0],commend_args);exit(-1);//执行到这里说明子进程替换失败}int status = 0;pid_t ret = waitpid(id,&status,0);//阻塞等待if(ret>0){//打印进程终止信号和退出码printf("等待子进程成功,sig:%d,code:%d\n",status&0x7F,status&0xFF);}}         return 0;
}

我们自己实现的 shell 在子进程退出后都会打印子进程的退出码,我们可以根据这一点来区分当前使用的是 Linux 的 shell 还是我们自己实现的 shell

aqa 芭蕾 eqe 亏内,代表着开心代表着快乐,ok 了家人们

相关内容

热门资讯

韩媒:韩检方对尹锡悦、金建希等... 中新网12月29日电 据韩国媒体报道,负责调查韩国前第一夫人金建希案件的特检组29日发布最终调查结果...
着力健全有利于“长钱长投“的制... 12月29日,A 股三大指数开盘后涨跌互现,沪指强势向上,冲击9连阳。截至10:23,A500ETF...
政策性农业保险的角色演变与制度... 本文字数:4989字 阅读时间:10分钟 作者简介:马彪,首都经济贸易大学金融学院副教授。 文章来...
推动楼市政策精准落地丨社评 明年着力稳定房地产市场的大政方针已定,抓好落实是关键。刚刚召开的全国住房城乡建设工作会议,重点列出了...
Adobe 因使用 SlimP... AIPress.com.cn报道 12月29日消息,作为全球创意软件巨头,Adobe 正面临其首起重...
伟星新材:竞争优势明显 保持积... 12月28日,伟星新材(002372)发布公告,伟星新材(002372)于2025年12月25日召开...
健全数据制度 释放乘数效应——... 来源:经济日报 党的二十届四中全会审议通过的《中共中央关于制定国民经济和社会发展第十五个五年规划的建...
海关出口退税律师张严锋:套用其... 2018年3月2日,B稽查局对A公司涉税事项进行检查。经检查,B稽查局认为A公司涉嫌通过套用他人出口...
哈尔滨权威刑事律师服务推荐:谷... 在哈尔滨,当人们遭遇刑事法律问题时,往往会困惑于刑事律师服务哪家权威刑事律师推荐哪些刑事辩护律师哪个...
原创 《... 2025年12月26日,《晋中市平遥牛肉保护和发展条例》新闻发布会在晋中举行。该条例经山西省人大常委...