Linux ゾンビ プロセスは、かなり前に停止したプロセスですが、まだプロセス テーブル内の位置を占めています。子プロセスが停止したときに親プロセスに wait() がない場合、通常はそれが表示されるのを確認できます。 "
" としてゾンビ プロセスを生成します。大量のゾンビ プロセスが生成されると、使用可能なプロセス番号がないため、システムは新しいプロセスを生成できなくなります。そのため、ゾンビ プロセスは回避する必要があります。
このチュートリアルの動作環境: linux5.9.8 システム、Dell G3 コンピューター。
Linux におけるゾンビ プロセス (無効なプロセス) の生成と回避
###1. ゾンビ プロセスとはUNIX システムでは、プロセスは終了しますが、その親プロセスは終了します。プロセスが彼を待機しない (wait/waitpid を呼び出す) と、彼はゾンビ プロセスになります。 ps コマンドを使用してプロセスの実行ステータスを観察すると、これらのプロセスのステータス バーが表示されないことがわかります。ゾンビ プロセスは、ずっと前に停止しましたが、プロセス テーブル内のスロットをまだ占有しているプロセスです。 ただし、プロセスの親プロセスが先に終了していれば、そのプロセスはゾンビプロセスにはなりません。各プロセスが終了すると、システムは現在のシステムで実行中のすべてのプロセスをスキャンして、終了したプロセスの子プロセスであるかどうかを確認し、子プロセスである場合は、Init プロセスがそれを引き継ぎ、親プロセスになります。したがって、各プロセスが親プロセスを持つことが保証されます。 Init プロセスは子プロセスを自動的に待機するため、Init によって引き継がれたすべてのプロセスがゾンビ プロセスになることはありません。 2. UNIX でのプロセスの動作方法各 Unix プロセスにはプロセス テーブルのエントリ ポイント (エントリ) があり、コア プロセスプロセスの実行時に使用され、受信されるすべての情報エントリポイントに保存されます。 ps コマンドを使用してシステム内のプロセス情報を表示すると、プロセス テーブル内の関連データが表示されます。 fork() システム コールで新しいプロセスが作成されると、コア プロセスはプロセス テーブル内の新しいプロセスにエントリ ポイントを割り当て、エントリ ポイントに対応するプロセス テーブルに関連情報を格納します。これらの情報の 1 つは、親プロセスの識別番号です。
子プロセスの終了と親プロセスの実行は非同期プロセスです。つまり、親プロセスは子プロセスがいつ終了するかを予測できません。それでは、親プロセスがビジーすぎて子プロセスを待つことができないため、または子プロセスがいつ終了するかわからないため、子プロセスが終了したときのステータス情報は失われるのでしょうか? それはできません。 UNIX には、子プロセスの終了時に親プロセスがステータス情報を知りたい限り、それを取得できるメカニズムが用意されているためです。このメカニズムは次のとおりです。子プロセスがライフサイクルを完了すると、exit() システムコールが実行され、カーネルは開いているファイル、占有メモリなどを含むプロセスのすべてのリソースを解放します。ただし、特定の情報 (プロセス ID、終了コード、プロセスの終了ステータス、プロセスにかかった CPU 時間など) は引き続き保持され、これらのデータはシステムがパスするまで保持されます。それを親プロセスに送信し、親プロセスが wait/waitpid 経由でフェッチするまで解放しません。 言い換えれば、プロセスが終了しても、プロセスは完全に消滅するわけではありません。プロセスは終了し、実行されなくなりますが、親プロセスが再利用するのを待っている残りのデータがまだ残っています。親プロセスが子プロセスを forks() する場合、wait() (または waitpid()) を使用して子プロセスが終了するのを待つ必要があります。子プロセスの残存データを消去するのは、この wait() アクションです。 3. ゾンビプロセスの害親プロセスがwait/waitpidを呼ばないと、保持している情報は解放されず、プロセス番号は常に占有されますが、システムはプロセステーブルの容量には限りがあり、使用できるプロセス番号も限られているため、ゾンビプロセスが大量に生成されると、使用可能なプロセス番号がなくなり、新たなプロセスを生成できなくなります。 したがって、無効なプロセスはシステムのメモリ リソースを占有し、システムのパフォーマンスに影響を与えるだけでなく、その数が多すぎるとシステムの麻痺を引き起こすこともあります。また、スケジューラは Defunct プロセスを選択できないため、kill コマンドで Defunct プロセスを削除することはできず、システムを再起動するしか方法がありません。 4. ゾンビプロセスの生成子プロセスが終了したときに親プロセスに wait() がない場合、通常は ps を使用して「#include <stdio.h> #include<sys/types.h> main() { if(!fork()) { printf(“child pid=%d\n”, getpid()); exit(0); } sleep(20); printf(“parent pid=%d \n”, getpid()); exit(0); }<div class="highlight"> <p data-pid="Y1zLLGu8">当上述程序以后台的方式执行时,第17行强迫程序睡眠20秒,让用户有时间输入ps -e指令,观察进程的状态,我们看到进程表中出现了defunct进程。当父进程执行终止后,再用ps -e命令观察时,我们会发现defunct进程也随之消失。这是因为父进程终止后,init 进程会接管父进程留下的这些“<strong>孤儿进程</strong>”(orphan process),而这些“孤儿进程”执行完后,它在进程表中的进入点将被删除。如果一个程序设计上有缺陷,就可能导致某个进程的父进程一直处于睡眠状态或是陷入死循环,父进程没有wait子进程,也没有终止以使Init接管,该子进程执行结束后就变成了defunct进程,这个defunct 进程可能会一直留在系统中直到系统重新启动。</p> <p data-pid="e4bzxqVS">在看一个产生僵尸进程的例子。</p> <p data-pid="oyygQagx">子进程要执行的程序test_prog</p> <div class="highlight"><pre class="brush:php;toolbar:false">//test.c #include <stdio.h> int main() { int i = 0; for (i = 0 ; i < 10; i++) { printf ("child time %d\n", i+1); sleep (1); } return 0; }
父进程father的代码father.c
#include <stdio.h> #include <unistd.h> #include <sys/types.h> #include <sys/wait.h> int main() { int pid = fork (); if (pid == 0) { system ("./test_prog"); _exit (0); }else { int i = 0; /* int status = 0; while (!waitpid(pid, &status, WNOHANG)) { printf ("father waiting%d\n", ++i); sleep (1); }*/ while (1) { printf ("father waiting over%d\n", ++i); sleep (1); } return 0; } }
执行./father,当子进程退出后,由于父进程没有对它的退出进行关注,会出现僵尸进程
20786 pts/0 00:00:00 father 20787 pts/0 00:00:00 father <defunct>
总结:子进程成为 defunct 直到父进程 wait(),除非父进程忽略了 SIGCLD 。更进一步,父进程没有 wait() 就消亡(仍假设父进程没有忽略 SIGCLD )的子进程(活动的或者 defunct)成为 init 的子进程,init 着手处理它们。
1、父进程通过wait和waitpid等函数等待子进程结束,这会导致父进程挂起。
在上个例子中,如果我们略作修改,在第8行sleep()系统调用前执行wait()或waitpid()系统调用,则子进程在终止后会立即把它在进程表中的数据返回给父进程,此时系统会立即删除该进入点。在这种情形下就不会产生defunct进程。
2. 如果父进程很忙,那么可以用signal函数为SIGCHLD安装handler。在子进程结束后,父进程会收到该信号,可以在handler中调用wait回收。
3. 如果父进程不关心子进程什么时候结束,那么可以用signal(SIGCLD, SIG_IGN)或signal(SIGCHLD, SIG_IGN)通知内核,自己对子进程的结束不感兴趣,那么子进程结束后,内核会回收,并不再给父进程发送信号
4. fork两次,父进程fork一个子进程,然后继续工作,子进程fork一个孙进程后退出,那么孙进程被init接管,孙进程结束后,init会回收。不过子进程的回收还要自己做。 下面就是Stevens给的采用两次folk避免僵尸进程的示例:
#include "apue.h" #include <sys/wait.h> int main(void) ...{ pid_t pid; if ((pid = fork()) < 0) ...{ err_sys("fork error"); } else if (pid == 0) ...{ /**//* first child */ if ((pid = fork()) < 0) err_sys("fork error"); else if (pid > 0) exit(0); /**//* parent from second fork == first child */ /**//* * We're the second child; our parent becomes init as soon * as our real parent calls exit() in the statement above. * Here's where we'd continue executing, knowing that when * we're done, init will reap our status. */ sleep(2); printf("second child, parent pid = %d ", getppid()); exit(0); } if (waitpid(pid, NULL, 0) != pid) /**//* wait for first child */ err_sys("waitpid error"); /**//* * We're the parent (the original process); we continue executing, * knowing that we're not the parent of the second child. */ exit(0); }
相关推荐:《Linux视频教程》
以上がLinux ゾンビプロセスとは何ですか?の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。