一:最简单的看看程序替换是什么样的(单个进程版)
1 #include<stdio.h>
2 #include<unistd.h>
3 #include<stdlib.h>
4 int main()
5 {
6 printf("Before : I am a process , myPid:%d,myPPid:%d\n",getpid(),getppid());
7
8 execl("/usr/bin/top","top",NULL);
9
10 printf("After : I am a process , myPid:%d,myPPid:%d\n",getpid(),getppid());
11
12 return 0;
13 }
程序在调用execl
之后不能打印"After"信息,因为一旦execl
被调用,当前的进程映像将被替换,因此第二个print中的代码将不会被执行。
二:进程替换的原理
用fork创建子进程后执行的是和父进程相同的程序,子进程往往要调用一种exec函数以执行另一个程序。
当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动
例程开始执行。
调用exec并不创建新进程,所以调用exec前后该进程的id并未改变
三:验证各种程序替换的接口(多进程版)
创建一个子进程,并使用execl
函数来替换子进程的映像,执行/usr/bin/ls -l -a
命令。
1 #include<stdio.h>
2 #include<unistd.h>
3 #include<stdlib.h>
4 #include<sys/types.h>
5 #include<sys/wait.h>
6 int main()
7 {
8 pid_t id =fork(); //child process return 0 , parent process return child pid
9
10 if(id==0) //child process
11 {
12 printf("Before : I am a process , myPid:%d,myPPid:%d\n",getpid(),getppid());
13 execl("/usr/bin/ls","-l","-a",NULL);
14 printf("After : I am a process , myPid:%d,myPPid:%d\n",getpid(),getppid());
15 exit(0);
16 }
17
18 //parent process
19 pid_t ret = waitpid(id,NULL,0); //child process pid , status , WNOHANG
20 if(ret>0)
21 {
22 printf("wait sucess,father pid:%d,wait child pid:%d\n",getpid(),ret);
23 }
24 return 0;
25 }
~
execl
会替换当前子进程的映像,包括程序的代码和数据
当改子进程程序替换之后,该子进程对应的PCB、进程地址空间以及页表等数据结构都没有发
生改变,对应的execl退出,会继续被父进程等待收回
四:总结
替换函数:
其实有六种以exec开头的函数,统称exec函数
exec参数的上传就如命令行一般,命令行怎么打,参数就怎么加,第一个参数为程序的路径,最后一个参数有再补个NULL即可
函数解释:
这些函数如果调用成功则加载新的程序从启动代码开始执行,不再返回。如果调用出错则返回-1
所以exec函数只有出错的返回值而没有成功的返回值。
// 尝试执行ls命令
if (execl("/usr/bin/ls", "ls", "-l", "-a", NULL) == -1) {
// 如果execl失败,打印错误信息并退出
perror("execl failed");
exit(EXIT_FAILURE); // 使用非零值退出,表示程序因错误而终止
}
// 如果execl成功,if里面的代码不会被执行
命名理解:
这些函数原型看起来很容易混,但只要掌握了规律就很好记。
execl的list链表与execv的vector数组的代码示例:
execl与execlp是否带路径代码示例:
execl
函数需要你提供程序的完整路径作为第一个参数
execlp
函数使用程序名来搜索程序的路径,不需要提供完整路径。它使用环境变量PATH($PATH)
来查找程序,如果PATH
环境变量包含的目录中有与程序名匹配的可执行文件,execlp
会尝试执行它。
环境变量env:
execle 是需要环境变量的,但即使我们参数中不提供环境变量,execle也能正常运行
环境变量即使没有给子进程也能被子进程拿到,那环境变量是什么时候给进程的呢?
所以即使不通过传参方式,进程也能在程序地址空间内找到环境变量。当程序被替换时,环境变量的信息不会被替换
1:新增环境变量
A:直接在bash中新增环境变量
在bash中新增的环境变量一路被子进程继承,做到添加环境变量
B:父进程地址空间内使用putenv函数
在Linux操作系统中,putenv
是一个用于设置环境变量的函数。可以在程序运行时修改或添加环境变量
putenv
函数会修改程序的全局环境,这个环境变量的设置会继承到该程序启动的任何子进程。
int putenv(char *string);
标准代码示例:
#include <stdlib.h>
int main() {
char *env_var = "MY_VAR=123";
if (putenv(env_var) == -1)
{
// 处理错误
}
// 继续你的程序逻辑...
return 0;
}
在父进程中使用putenv添加环境变量,出现在子进程的环境变量中:
2:彻底替换环境变量
当我们使用了自己的环境变量参数时,原先的环境变量会被完全替换掉
exec调用举例总结:
exec也可以调用自己写的程序:
五:自定义壳shell
ls 可以查看当前文件夹下的文件
ps
可以显示当前系统中正在运行的进程的状态。
所以要写一个shell,需要循环以下过程:
1:获取命令行
2:解析命令行
3:建立一个子进程(fork)
4:替换子进程(execvp)
5:父进程等待子进程退出(wait)