Linux中的僵尸进程和孤儿进程
孤儿进程
孤儿进程指的一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被init
进程(进程号为1)所收养,并由init进程对它们完成状态收集工作。
子进程死亡需要父进程来处理, 那么意味着正常的进程应该是子进程先于父进程死亡。当父进程先于子进程死亡时, 子进程死亡时没父进程处理, 这个死亡的子进程就是孤儿进程。
但孤儿进程与僵尸进程不同的是,由于父进程已经死亡,系统会帮助父进程回收处理孤儿进程。所以孤儿进程实际上是不占用资源的,因为它终究是被系统回收了。不会像僵尸进程那样占用ID, 损害运行系统。
僵尸进程
僵尸进程即子进程先于父进程退出后,子进程的PCB需要其父进程释放,但是父进程并没有释放子进程的PCB,这样的子进程就称为僵尸进程,僵尸进程实际上是一个已经死掉的进程.
下面有实例:
#include
#include
#include
#include
#include
int main() {
pid_t pid = fork();
if (pid == 0) {
// child
printf("child pid: %d, parent pid:%d,\n", getpid(), getppid());
} else {
//parent
while (1) {
sleep(2);
printf("I am parent!!!\n");
}
}
exit(0);
}
在执行以后,我们可以在终端中使用ps -aux | grep pid
打印进程, 发现:
brownfe+ 8241 0.0 0.0 0 0 ? Z 15:35 0:00 [zombie]
可以看到结果, 子进程后有一个
, 表示僵尸进程. 因为子进程已经结束, 而其父进程并未释放其PCB,所以产生了这个僵尸进程.
一个进程在调用exit
命令结束自己的生命的时候, 其实它并没有真正的被销毁, 而是留下一个称为僵尸进程Zombie
的数据结构。
在Linux进程的状态中, 僵尸进程是非常特殊的一种,它已经放弃了几乎所有内存空间,没有任何可执行代码,也不能被调度,仅仅在进程列表中保留一个位置,记载该进程的退出状态等信息供其他进程收集,除此之外,僵尸进程不再占有任何内存空间,
这个僵尸进程需要它的父进程来为它收尸,如果他的父进程没有处理这个僵尸进程的措施,那么它就一直保持僵尸状态,如果这时父进程结束了,那么init
进程自动会接手这个子进程,为它收尸,它还是能被清除的。但是如果如果父进程是一个循环,不会结束,那么子进程就会一直保持僵尸状态,这就是为什么系统中有时会有很多的僵尸进程。
系统调用
exit
的作用是进程退出, 也就是将一个正常的进程变成一个僵尸进程, 它并不能完全释放这个进程资源.
僵尸进程的处理方法
从前面我们知道, Linux任何一个进程(init
除外)在退出(exit()
或者return
之后, 不会立刻释放自己的pcb
, 而是变成僵尸进程(Zombie
), 等待父进程处理.
如果父进程在子进程结束之前就退出了,那么子进程就会由init
进程处理, 成为孤儿进程, 孤儿进程不会占用系统资源.
为了避免僵尸进程
占用系统资源,常见有方法:
- 暴力杀死
僵尸进程
的父进程. 这样僵尸进程
变成孤儿进程
被init
回收. -
SIGCHLD
信号+wait
函数
wait
函数和SIGCHLD
信号
如果进程调用wait
函数, 调用进程会阻塞自己, 由wait
函数会分析当前进程的某个子进程已经退出, 如果有,wait
函数会收集这个子进程的信息,并将其销毁以后返回.
#include
#include
pid_t wait(int *status)
waitpid...
其中status
用来保存已经退出子进程的返回状态(exit(1)的返回内容
). 该方法如果成功, 就返回被收集的子进程的进程ID, 如果调用进程没有子进程, 调用就会失败, 此时wait返回-1, 同时errno
被置为ECHILD
。
另外一个关键点在于,子进程终止时, 内核就会向它的父进程发送一个SIGCHLD
信号, 默认情况下进程会忽略这个信号. 我们可以修改信号处理逻辑, 专门处理这个信号, 在收到该信号时, 主动调用wait
或者waitpit
回收子进程的资源.
#include
#include
#include
#include
#include
#include
#include
void deal_child(int num) {
printf("deal_child into\n");
wait(NULL);
}
int main() {
signal(SIGCHLD, deal_child);
pid_t pid = fork();
if (pid == 0) {
printf("I am child, pid=%d, ppid=%d\n", getpid(), getppid());
printf("child is running...\n");
sleep(2);
printf("child will end...\n");
} else if (pid > 0) {
sleep(1); //让子进程先执行
printf("parent is running\n");
sleep(10); //一旦被打断就不能再进入睡眠
printf("sleep 10 s over\n");
sleep(5);
printf("sleep 5s over\n");
}
exit(0);
}