孤儿进程 僵尸进程 解决办法

    科技2026-01-17  12

    定义

    孤儿进程:一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作。 僵尸进程:一个进程使用fork创建子进程,如果子进程退出,而父进程并没有调用wait或waitpid获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中。这种进程称之为僵死进程。

    危害

    unix提供了一种机制可以保证只要父进程想知道子进程结束时的状态信息, 就可以得到。这种机制就是: 在每个进程退出的时候,内核释放该进程所有的资源,包括打开的文件,占用的内存等。 但是仍然为其保留一定的信息(包括进程号the process ID,退出状态the termination status of the process,运行时间the amount of CPU time taken by the process等)。直到父进程通过wait / waitpid来取时才释放。 但这样就导致了问题,如果进程不调用wait / waitpid的话, 那么保留的那段信息就不会释放,其进程号就会一直被占用,但是系统所能使用的进程号是有限的,如果大量的产生僵死进程,将因为没有可用的进程号而导致系统不能产生新的进程. 此即为僵尸进程的危害,应当避免。 孤儿进程是没有父进程的进程,孤儿进程这个重任就落到了init进程身上,init进程就好像是一个民政局,专门负责处理孤儿进程的善后工作。每当出现一个孤儿进程的时候,内核就把孤 儿进程的父进程设置为init,而init进程会循环地wait()它的已经退出的子进程。这样,当一个孤儿进程凄凉地结束了其生命周期的时候,init进程就会代表党和政府出面处理它的一切善后工作。因此孤儿进程并不会有什么危害。

    测试

    孤儿进程

    #include <stdio.h> #include <stdlib.h> #include <errno.h> #include <unistd.h> int main() { pid_t pid; //创建一个进程 pid = fork(); //创建失败 if (pid < 0) { perror("fork error:"); exit(1); } //子进程 if (pid == 0) { printf("I am the child process.\n"); //输出进程ID和父进程ID printf("pid: %d\tppid:%d\n",getpid(),getppid()); printf("I will sleep five seconds.\n"); //睡眠5s,保证父进程先退出 sleep(5); printf("pid: %d\tppid:%d\n",getpid(),getppid()); printf("child process is exited.\n"); } //父进程 else { printf("I am father process.\n");就在同一个窗口中打开了另一个终端,当然再按一次ctrl+shift+t,会再生成一个,需要多少了大家可以自行决定。效果截图 //父进程睡眠1s,保证子进程输出进程id sleep(1); printf("father process is exited.\n"); } return 0; }

    输出

    (base) ➜ THREAD ./Orphan I'm father process. I'm the child process. pid: 4198 ppid: 4197 I'll sleep 5s. father process is exited. (base) ➜ THREAD pid: 4198 ppid: 895就在同一个窗口中打开了另一个终端,当然再按一次ctrl+shift+t,会再生成一个,需要多少了大家可以自行决定。效果截图 child process is exited.

    top进程895

    进程号 USER PR NI VIRT RES SHR %CPU %MEM TIME+ COMMAND 895 toto 20 0 19376 10692 8172 S 0.0 0.1 0:00.44 systemd

    systemd 为守护进程

    僵尸进程

    #include <stdio.h> #include <unistd.h> #include <errno.h> #include <stdlib.h> int main() { pid_t pid; pid = fork(); if (pid < 0) { perror("fork error:"); exit(1); } else if (pid == 0) { printf("I am child process.I am exiting.\n"); exit(0); } printf("I am father process.I will sleep two seconds\n"); //等待子进程先退出 sleep(2); //输出进程信息 system("ps -o pid,ppid,state,tty,command"); printf("father process is exiting.\n"); return 0; }

    输出

    (base) ➜ THREAD ./ZombieProcess0 I am father process.I will sleep two seconds I am child process.I am exiting. PID PPID S TT COMMAND 3523 3389 S pts/1 zsh 4845 3523 S pts/1 ./ZombieProcess0 4846 4845 Z pts/1 [ZombieProcess0] <defunct> 4847 4845 S pts/1 sh -c ps -o pid,ppid,state,tty,command 4848 4847 R pts/1 ps -o pid,ppid,state,tty,command father process is exiting.

    可以看到4846的State为Z,僵尸进程。

    tty是终端设备的统称,它是一种字符型设备,有多种类型。所以通常使用tty来简称各种类型的终端设备。 pts介绍: pts指的是所谓的伪终端或者是虚拟终端,表现在当你打开一个终端时,这个终端的名称就为pts/0,但是当你再次打开一个终端时,这个 新的终端就叫pts 在Linux系统下,将设备名放在特殊文件目录/dev下,终端特殊设备文件一般有以下几种 (1)串行端口终端(/dev/ttySn) 它是使用计算机端口连接的终端设备,并将每个串行口都看作是一个字符设备。如果要向一个端口发送数据,可以在命令行上把标准输出重定向到这些特殊文件名上即可 (2)伪终端(/dev/pty/) 是成对的逻辑终端设备,例如/dev/ptyp3和/dev/ttyp3,它们与实际物理设备并不直接相关。 (3)控制终端(/dev/tty) 如果当前进程有控制终端的话,那么/dev/tty就是当前进程的控制终端的设备特殊文件。可以使用命令”ps –ax”来查看进程与哪个控制终端相连。假如你登录的是shell,那么/dev/tty就是你使用的终端,使用命令”tty”可以查看它 具体对应哪个实际终端设备。 (4)控制台终端(/dev/ttyn, /dev/console) 在 UNIX系统中,计算机显示器通常被称为控制台终端。它仿真了类型为Linux的一种终端,并且有一些设备特 殊文件与之相关联,你也可以登录到不同的虚拟终端上去,因而可以让系统同时有几个不同的会话期存在。只有系统或超级用户root可以向/dev/tty0进行写操作。

    僵尸进程解决办法

    通过信号机制

    子进程退出时向父进程发送SIGCHILD信号,父进程处理SIGCHILD信号。在信号处理函数中调用wait进行处理僵尸进程。测试程序如下所示:

    SIGCHILD信号,子进程先于父进程退出的话,操作系统通过此信号通知父进程,这个信号的默认处理方式是忽略处理。这就是为什么子进程退出,父进程没有任何反应的原因。

    #include <stdio.h> #include <unistd.h> #include <errno.h> #include <stdlib.h> #include <signal.h> #include <sys/wait.h> static void sig_child(int signo); int main() { pid_t pid; //创建捕捉子进程退出信号 signal(SIGCHLD,sig_child); pid = fork(); if (pid < 0) { perror("fork error:"); exit(1); } else if (pid == 0) { printf("I am child process,pid id %d.I am exiting.\n",getpid()); exit(0); } printf("I am father process.I will sleep two seconds\n"); //等待子进程先退出 sleep(2); //输出进程信息 system("ps -o pid,ppid,state,tty,command"); printf("father process is exiting.\n"); return 0; } static void sig_child(int signo) { pid_t pid; int stat; //处理僵尸进程 while ((pid = waitpid(-1, &stat, WNOHANG)) >0) printf("child %d terminated.\n", pid); }

    结果:

    (base) ➜ THREAD ./SignalZombie I'm father,I'll sleep 2s. I'm child process,pid id = 5515.I'm exiting. child 5515 terminated. PID PPID S TT COMMAND 3523 3389 S pts/1 zsh 5514 3523 S pts/1 ./SignalZombie 5516 5514 S pts/1 sh -c ps -o pid,ppid,state,tty,command 5517 5516 R pts/1 ps -o pid,ppid,state,tty,command father process is exiting.

    fork()两次

    #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <errno.h> int main() { pid_t pid; //创建第一个子进程 pid = fork(); if (pid < 0) { perror("fork error:"); exit(1); } //第一个子进程 else if (pid == 0) { //子进程再创建子进程 printf("I am the first child process.pid:%d\tppid:%d\n",getpid(),getppid()); pid = fork(); if (pid < 0) { perror("fork error:"); exit(1); } //第一个子进程退出 else if (pid >0) { printf("first procee is exited.\n"); exit(0); } //第二个子进程 //睡眠3s保证第一个子进程退出,这样第二个子进程的父亲就是init进程里 sleep(3); printf("I am the second child process.pid: %d\tppid:%d\n",getpid(),getppid()); exit(0); } //父进程处理第一个子进程退出 if (waitpid(pid, NULL, 0) != pid) { perror("waitepid error:"); exit(1); } exit(0); return 0; }
    Processed: 0.021, SQL: 9