僵尸进程
我们知道linux下进程有种状态叫做僵尸状态;
原因是父进程fork()出来的子进程结束之后,内核会给父进程发送一个SIGCHLD信号,;
这个SIGCHLD信号的作用是及时通知父进程子进程的退出让父进程通过一系列手段将他回收;
回收的意义:
1.避免僵尸进程的产生,内存泄漏
2.且使得父进程能拿到子进程的退出状态,进行相应处理
如果父进程没有忽略这个信号(设置SIG_IGN),也没有等待(wait)子进程,而且结束的也比子进程晚(不能托管被init1号进程领养),并且也没有handler捕捉这个信号,那子进程就会进入僵尸状态; 维护者自己的task_struct开销,这也是一种内存泄露;
解决方式
对于这个问题有已下几种解决方式:
父进程阻塞调用wait()
#include <sys/types.h> /* 提供类型pid_t的定义 */
#include <sys/wait.h>
pid_t wait(int *status)//status 输出型参数,可以拿到子进程退出的状态码,不关心的话设置NULL即可;
进程一旦调用了wait,就立即阻塞自己,由wait自动分析是否当前进程的某个子进程已经退出,如果让它找到了这样一个已经变成僵尸的子进程,wait就会收集这个子进程的信息,并把它彻底销毁后返回;如果没有找到这样一个子进程,wait就会一直阻塞在这里,直到有一个出现为止。
- 显然wait的调用是非常不合理的,阻塞时等待会让父进程干不了别的事情;
父进程非阻塞调用waitpid()
#include <sys/types.h> /* 提供类型pid_t的定义 */
#include <sys/wait.h>
pid_t waitpid(pid_t pid,int *status,int options)//pid设置为-1代表等待任意一个进程, options设置为WNOHANG 即便无子进程退出,它也会立即返回,不会像wait那样永远等下去!
根据源码的观察,不难发现,其实wait就是waitpid的一种封装:
static inline pid_t wait(int * wait_stat) { return waitpid(-1,wait_stat,0);//-1等待任意进程退出,0代表什么都不设置,阻塞式等待! }
- waitpid虽然不会阻塞时等待,但是也需要占用cpu资源,定期的轮训waitpid看看有没有子进程处于僵尸状态,以便将他回收,也是有一定开销的
SIGCHLD信号
其实我们的子进程每次终止时内核都会给父进程发送一个SIGCHLD信号, 如果父进程没有处理这个信号,也没有等待(wait)子进程,那他就会进入僵尸状态;
既然这样,我们可以用signal()函数+waitpid()接口处理一下这个信号,答到一个子进程退出之后,主动通知父进程将其回收的异步通知作用!(显然效率是比父进程主动wait和waitpid这种同步处理高!)
#include <signal.h>
typedef void (*sighandler_t)(int);//简单来说,sighandler_t是一个无返回值,参数为一个int的函数指针
sighandler_t signal(int signum, sighandler_t handler);
- signum参数指明了所要处理的信号类型,它可以取除了SIGKILL和SIGSTOP外的任何一种信号(SIGKILL是kill -9命令,如果把它改了,有可能一个进程我们真的就无法“杀死终止”了)
- handler参数中的int参数就是捕获到的对应信号的int编码,linux用kill -l命令可以查看:
参考代码如下:
#include<stdio.h>
#include<stdlib.h>
#include<signal.h>
#include<unistd.h>
#include<sys/wait.h>
void handler(int sig)
{
pid_t id;
while((id = waitpid(-1, NULL, WNOHANG)) > 0)//while循环处理需要回收的子进程
printf(“wait child success : %d\n”, id);
}
int main()
{
signal(SIGCHLD, handler);//指定SIGCHLD信号来到时,需要被handler函数处理
pid_t ret = fork();
if(ret == 0)
{
printf(“child : %d\n”, getpid());
sleep(3);
exit(0);//子进程退出,并给父进程发送SIGCHLD信号和0这个退出码
}
while(1)
{
printf(“father process is doing some thing!\n”);
sleep(1);
}
return 0;
}
注意,handler中使用while循环处理回收任务的原因是:SIGCHILD是个非可靠信号!
一个父进程可能有很多子进程,如果多个子进程同时退出,SIGCHILD会出现信号丢失(只存在1个)。如果用if处理,也就只处理一个子进程。剩下的子进程也就变成了僵尸进程!
因此我们发现有子进程退出,就用while循环来处理,如果恰好多个子进程退出,即便信号丢失,我们也能while通过waitpid的返回值不断释放他们,即便只有一个子进程退出,第二次循环waitpid会返回0,我们也会跳出循环!
有点像mutex+cond需要while处理虚假唤醒,参考我的这篇文章
SIG_IGN信号
我们都知道,解决僵尸进程还有一个方式,就是让他变为“孤儿进程”,也就是init1号进程领养托管,进而释放这个子进程的资源!
除了让父进程退出的比子进程早之外,我们可以通过如下操作,将SIGCHLD信号的处理方式设置为SIG_IGN–>忽略,这样代表父进程忽视了这个信号,子进程相当于被弃养,会被init1号进程领养
signal(SIGCHLD, SIG_IGN);
这样可让内核把僵尸子进程转交给init1号进程去处理释放,省去了父进程wait这个子进程的麻烦;
这个小技巧常常被用于提升并发服务器的性能!