Linux下的进程等待
大家都知道父进程退出的时候,如果没关注到子进程,子进程就会变成僵尸进程,占用资源却不做事(吃白饭),这肯定不能被允许,那么怎么解决呢?
这里给大家提供两种方法。
-
退出父进程。
父进程一旦退出,子进程保留的资源也就自然释放。但是显然这种方法不常用,因为我们创建子进程的一部分原因就是为了分身探路,保证父进程的安全。下面主要介绍第二种方法。 -
进程等待
父进程一直关注着子进程,等到接到子进程的消息后,子进程就算是结束任务了,资源也就释放了。
在命令行输入:
man 2 wait
就会有下面两个函数接口:
这就是两个进程等待的函数,它们都放在unistd.h中。
pid_t是Linux系统自定义的进程类型号,其实是宏定义的unsigned int 类型。
先来研究第一个函数。
- wait
wait函数可以传入一个int * 型参数用来接收子进程的返回值,这就是status。
wait函数会使父进程阻塞,等待任意一个子进程结束,释放子进程的资源,再开始运行。返回值是子进程的进程号(pid)。
Example:
#include<stdio.h>//printf函数
#include<stdlib.h>//exit函数
#include<unistd.h>//wait函数
int main(void)
{
pid_t pid = fork();//创建一个子进程
if(pid == 0){
printf("这是一个child process .\n");
sleep(5);
exit(0); //子进程睡5秒然后退出,
}
//阻塞等待子进程运行完毕。
wait(NULL);//填入NULL表示我不想存储返回值。
printf("子进程运行完毕。");
sleep(5);//父进程再睡5秒,便于我截图。
return 0;
}
运行后就会发现,屏幕上啥都没有,5秒后才打印那句“子进程运行完毕。”,查看进程状态时就会发现,刚开始时,
父子进程都是在前台sleep,5秒后,子进程结束,没有出现僵尸进程。
- waitpid
waitpid函数中有3个参数,第一个参数pid为-1表示任意一个子进程退出;pid为某个子进程的进程号时,则等到该子进程退出。
第二个参数跟wait函数一样,用来存储子进程的返回值。
第三个参数为0时表示阻塞等待,为 WNOHANG时表示不阻塞。
waitpid的返回值为0表示子进程未退出,为-1表示出错;如果子进程运行完毕,则返回子进程号。
看了这一段冗长的汉字,大家可能有点晕,来几个简单的例子阐述下,
//例子1
waitpid(-1,NULL,0);//等价于wait(NULL) 等待任意子进程退出,且阻塞等待。
//例子2
int val = 0;
waitpid(-1,&val,0);//表示等待任意子进程退出,将子进程的返回值存储到val中,阻塞等待。
//例子3
pid_t pid = fork();
waitpid(pid,&val,0);//表示等待进程号为pid的子进程退出,将该子进程的返回值放在val中,阻塞等待。
但是大家可能对一个词可能不太理解,就是阻塞,什么叫做阻塞呢?
术语一点就是为了完成某个功能的一个调用,缺少某种条件,一直等待。其实就是不动了,(但还能动)。
接着而来的一个思考就是,我为什么要设置不阻塞这个状态?
**让父进程等待子进程进行完毕不久好了吗?**NO,NO,NO!!!
- Example2:
小明在河边钓鱼,突然他想拿出塞在衣服下的《Linux操作系统》来看,怎么办?小明难道能一直盯着鱼钩吗?作为一个时间管理大师,这显然不行的。
所以不阻塞就是为了增加时间利用率,子进程未退出时,父进程干自己的事,直到子进程退出,再回来接收信息,释放资源。我们用循环来实现。
#include<stdio.h>//printf函数
#include<stdlib.h>//exit函数
#include<unistd.h>//wait函数
int main(void)
{
pid_t pid = fork();//创建一个子进程
if(pid == 0){
sleep(5);
exit(0); //子进程睡5秒然后退出,
}
pid_t val;
//如果子进程没完毕,则进入循环。这也是不阻塞时对父进程的利用。
while((val=waitpid(pid,NULL,WNOHANG))==0){
printf("趁子进程没完,打会LOL\n");
sleep(1);//防止打印太快,
}
printf("Oh no,子进程完了\n");
//printf中一定要加\n,不然缓冲区不会被刷新,就会最后一块打印。
return 0;
}
结果如下:
刚开始时,子进程一直睡觉,父进程不管它,一直干自己的工作(循环打LOL),等到子进程结束,再回头去释放资源。
-
接下来,我们看看子进程的返回值。
上面我们说了子进程的返回值放在指针status指向的空间中。 -
Example3
请看代码
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
int main(void)
{
pid_t pid=fork();
//子进程的分流
if(pid==0){
sleep(5);
return 255;
}
int status=0;//用来接收子进程的返回值
pid_t val=0;//接收子进程的进程号
//这也是不堵塞进程的常用做法,循环等待子进程end,
while((val=waitpid(pid,&status,WNOHANG))==0){
printf("请等待子进程结束\n");
sleep(1);
}
printf("子进程的返回值是: %d ",status);
//输出子进程的返回值
printf("子进程的返回值是:%d\n",status);
printf("我是父进程 .\n");
printf("我在打LOL .");
return 0;
结果如下:
我们发现,返回值竟然不是255。
当我们把子进程返回值改为256时,发现返回值变成0。
那么这是为什么呢?
实际上,进程的返回值存储的时候只用一个字节,所以256就是在二进制时进了一位,所以全是0。
255转换成16进制是0xff,而65280转换成16进制是0xff00,系统自动进了8个字节,所以得出以下规律:
status一共32位二进制位,而存储一个返回值只用9~16位。
那么我们如何打印出返回值呢?
我们再倒回去8位即可
这样处理
status = (status>>8);
但是,按位移动怎么能保证8位以后的位全是0呢?
按位与上8位以后全是0,之前全是1的数,即0xff。
status = (status>>8) & 0xff ;
最后的代码:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
int main(void)
{
pid_t pid=fork();
//子进程的分流
if(pid==0){
sleep(5);
return 255;
}
int status=0;//用来接收子进程的返回值
pid_t val=0;//接收子进程的进程号
//这也是不堵塞进程的常用做法,循环等待子进程end,
while((val=waitpid(pid,&status,WNOHANG))==0){
printf("请等待子进程结束\n");
sleep(1);
}
status = (status>>8) && 0xff;
printf("子进程的返回值是: %d ",status);
//输出子进程的返回值
printf("子进程的返回值是:%d\n",status);
printf("我是父进程 .\n");
printf("我在打LOL .");
return 0;
结果与我们预想的一样: