很多刚接触 Linux 的童鞋,都会被两个名字诡异的概念搞的晕头转向:僵尸进程 与孤儿进程。
一个像"死"之后没人给下葬,一个像没人管的孩子。听起来挺玄乎,其实背后只是简单的父子关系、退出机制和信号处理的逻辑。
今天跟大家一起来解析一下Linux中的这两个特殊的进程。进程是操作系统基础的调度单位。父子进程,就是在一个进程的基础上创建出另一条完全独立的进程,这个就是子进程,相当于父进程的副本。进程通过 fork() 系统调用创建,形成父子关系。父进程和子进程的运行是异步的,父进程无法预知子进程何时结束。当进程结束时,它所占用的资源(如内存、文件描述符)会被内核回收,但其进程控制块(PCB)中仍保留着退出状态等信息,等待父进程读取。如果父子进程的生命周期配合不当,就会产生两种特殊的进程状态:僵尸进程和孤儿进程。1) 定义:子进程已经终止(通过exit()退出),但其父进程没有及时调用wait()或waitpid()回收子进程的资源,内核仍然保留着该子进程的进程描述符(PID、退出码等),状态为Z(Zombie),此子进程成为僵尸进程。2) 危害:僵尸进程已终止但未被收尸,会占用PID资源,若大量存在,则系统无法再创建新进程。// 僵尸进程示例#include<stdio.h>#include<unistd.h>#include<stdlib.h>intmain(){ pid_t pid = fork(); if (pid == 0) { // 子进程:立即退出 printf("子进程 %d 退出\n", getpid()); exit(0); } else { // 父进程:故意不wait,子进程变僵尸 printf("父进程 %d 睡眠30秒,子进程 %d 将成为僵尸\n", getpid(), pid); sleep(30); // 此时用 `ps -aux | grep zombie` 可看到僵尸进程 } return 0;}
方法一: 父进程及时调用wait()或waitpid()回收资源pid_t pid = fork();if (pid == 0) exit(0); // 子进程退出else wait(NULL); // 父进程回收
方法二: SIGCHLD信号,在信号处理函数中循环waitpidvoidsigchld_handler(int sig){ while (waitpid(-1, NULL, WNOHANG) > 0); // 回收所有僵尸进程(NOHANG表示不阻塞)}signal(SIGCHLD, sigchld_handler)
方法三: 双fork技巧,创建孙子进程执行任务,子进程立即退出,孙进程由init()接管,避免父进程直接管理pid_t pid = fork();if (pid == 0) { if (fork() == 0) execve(...); // 孙子进程执行任务 else exit(0); // 子进程退出}waitpid(pid, NULL, 0); // 父进程回收子进程
1) 定义:当一个子进程还未退出,但其父进程先退出了,则该子进程成为孤儿进程。2) 危害:孤儿进程没有危害,因为孤儿进程会被进程ID为1的init()进程收养,并在子进程结束时调用wait()回收资源。// 孤儿进程示例#include<stdio.h>#include<unistd.h>#include<stdlib.h>intmain(){ pid_t pid = fork(); if (pid == 0) { // 子进程 sleep(2); // 确保父进程先退出 printf("子进程 %d 的父进程现在是 %d\n", getpid(), getppid()); // 输出:父进程现在是 1(被 init 收养) sleep(10); // 继续运行,成为孤儿 } else { // 父进程:立即退出 printf("父进程 %d 退出\n", getpid()); exit(0); } return 0;}
僵尸进程:子进程退出而父进程未及时回收其资源,处理不当容易会耗尽PID。有一定危害。孤儿进程:父进程先退出,子进程由init()进程接管,无危害。