前言:
进程控制不仅仅是管理程序的执行顺序,还涉及到资源的分配等问题,那么话不多说,开始我们今天的话题!
?进程退出函数✈️exit函数上次我们说到,进程退出时,都会返回一个退出码,用来表示进程退出的状态,而在更前面,我们曾经说过exit函数用来退出进程:
代码语言:javascript代码运行次数:0运行复制<code class="javascript"> 1 #include<stdio.h> 2 #include<stdlib.h> 3 #include<unistd.h> 4 5 int main() 6 { 7 while(1) 8 { 9 printf("I'm a process, pid=%d\n", getpid()); 10 sleep(1); 11 12 exit(3); 13 } 14 return 0; 15 }</code>
进程退出函数exit,函数参数可作为进程退出状态:

eixt:退出进程 status:进程退出状态退出码。
✈️_exit函数
这是man手册里的_exit(),我们通过下面这段代码测试一下他们功能有何异同:
代码语言:javascript代码运行次数:0运行复制<code class="javascript">#include<stdio.h>#include<string.h>#include<errno.h>#include<stdlib.h>#include<unistd.h>void Print(){ printf("hello Print\n"); _exit(4);}int main(){ while(1) { printf("I am a process: %d\n", getpid()); sleep(1); Print(1); //exit(3);放出另外一个_exit,反之则放出exit关闭_exit } return 0;}</code>
这是注释掉了exit(),调用Print函数的_exit()的结果。

这是注释掉Print函数内的_exit,使用exit()函数返回的结果。
从此看来我们并没有发现什么不同之处,返回的错误码也没问题。他们的区别可以通过一下代码分析:
代码语言:javascript代码运行次数:0运行复制<code class="javascript">#include<stdio.h>#include<string.h>#include<errno.h>#include<stdlib.h>#include<unistd.h>int main(){ printf("hello guy\n"); sleep(1); exit(1); return 0;}</code>
exit函数返回的结果符合我们的预期,如果把打印的\n删除:

也没什么问题,但是如果换成_exit函数:

从这里可以看到,当_exit函数遇到像printf打印却没有换行符的时候,就不能正确打印出自己想要的数据。
区别: exit是用来终止进程的,exit(退出码),在我们进程的任何地方调用exit() 都表示进程退出,而_exit与exit的区别就是,exit() 会刷新缓冲区(以后会详谈)。
实际上,_exit()函数是Linux下的一种系统调用,为什么要存在exit() 和 _exit() 两个不同的接口呢?exit() 函数是语言层面的封装,使用这种封装的好处就是语言具有 跨平台、可移植性。
不同系统、平台可能给你提供的系统调用不相同,但是在语言层的封装却都是相同的,所以 C语言具有可移植性、跨平台性。
我们之前说过,如果一个进程变僵尸那么使用kill -9也无能为力,造成僵尸的原因就是子进程退出了,父进程并没有将资源回收所导致的,所以系统提供了一些系统调用,通过父进程等待的方式获取子进程退出信息。
✈️wait接口在Linux中,为了防止进程变僵尸,系统系统了这样一个接口 wait():

wait接口是用来回收子进程资源的一个接口,我们看到wait接口的参数是一个指针,这其实是一个 输出型参数 ,我们现将其设置为 NULL。
接口功能:
我们通过代码来认识一下它的功能:
代码语言:javascript代码运行次数:0运行复制<code class="javascript">#include<stdio.h>#include<stdlib.h> #include<unistd.h>#include<sys/wait.h>#include<sys/types.h>int main(){ pid_t id = fork(); if(id == 0) { //child int cnt = 5; while(cnt) { printf("child is running, pid: %d, ppid: %d\n", getpid(), getppid()); sleep(1); cnt--; } printf("child prepare for exit, will be ZM!\n"); exit(0); } printf("father is sleep...\n"); sleep(10); printf("father start recycle\n"); //father pid_t rid = wait(NULL); if(rid > 0) { printf("wait sucess, rid: %d\n", rid); } printf("father get source sucess!\n"); sleep(3); return 0;}</code>
图中画的红色框框表示僵尸了一段时间,而当父进程开始回收之后,子进程的僵尸状态就没有了。
由此我们可以间接得出,fork之后,父进程一定要最后退出!
Linux也提供了wait方式来获取子进程退出信息的接口 waitpid():

其中waitpid返回值与wait的返回值含义相同,第一个参数的pid有很多种表示方法,但是常见的表示有这两种:
pid参数意义:
status同样是一个输出型参数。options有两种传值,一种是0,一种是非阻塞,我们目前将其设置为0表示阻塞状态。
通过下面代码感受getpid():
代码语言:javascript代码运行次数:0运行复制<code class="javascript">#include<stdio.h>#include<stdlib.h> #include<unistd.h>#include<sys/wait.h>#include<sys/types.h>int main(){ pid_t id = fork(); if(id == 0) { //child int cnt = 5; while(cnt) { printf("child is running, pid: %d, ppid: %d\n", getpid(), getppid()); sleep(1); cnt--; } exit(1); } //father int status = 0; pid_t rid = waitpid(id, &status, 0);//阻塞等待 if(rid > 0) { printf("wait sucess, rid: %d status: %d\n", rid, status);//查看rid和status } return 0;}</code>
我们给status的初始值为0,但是这里却变成了256。这里的status并不是指单纯的整数,与进程的 退出码 和 退出信号 有关,而其存储形式类似于位图的存储:
在这里插入图片描述我们只看比特位的后十六位:

中间有一个名为core dump的比特位我们在信号部分在详谈。
所以我们status为什么是256其实就很简单了:
✈️导出错误码上面说了status的最后16位比特位有效,并且这十六位由退出码和信号编号所组成,所以我们可以使用位运算的方式将退出码和退出信号提取出来:
代码语言:javascript代码运行次数:0运行复制<code class="javascript">#include<stdio.h>#include<stdlib.h> #include<unistd.h>#include<sys/wait.h>#include<sys/types.h>int main(){ pid_t id = fork(); if(id == 0) { //child int cnt = 50; while(cnt) { printf("child is running, pid: %d, ppid: %d\n", getpid(), getppid()); cnt--; sleep(1); } exit(0); } //father int status = 0; pid_t rid = waitpid(id, &status, 0); if(rid > 0) { //status & 0x7F: 0000 0000 0000 0000 0000 0000 0111 1111 printf("wait sucess, rid: %d status: %d exit signo%d exit code: %d\n", rid, status, status&0x7F, (status>>8)&0xFF); } return 0;}</code>
使用了kill -9得出的退出信号就是9,进程被杀死退出码为0。如果在代码中出现了逻辑错误,比如除零错误:

还有类似空指针等情况,有兴趣可以自己尝试,这里就不在试了。
实际上,Linux给我们提供了两个常见的宏定义:
有了这些宏,我们就不用那么麻烦直接使用位操作了,我们可以直接使用对应的宏:
代码语言:javascript代码运行次数:0运行复制<code class="javascript">#include<stdio.h>#include<stdlib.h> #include<unistd.h>#include<sys/wait.h>#include<sys/types.h>int main(){ pid_t id = fork(); if(id == 0) { //child int cnt = 5; while(cnt) { printf("child is running, pid: %d, ppid: %d\n", getpid(), getppid()); cnt--; sleep(1); } exit(123); } //father int status = 0; pid_t rid = waitpid(id, &status, 0); if(rid > 0) { //status & 0x7F: 0000 0000 0000 0000 0000 0000 0111 1111 // printf("wait sucess, rid: %d status: %d exit signo%d exit code: %d\n", rid, status, status&0x7F, (status>>8)&0xFF); if(WIFEXITED(status))//使用宏定义 { printf("wait sucess, rid: %d status: %d exit code: %d\n", rid, status, WEXITSTATUS(status)); } else { printf("child process error!\n"); } } return 0;}</code>
源码中的status:

我们前面代码所采用的等待方式均为阻塞等待,即在回收子进程的资源之前,父进程什么事情可不干,可以用grep 查询:
代码语言:javascript代码运行次数:0运行复制<code class="javascript">ps ajx | grep -i mybin#查询当前进程状态</code>

既然有阻塞,也必然有非阻塞:
表1 阻塞与非阻塞区别
阻塞
非阻塞
方式
当waitpid函数以阻塞模式调用时,父进程等待子进程退出,这期间父进程不做任何事情
当waitpid函数以非阻塞调用时,父进程以轮询的方式每段时间检测子进程是否退出,没退出就返回做父进程的事情
参数
0
WNOHANG
我们以下面这段代码来理解非阻塞:
代码语言:javascript代码运行次数:0运行复制<code class="javascript">#include<stdio.h>#include<stdlib.h> #include<unistd.h>#include<sys/wait.h>#include<sys/types.h>int main(){ pid_t id = fork(); if(id == 0) { //child int cnt = 5; while(cnt) { printf("child is running, pid: %d, ppid: %d\n", getpid(), getppid()); cnt--; sleep(1); } exit(123); } //father int status = 0; while(1) { pid_t rid = waitpid(id, &status, WNOHANG);//非阻塞调用 if(rid > 0) { printf("wait sucess, rid: %d, status: %d, exit code: %d\n", rid, status, WEXITSTATUS(status)); break; } else if(rid == 0) { printf("father say: child is running, do other things\n"); } else { perror("waitpid"); break; } sleep(1); } return 0;}</code>
父进程在等待子进程资源回收的时候自己也在不断地执行,最后也可以成功回收子进程。
为了更直观看到现象,我们不妨模拟进程等待的环境:
代码语言:javascript代码运行次数:0运行复制<code class="javascript">#include<stdio.h>#include<stdlib.h> #include<unistd.h>#include<sys/wait.h>#include<sys/types.h>#define NUM 5typedef void(*fun_t)();fun_t tasks[NUM];void printLog(){ printf("this is a log print task\n");}void printNet(){ printf("this is a net\n");}void printNPC(){ printf("this is a flush NPC\n");}void initTask(){ tasks[0] = printLog; tasks[1] = printNet; tasks[2] = printNPC; tasks[3] = NULL;}void executeTask(){ for(int i = 0; tasks[i]; ++i) tasks[i]();}int main(){ initTask(); pid_t id = fork(); if(id == 0) { //child int cnt = 5; while(cnt) { printf("child is running, pid: %d, ppid: %d\n", getpid(), getppid()); cnt--; sleep(1); } exit(123); } //father int status = 0; while(1) { pid_t rid = waitpid(id, &status, WNOHANG); if(rid > 0) { printf("wait sucess, rid: %d, status: %d, exit code: %d\n", rid, status, WEXITSTATUS(status)); break; } else if(rid == 0) { printf("father say: child is running, do other things\n"); printf("##############task begin############\n"); executeTask(); printf("##############task end##############\n"); } else { perror("waitpid"); break; } sleep(1); } return 0;}</code>区别
wait
waitpid
参数
*status
id, *status, options
状态
等待 任意 子进程退出,并返回终止子进程id
等待 指定 子进程退出,并返回终止子进程id
阻塞
无(默认阻塞调用)
0 和 WNOHANG(用来支持阻塞非阻塞的宏)
根据你所期望进程执行的行为从而选择对应的接口。创作不易,如果对您有帮助的话,还望留下一个小小的赞~~
以上就是Linux-进程控制的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号