网站整合建设是啥意思,网站建设十,网站建设公司沈阳,俄罗斯国际空间站Linux程序替换创建子进程的目的#xff1f;程序替换如何实现程序替换#xff1f;什么是程序替换#xff1f;先见一见单进程版本的程序替换程序替换原理多进程版本的程序替换execl函数组简易版Shell创建子进程的目的#xff1f; 目的:为了帮助父进程完成一些特定的任务…
Linux程序替换创建子进程的目的程序替换如何实现程序替换什么是程序替换先见一见单进程版本的程序替换程序替换原理多进程版本的程序替换execl函数组简易版Shell创建子进程的目的 目的:为了帮助父进程完成一些特定的任务 子进程帮助父进程完成任务的方式有那些? 1、执行一段父进程的代码这是我们初学者经常使用子进程的方式 2、让子进程执行一段与父进程完全不一样、全新的代码 那么如何做到让子进程执行一段全新的代码呢 对子进程实现程序替换 程序替换
如何实现程序替换 Linux给我们提供了7个接口 #include unistd.h int execl(const char *path, const char *arg, …); int execlp(const char *file, const char *arg, …); int execle(const char *path, const char *arg, …, char * const envp[]); int execv(const char *path, char *const argv[]); int execvp(const char *file, char *const argv[]); int execvpe(const char *file, char *const argv[], char *const envp[]); int execve(const char *path, char *const argv[], char *const envp[]); 这些函数叫做exec函数组我们暂且先不详细讲解着些函数的具体用法我们后文在重点讲解 我们现在只需知道通过调用这7个函数中的任意一个就可以完成程序替换 什么是程序替换
先见一见单进程版本的程序替换 为此我们先从最简单的ececl()函数讲解着走 execl函数第一个参数path表示我们要替换的程序在哪里第二个参数arg表示我们想用怎么样的方式运行我们的程序写完记得在传个参数NULL结尾 test代码 运行结果如下 现象我们观察到了我们的进程执行了我们begin的打印然后又立马执行了ls -a -l命令随之整个进程就运行结束了我们发现并没有像我们想象的那样接着运行我们所写的printf(“end…\n”);语句 这是为什么 想要弄清楚这些现象产生的原因我们就必须清楚execl()接口进行程序替换的原理 程序替换原理 首先根据上面的代码我们自己写的代码(比如:打印begin的语句)在运行起来的时候就已经是一个进程那么这时候该进程就已经有了自己的内核数据结构了比如pcb、进程地址空间、页表等现在当我们的进程运行到ececl语句的时候会发生程序替换 当我们自己写的程序运行到execl()语句时就会根据ececl()的path参数将ls命令从磁盘加载进物理内存加载到物理内存的那个地方呢加载到我们自己写的程序对应的物理内存上的位置 也就是说用ls命令的数据和代码替换原来老的程序的代码和数据物理空间还是原来的物理空间页表的映射关系也基本不变如果ls命令数据和代码太多了os会在页表增加一些映射关系然后该进程开始重新运行一段新的程序 明白了上面的原理我们就能明白为什么我们的老程序在运行到execl过后会执行一段新的程序同时由于新程序的代码和数据替换了老程序的代码和数据当新程序运行结束过后并不会再去运行原来老的程序的剩下的语句比如上面代码中的打印end语句因为打印end语句属于老程序的代码而老程序的代码在execl接口中就被替换了新程序运行完毕也就表示这个进程终止了新程序的退出码也就是该进程的退出码了 换而言之就是当我们的程序利用execl完成程序替换过后当前进程的退出码就由新程序决定了 为此我们可以查看一下在程序替换过后进程的退出码 测试代码 当然我们也可以让我们的程序执行一个错误的ls命令再次来观察当前进程的退出码 站在进程的角度的角度来看等待程序替换 现在我是一个进程我的代码段和数据段都在物理内存上有一份映射现在我调用了execl接口execl会将我在物理内存上的数据和代码用一个新的程序的数据和代码来替换然后我当前进程开始重新运行这段新程序并且我(当前进程)的退出码由这个新程序的main函数返回值来确定在整个程序替换期间我(当前进程)并没由被销毁我依旧存在在完成程序替换过后依旧是我(当前进程)来运行这段新程序并不会创建一个新的进程来运行新的代码 站在新程序的角度来看待程序替换 我是一个程序我安安静静的躺在磁盘看“电视”突然某一天我被execl加载进内存让后某个进程就要求我帮他办件事那么我新程序被加载进内存这个动作是由谁完成的 execl execl就充当着这个加载器的角色 既然我自己写的程序都能加载新的程序那么OS 当我们想要运行某段程序的时候OS会首先为我们的程序建立pcb、进程地址空间、页表等内核数据结构也就是说这时候进程已经创建好了然后在让当前进程调用execl()接口将我们的程序加载进内存然后再开始运行我们程序而我们的程序是从main函数开始的但是我们的进程是先调用的execl过后我们的程序才开始运行起来的那么换而言之在execl内部帮助我们完成程序替换过后execl会调用该程序的main函数然后让该程序成功运行起来 多进程版本的程序替换 上面我们讲解了单进程版本的程序替换和程序替换的原理接下来我们来尝试一下多进程版本的程序替换 也就是说我们让我们的子进程去执行一段与父进程完全不一样的代码 测试代码 当然我们也可以让子进程去运行我们自己写的程序无论我们的程序是用什么语言写的 比如现在我用C语言写一个程序去运行一个C写的程序 测试 被子进程运行的程序: 主程序: 运行结果 接下来我们来讲解一下多进程进行程序替换的原理 首先我们的父进程也就是mytest利用fork函数创建了一个子进程对吧 那么刚开使的时候子进程会继承父进程的大多数信息包括子进程会共享着父进程的代码和数据通过前面的学习我们知道当我们的子进程想要修改与父进程共享的数据时会发生写时拷贝在物理内存中重新找一块新空间让后将将需要修改的数据拷贝到新空间中去然后修改子进程页表映射到该物理内存的映射关系然后再让子进程去修改数据以此达到进程之间的相互独立 那么现在也是这样刚开始的时候父子进程都共享着同一块物理内存的数据和代码 当我们的子进程调用execl函数进行程序替换时是会用程序的代码和数据来替换子进程原来数据段和代码段存的信息的如果我们直接在“数据”和“代码”这块空间进行替换的话我们就会将父进程的代码和数据也替换掉从而影响到了进程的独立性我们现在的目的是不想影想父进程而让子进程执行一段全新的代码为此os也会也会触发写时拷贝当我们的子进程尝试修改代码段和数据段的信息时os也会去重新找一物理内存中重新找一块空间来存储子进程的代码和数据同时修改子进程代码段和数据段映射关系 重新理解Shell运行原理 明白了上面的过程我们就能更好的理解Shell的运行原理了首先shell从命令行接受到我们的命令后会创建一个子进程来执行我们的命令然后在让该子进程调用execl函数来进程程序替换替换掉子进程从Shell哪里继承下来的代码和数据然后让子进程开始运行这段程序 execl函数组 下面我们来正式介绍一下execl函数组 int execl(const char *path, const char *arg, …) 参数 path//用于指定我们执行的命令在哪里 arg 可变参数可以传任意个参数该参数的作用主要是告诉execl()你想怎样执行这段程序你在命令行是怎么写的在arg参数就怎么写注意分割比如我们需要让execl按照ls -a -l的格式执行ls命令那么我们喂给execl的参数就是(从第二个参数起)“ls”、“-a”、“-l”NULL一个选项一个字符串注意当我们确定完程序运行的格式过后必须以再传递一个NULL结尾表示我们已经传递完当前程序的执行的格式 比如 返回值该函数只会返回-1由于execl是进行程序替换当execl完成程序替换那一刻开始execl后续的代码都被替换成了新程序的代码和数据根本就运行不到后续的代码和数据因此也就无法返回程序替换成功的返回值当我们的程序替换失败的时候我们进程的老数据和代码并没由被替换掉当前进程依旧按照顺序执行剩下的代码同时才能向上面返回-1来表示程序替换失败 也就是说execl程序替换成功是没有返回值的如果execl有返回值那么说明程序替换失败当前进程就会执行execl后续的代码 int execv(const char *path, char *const argv[]); 我们可以发现execv接口与execl接口十分相似但是在参数上却并不是一样的 execl的参数可以是任意个而execv的参数只有2个 同时execv的功能与execl的功能一样只是在使用上有点区别 我们可以看一看execl就表示execl这个llist就代表着列表的意思表示execl的程序运行格式以列表的形式传递 execv表示execv这个vvector表示数组的意思就表示程序运行的格式以数组的形式传递 具体演示 程序运行结果 程序依旧正常运行 有了前面的理解后面我们在认识其他exec函数就轻松了 int execlp(const char *file, const char *arg, …); execlpl表示以列表的形式传递程序如何运行这个程序 p:表示path表示我们只需告诉execlp我们要运行的程序的名称也就是传递file参数execlp会自动去PATH环境变量下搜索 具体演示 程序运行结果 int execle(const char *path, const char *arg, …,char *const envp[]); l如何运行程序的参数以列表的形式传递 eenv表示自己维护环境变量 比如我们可以将当前进程的环境变量表传递给我们的新程序 我们的新程序就可以使用这张环境变量表 子进程去替换的程序 主程序 程序运行结果 我们也可以向环境变量里面加一点东西进去 这里我们就需要使用putenv()这个函数了putenv功能是向环境变量表中导入一个环境变量 讲解到这里其他的execl函数也就依此类推了 只不过我们需要注意一下在exec函数组中 只有int execve(const char *filename, char *const argv[])是真正的系统调用 其他的exec函数是基于该系统调用进行的封装
简易版Shell
#includestdio.h
#includestdlib.h
#includesys/wait.h
#includesys/types.h
#includeunistd.h
#includestring.h
#includestdbool.h#define COMMOD_NUM 256
#define ARGV_NUM 64bool Strtok(char*commod,char**argv)
{//先跳过空格size_t i0;size_t lenstrlen(commod);size_t k0;while(ilencommod[i] )i;if(ilen)return false;size_t begini;size_t endbegin;while(beginlen){while(commod[end]! commod[end]!\0)end;commod[end]\0;argv[k]commodbegin;beginend1;endbegin; }argv[k]NULL;return true;
}
extern char**environ;
int main()
{while(1)
{printf([cxkVM-12-16-centos myshell]$ );
char commod[COMMOD_NUM]{0};//用于接受从命令行输入的命令
char*argv[ARGV_NUM]{NULL};//用于存储将commod切割成一个一个字符串的指针
fgets(commod,COMMOD_NUM,stdin);
commod[strlen(commod)-1]\0;//分割字符串if(Strtok(commod,argv)false)continue;
//创建子进程
pid_t idfork();
if(id0)
{
int n execvp(argv[0],argv);
if(n-1)
{printf(-bash: %s: command not found\n,argv[0]);exit(1);
}
}
//父进程
waitpid(-1,NULL,0);
}return 0;
}