本文介绍了进程终止、进程等待、进程替换等进程控制的相关概念。
我们在之前的内容中已经了解过系统调用fork,今天我们再来深入了解一下fork。
退出码是用来标识一个进程任务执行结果的情况。因为成功只有一种情况,而失败的情况很多,因此,一般情况下0表示执行成功,非0表示执行失败。非0的数字不同,所表示的错误不同。系统对于退出码一般都有着相应的文字藐视,当然我们也可以自定义,也可以直接使用系统给定的映射关系。(例如,strerror这个函数)
文件test4.c
1 #include<stdio.h>
2 #include<string.h>
3 int main()
4 {
5 for(int i = 0;i < 200; ++i)
6 {
7 printf("%d : %s\n",i,strerror(i));
8 }
9 return 0;
10 }
运行:
可以看到strerror函数有134种系统映射好的文字描述的错误码。
_exit()和exit()不同:
test4.c
1 #include<stdio.h>
2 #include<stdlib.h>
3 int main()
4 {
5 printf("hello world!");
6 sleep(1);
7 exit(1);
8 return 0;
9 }
test4.c
1 #include<stdio.h>
2 #include<unistd.h>
3 #include<stdlib.h>
4 int main()
5 {
6 printf("hello world!");
7 sleep(1);
8 _exit(1);
9 return 0;
10 }
同样的代码,调用exit和调用_exit终止进程后,得到的结果却不同。exit会主动刷新缓冲区,而_exit不会主动刷新缓冲区,这是因为缓冲区所在的位置。
之前讲进程状态的时候,我们讲到一种特殊的转态:僵尸状态:子进程退出,父进程没有等待子进程(没有回收子进程的资源),会造成子进程处于僵尸状态,造成内存泄漏,同时这种状态的进程无法被杀死(甚至是kill -9都不能杀死这个进程,因为他已经死了)。为了避免这种情况,为了让父进程知道子进程的任务完成的怎么样,我们需要让父进程通过进程等待的方式,回收子进程退出后的资源。
1.父进程通过进程等待的方式回收子进程的资源; 2.父进程要获取子进程的退出信息
**参数:**输出型参数,一般设置为status,用于获取进程的退出状态,如果不关心进程的退出状态可以设置为NULL; **返回值:**wait成功返回等待的子进程pid,等待失败返回-1。
参数:
它的次低8位表示它的退出状态(退出码)((status >> 8) & 0xff); 低7位表示终止信号(&0x7f)。
返回值:如果等待成功,返回所等待的进程pid;如果等待失败,则返回0。
子进程和父进程都有各自对应PCB,当子进程退出且父进程未回收子进程资源时,子进程的退出信息以及子进程的资源信息都保存在子进程的PCB中。waitpid系统调用,以操作系统的身份执行代码,找到子进程,将输出型参数status参数传到子进程内部,把子进程的退出信息设置进status中,再将status取出,父进程就获得了子进程的退出信息。
对于获取进程的退出结果,我们可以不采用对输出型参数进行位操作,因为Linux提供了对应操作的宏。
WIFEXITED(status):若子进程为正常终止,返回的状态,则为真(查看进程是否正常退出) WEXITSTATUS(status):若WIFEXITED为非0,则用它提取子进程的退出码(查看进程的退出码)
进程替换,就是在原进程执行到一定时间是,将原进程替换成磁盘中指定的一个新的进程,然后去执行新的进程(原进程剩余部分不会执行)。
注意:对于execl系列函数的参数要以NULL作为结尾(参数是可以变参数) exec系列函数的返回值,如果替换成功,则没有返回值,而替换失败则返回-1。
文件mytest.c
1 #include<stdio.h>
2 #include<unistd.h>
3 int main()
4 {
5 printf("process is running...\n");
6 execl("/usr/bin/ls","ls","--color=auto","-a","-l",NULL);
7 printf("process running done!");
8 return 0;
9 }
其他函数的用法: l(list):表示参数采用列表; v(vector):表示参数采用数组(将所有执行的参数传入数组中进行统一传递,不用使用可变参数); p(path):自动搜索环境变量(PATH); e(env):表示自己维护环境变量。
文件mytest.c
1 #include<stdio.h>
2 #include<unistd.h>
3 int main()
4 {
5 printf("process is running...\n");
6 execlp("ls","ls","--color=auto","-a","-l",NULL);
7 printf("process running done!");
8 return 0;
9 }
文件mytest.c
1 #include<stdio.h>
2 #include<unistd.h>
3 int main()
4 {
5 printf("process is running...\n");
6 char* const argv[] = {
7 "ls",
8 "--color=auto",
9 "-a",
10 "-l",
11 NULL
12 };
13 execv("/usr/bin/ls",argv);
14 printf("process running done!");
15 return 0;
16 }
文件mytest.c
1 #include<stdio.h>
2 #include<unistd.h>
3 int main()
4 {
5 printf("process is running...\n");
6 char* const argv[] = {
7 "ls",
8 "--color=auto",
9 "-a",
10 "-l",
11 NULL
12 };
13 execvp("ls",argv);
14 printf("process running done!");
15 return 0;
16 }
文件myprocess.c
1 #include<stdio.h>
2 #include<stdlib.h>
3 int main()
4 {
5 printf("PATH:%s\n",getenv("PATH"));
6 printf("PWD:%s\n",getenv("PWD"));
7 printf("MYENV:%s\n",getenv("MYENV"));
8 printf("我是一个进程\n");
9 printf("我是一个进程\n");
10 printf("我是一个进程\n");
11 printf("我是一个进程\n");
12 return 0;
13 }
文件mytest.c
1 #include<stdio.h>
2 #include<unistd.h>
3 int main()
4 {
5 printf("process is running...\n");
6 char* const envi[] = {
7 (char*)"MYENV=123454",
8 NULL
9 };
10 execle("./myprocess","myprocess",NULL,envi);
11 printf("process running done!");
12 return 0;
13 }
只有它是系统调用,其他exec系列的函数都是基于它做的封装,目的是为了有更多的应用场景,有更多的选择。
为什么调用替换函数后,原程序的剩余部分不再执行?
用fork创建子进程后,执行的可能是父进程相同的进程(也可能执行与父进程不同道德代码分支),子进程往往会调用exec系列的函数以执行另一个程序。 当进程调用exec系列函数时,该进程的用户空间的代码和朱家具完全被新程序替换,从新程序的启动例程开始执行。 要注意,调用exec并不创建新进程,所以调用exec前后该进程的id并未改变。
简单来说,程序替换的本质时将指定的程序的代码和数据加载到指定的位置,覆盖原本的代码和数据(即,进程替换时并没有创建新的进程)。 因此,原进程后续的代码会被新进程的代码和数据所覆盖,因此就不会执行剩余代码了。 这也是为什么只有当程序替换失败才会有返回值,因为如果替换成功就不会执行接下来的代码,返回值就无意义。
以上就是今天要讲的内容,本文介绍了进程终止、进程等待、进程替换等的相关概念。本文作者目前也是正在学习Linux相关的知识,如果文章中的内容有错误或者不严谨的部分,欢迎大家在评论区指出,也欢迎大家在评论区提问、交流。 最后,如果本篇文章对你有所启发的话,希望可以多多支持作者,谢谢大家!