如何处理僵死进程?
给进程设置僵尸状态的目的是维护子进程的信息,以便父进程在以后某个时间获取,这些信息包括进程的 pid、终止状态以及资源的利用信息(CPU时间,内存使用量等)。如果一个进程终止,而该进程有子进程处于僵尸状态,那么它的所有僵尸子进程的 ppid 将被重置为1(init 进程)。继承这些子进程的 init 进程将清理它们(init 进程将 wait 它们,从而去除僵死状态)。
但在通常情况下,我们是不愿意存留僵死进程的,它们占用内存空间,最终可能导致我们耗尽进程资源,那么将如何避免产生僵死进程呢?
(1)父进程调用 pid_t wait(int *status)函数来获取子进程的退出状态
函数功能:父进程一旦调用了 wait 就立即阻塞自己,由 wait 自动分析是否当前的某个子进程已经退出,如果让它找到了一个已经变成僵死进程的子进程,wait 就会收集这个子进程的信息,并把它彻底摧毁后返回;如果没有找到这样一个子进程,wait 就会一直阻塞在这里,直到有一个出现为止。
函数返回值:当wait() 与 fork() 配套出现时,如果在使用fork()之前调用了 wait(),wait() 的返回值则为-1,正常情况下返回子进程的 pid。
参数 status 用来保存被收集退出时的一些状态,它是指向 int 类型的指针。如果我们对子进程是怎样死掉的毫不在意,只想把这个僵死进程消灭掉,
我们就可以设定这个参数为 NULL,即 pid = wait(NULL);调用成功,就会返回被收集的在子进程的 pid;如果调用的进程没有子进程,调用失败,返回-1。
通过以上了解,我们可以了解到:辛辛苦苦创建了一个子进程,最终子进程和父进程成了串行运行,实际上并没有达到提高效率的目的。
(2)异步处理僵死进程 ----> 我们提出了信号的概念
信号的概念:系统先定义好的某些特定的事件,可以被发生,也可以被接收,发生和接收的主体都是进程。
系统中信号的定义:/usr/include/bits/signal.h
信号的来源:按照产生条件的不同可以分为软件和硬件两种
a. 硬件方式:当用户在终端上按下某键时,将产生信号;如果按下组合键((Ctrl + c 代表1)SIGINT 中断信号;(Ctrl + \代表是 3)SIGQUIT 退出信号;(Ctrl + z 代表19)SIGSTOP 信号)后将产生一个信号
b. 软件方式:用户在终端下调用 kill 命令向进程发送任务信息
信号的种类:在 shell 下输入 kill -l 可显示linux系统支持的全部信号;信号的值定义在 signal.h 中。每个信号都由一个编号和宏定义名称
信号的接受:在接受信号的进程 PCB 结构中有 long signal,通过 signal 来表示信号
进程对信号的响应:当进程发生时,用户可以要求进程以以下三种方式之一对信号做出响应:
a. 默认信号:按系统默认方式处理,大部分信号的默认操作是终止操作,且所有的实时信号的默认动作都是终止进程
b. 忽略信号:大多数信号都可以使用这种方式进行处理,但是SIGKILL和SIGSTOP这两个信号不能忽略,同时这两种信号也不能捕获和阻塞。此外,如果会忽略某些由硬件异常产生的信号(如非法存储访问或除以0),则进程的行为是不可预测的。
c. 捕捉信号:对于捕捉的信号,可以为其指定信号处理函数,信号发生时该函数自动被调用,在该函数内部实现对信号的处理。
修改信号的响应方式:修改 PCB 结构中,struct sigaction 结构体数组中对应信号值作为下标的函数指针
函数原型:void (*signal(int signum, void (*fun))(int))(int);//signum 指定信号的值,int 指函数指针对前面信号值的处理;
帮助理解:typedef void (*fun_handle)(int);
fun_handle signal(int signum, fun_handler fun);
返回值:调用成功返回最后一次安装信号 signum 而调用 signal() 时的 fun 值,失败则返回 SIG_ERR。
修改信号的响应方式的时机:进程刚开始就执行修改关注的信号的响应方式。
代码中 signal 在何时调用:一般情况下在 main 函数开始第一行调用。
结合信号来处理僵死进程,父进程不阻塞并且处理僵死进程,那子进程结束时,只需要向其父进程发送一个 SIGCHID 信号。
优势:a. 父进程和子进程可以并行处理;
b. 可以处理所有的僵死进程。
示例如下:
#include <stdio.h>
#include <unistd.h>
#include <assert.h>
#include <signal.h>
void fun(int sign)
{
printf("fun was called\n");
pid_t pid = wait(NULL);
printf("pid == %d\n", pid);
printf("fun over\n");
}
void main()
{
pid_t pid = fork();
assert(pid != -1);
if (pid == 0)
{
printf("child start\n");
sleep(2);
printf("child end\n");
kill(getppid(), SIGINT);//子进程结束时会向其父进程发送SIGCHLD
}
else
{
signal(SIGCHLD, fun);
printf("father start\n");
sleep(10000);
printf("father end\n");
}
}
运行结果如下:
还没完呢。。。。。。。