Linux 僵尸进程与孤儿进程
- 1. 僵尸进程 (Zombie Process)
- 1.1 什么是僵尸进程
- 1.2 僵尸进程的产生
- 1.3 僵尸进程的危害
- 1.4 如何处理僵尸进程
- 2. 孤儿进程 (Orphan Process)
- 2.1 什么是孤儿进程
- 2.2 孤儿进程的特点
- 2.3 孤儿进程的应用
在 Linux 系统中,进程是资源分配和调度的基本单位。每个进程都有其生命周期,从创建到终止。然而,当一个进程终止时,它并不会立即从系统中完全移除,而是会经历一些特殊的状态,例如僵尸进程和孤儿进程
1. 僵尸进程 (Zombie Process)
1.1 什么是僵尸进程
当一个进程调用 exit()
函数正常或异常终止时,内核会将进程的状态设置为终止状态,并保留进程的一些信息,例如退出码、CPU 使用时间等。此时,进程的绝大部分资源(如内存、文件描述符等)都会被释放,但进程的进程控制块(PCB)仍然保留在系统中,以便父进程可以获取子进程的退出信息。这种处于终止状态但仍然占用少量系统资源的进程被称为僵尸进程。
1.2 僵尸进程的产生
当一个父进程创建了一个子进程后,子进程终止时,会向父进程发送一个 SIGCHLD
信号。父进程可以选择忽略该信号,也可以通过调用 wait()
或 waitpid()
等函数来处理子进程的退出。如果父进程没有及时处理子进程的退出信息,那么子进程就会变成僵尸进程。
1.3 僵尸进程的危害
僵尸进程本身不会占用大量的系统资源,但如果系统中存在大量的僵尸进程,可能会导致以下问题:
- 进程 ID 耗尽: 每个进程都需要一个唯一的进程 ID。如果大量的进程终止后变成僵尸进程,而父进程又没有及时回收它们,那么系统中的可用进程 ID 可能会被耗尽,导致无法创建新的进程。
- 系统资源浪费: 僵尸进程虽然占用的资源不多,但毕竟还是占用着一些系统资源,例如进程表项。如果僵尸进程过多,可能会导致系统资源浪费。
1.4 如何处理僵尸进程
处理僵尸进程的方法主要有两种:
- 等待子进程结束: 父进程应该及时调用
wait()
或waitpid()
等函数来等待子进程结束,并获取子进程的退出信息。这样可以避免子进程变成僵尸进程。
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>int main() {pid_t pid;int status;pid = fork();if (pid == 0) {// 子进程printf("Child process is running...\n");exit(0);} else if (pid > 0) {// 父进程printf("Parent process is waiting for child process...\n");wait(&status); // 等待子进程结束printf("Child process has finished.\n");} else {// 创建子进程失败perror("fork failed");return 1;}// 判断子进程退出状态if (WIFEXITED(status)) {// 子进程正常退出printf("Child process exited normally with status %d\n", WEXITSTATUS(status));} else if (WIFSIGNALED(status)) {// 子进程被信号终止printf("Child process terminated by signal %d\n", WTERMSIG(status));}return 0;
}
在上面的代码中,我们使用了 WIFEXITED()
和 WIFSIGNALED()
两个宏来判断子进程的退出状态。
-
WIFEXITED(status)
:如果子进程是正常退出(通过调用exit()
或return
),则返回 true。此时,可以通过WEXITSTATUS(status)
宏来获取子进程的退出码。 -
WIFSIGNALED(status)
:如果子进程是被信号终止的,则返回 true。此时,可以通过WTERMSIG(status)
宏来获取导致子进程终止的信号编号。 -
杀死父进程: 如果父进程不再需要子进程的信息,可以直接杀死父进程。这样,子进程就会被 init 进程接管,并由 init 进程负责回收。
此外,还可以通过以下方法避免僵尸进程:
- 忽略 SIGCHLD 信号: 父进程可以通过
signal(SIGCHLD, SIG_IGN)
通知内核,表示自己对子进程的退出不感兴趣。这样,子进程退出后会立即释放数据结构,不会变成僵尸进程。
#include <signal.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>int main() {pid_t pid;pid = fork();if (pid == 0) {// 子进程printf("Child process is running...\n");exit(0);} else if (pid > 0) {// 父进程printf("Parent process is running...\n");signal(SIGCHLD, SIG_IGN); // 忽略 SIGCHLD 信号while (1) {// 父进程执行其他任务sleep(1);}} else {// 创建子进程失败perror("fork failed");return 1;}return 0;
}
- 使用
wait3()
或wait4()
函数: 这两个函数提供了更多的选项来处理子进程的退出状态,例如可以获取子进程的资源使用情况。
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/resource.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>int main() {pid_t pid;int status;struct rusage usage;pid = fork();if (pid == 0) {// 子进程printf("Child process is running...\n");exit(0);} else if (pid > 0) {// 父进程printf("Parent process is waiting for child process...\n");wait4(pid, &status, 0, &usage); // 等待子进程结束,并获取资源使用情况printf("Child process has finished.\n");} else {// 创建子进程失败perror("fork failed");return 1;}return 0;
}
2. 孤儿进程 (Orphan Process)
2.1 什么是孤儿进程
当一个父进程在子进程之前终止时,子进程就会变成孤儿进程。此时,init 进程(进程 ID 为 1)会自动接管孤儿进程,成为它们的父进程。
2.2 孤儿进程的特点
- 父进程: 孤儿进程的父进程是 init 进程。
- 生命周期: 孤儿进程的生命周期与 init 进程相同。当 init 进程终止时,所有孤儿进程也会被终止。
- 资源回收: init 进程会负责回收孤儿进程的资源,因此孤儿进程不会像僵尸进程那样占用系统资源。
2.3 孤儿进程的应用
孤儿进程在某些场景下非常有用。例如,守护进程通常会创建子进程来执行一些后台任务,而守护进程本身则会继续运行。在这种情况下,子进程就可能成为孤儿进程。
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>int main() {pid_t pid;pid = fork();if (pid == 0) {// 子进程printf("Child process is running...\n");while (1) {// 子进程执行一些任务sleep(1);}} else if (pid > 0) {// 父进程printf("Parent process is exiting...\n");exit(0); // 父进程退出} else {// 创建子进程失败perror("fork failed");return 1;}return 0;
}