Heim  >  Artikel  >  Backend-Entwicklung  >  PHP7-Waisenprozess und Zombie-Prozess

PHP7-Waisenprozess und Zombie-Prozess

藏色散人
藏色散人nach vorne
2019-04-08 10:22:033244Durchsuche

Grundlegende Konzepte

Wir wissen, dass unter Unix/Linux unter normalen Umständen der untergeordnete Prozess durch den übergeordneten Prozess erstellt wird und der untergeordnete Prozess einen neuen Prozess erstellt. Das Ende des untergeordneten Prozesses und die Ausführung des übergeordneten Prozesses sind asynchrone Prozesse, das heißt, der übergeordnete Prozess kann niemals vorhersagen, wann der untergeordnete Prozess enden wird. Wenn ein Prozess seine Arbeit abschließt und beendet wird, muss sein übergeordneter Prozess den Systemaufruf wait() oder waitpid() aufrufen, um den Beendigungsstatus des untergeordneten Prozesses zu erhalten.

Verwaister Prozess

Ein übergeordneter Prozess wird beendet, während einer oder mehrere seiner untergeordneten Prozesse noch ausgeführt werden. Diese untergeordneten Prozesse werden dann zu verwaisten Prozessen. Der verwaiste Prozess wird vom Init-Prozess übernommen (Prozessnummer ist 1) und der Init-Prozess schließt die Statuserfassungsarbeit für ihn ab.

Zombie-Prozess

Ein Prozess verwendet fork, um einen untergeordneten Prozess zu erstellen, wenn der untergeordnete Prozess beendet wird und der übergeordnete Prozess nicht „wait“ oder „waitpid“ aufruft, um die Statusinformationen zu erhalten des untergeordneten Prozesses, dann Der Prozessdeskriptor des untergeordneten Prozesses wird weiterhin im System gespeichert. Dieser Prozess wird als Zombie-Prozess bezeichnet.

Probleme und Gefahren

Unix bietet einen Mechanismus, um sicherzustellen, dass der übergeordnete Prozess die Statusinformationen erhalten kann, wenn der untergeordnete Prozess endet, solange er sie wissen möchte . Dieser Mechanismus ist: Wenn jeder Prozess beendet wird, gibt der Kernel alle Ressourcen des Prozesses frei, einschließlich geöffneter Dateien, belegtem Speicher usw. Bestimmte Informationen bleiben jedoch weiterhin erhalten (einschließlich der Prozess-ID, des Beendigungsstatus des Prozesses, der vom Prozess benötigten CPU-Zeit usw.). Es wird erst freigegeben, wenn der übergeordnete Prozess es über wait/waitpid abruft. Dies führt jedoch zu Problemen. Wenn der Prozess nicht „wait/waitpid“ aufruft, werden die gespeicherten Informationen nicht freigegeben und die Anzahl der Prozessnummern, die das System verwenden kann, ist begrenzt Wenn ein Zombie-Prozess generiert wird, kann das System keine neuen Prozesse generieren, da keine Prozessnummer verfügbar ist. Dies ist der Schaden von Zombie-Prozessen und sollte vermieden werden.

Ein Waisenprozess ist ein Prozess ohne übergeordneten Prozess. Der Init-Prozess ist wie ein Büro für zivile Angelegenheiten, das für die Bearbeitung der Folgen des Waisenprozesses verantwortlich ist. Immer wenn ein verwaister Prozess erscheint, setzt der Kernel den übergeordneten Prozess des verwaisten Prozesses auf init, und der init-Prozess wartet zyklisch auf seine beendeten untergeordneten Prozesse. Wenn ein verwaister Prozess seinen Lebenszyklus kläglich beendet, kümmert sich der Init-Prozess auf diese Weise im Namen der Partei und der Regierung um alle seine Nachwirkungen. Daher richten verwaiste Prozesse keinen Schaden an.

Jeder untergeordnete Prozess (außer init) verschwindet nicht sofort nach exit(), sondern hinterlässt eine Datenstruktur, die als Zombie-Prozess (Zombie) bezeichnet wird und auf die Verarbeitung durch den übergeordneten Prozess wartet. Dies ist die Phase, die jeder untergeordnete Prozess am Ende durchläuft. Wenn der untergeordnete Prozess nach Exit() keine Zeit zum Verarbeiten hat, können Sie mit dem Befehl ps feststellen, dass der Status des untergeordneten Prozesses „Z“ ist. Wenn der übergeordnete Prozess dies rechtzeitig verarbeiten kann, ist es möglicherweise zu spät, den Zombie-Status des untergeordneten Prozesses mithilfe des Befehls ps anzuzeigen. Dies bedeutet jedoch nicht, dass der untergeordnete Prozess den Zombie-Status nicht durchläuft. Wenn der übergeordnete Prozess beendet wird, bevor der untergeordnete Prozess endet, wird der untergeordnete Prozess von init übernommen. init verarbeitet den untergeordneten Prozess im Zombie-Zustand als übergeordneten Prozess.

Zombie-Prozess-Gefahrenszenarien

Zum Beispiel gibt es einen Prozess, der regelmäßig einen untergeordneten Prozess generiert. Dieser untergeordnete Prozess muss nur sehr wenige Dinge tun Wird beendet, wird der Lebenszyklus dieses untergeordneten Prozesses sehr kurz. Der übergeordnete Prozess generiert jedoch nur neue untergeordnete Prozesse und kümmert sich nicht darum, was nach dem Beenden des untergeordneten Prozesses geschieht Danach gibt es viele Zombie-Prozesse im System. Wenn Sie den Befehl ps verwenden, um ihn anzuzeigen, werden Sie viele Prozesse mit dem Status Z sehen. Streng genommen ist der Zombie-Prozess nicht die Ursache des Problems. Der Übeltäter ist der übergeordnete Prozess, der eine große Anzahl von Zombie-Prozessen hervorgebracht hat. Wenn wir also nach einer Möglichkeit suchen, eine große Anzahl von Zombie-Prozessen im System zu eliminieren, besteht die Antwort darin, den Täter zu erschießen, der eine große Anzahl von Zombie-Prozessen erzeugt hat (d. h. durch Senden eines SIGTERM- oder SIGKILL-Signals durch Kill). Nachdem der Täterprozess abgeschossen wurde, werden die von ihm generierten Zombie-Prozesse vom Init-Prozess übernommen. Der Init-Prozess wartet () auf diese Waisenprozesse und gibt die von ihnen belegten Ressourcen in der Systemprozesstabelle frei. Auf diese Weise können diese verwaisten Prozesse, die abgestorben sind, in Frieden verschwinden.

Test von verwaisten Prozessen und Zombie-Prozessen

1 Der verwaiste Prozess wurde vom Init-Prozess

$pid = pcntl_fork();
if ($pid > 0) {
    // 显示父进程的进程ID,这个函数可以是getmypid(),也可以用posix_getpid()
    echo "Father PID:" . getmypid() . PHP_EOL;
    // 让父进程停止两秒钟,在这两秒内,子进程的父进程ID还是这个父进程
    sleep(2);
} else if (0 == $pid) {
    // 让子进程循环10次,每次睡眠1s,然后每秒钟获取一次子进程的父进程进程ID
    for ($i = 1; $i <= 10; $i++) {
        sleep(1);
        // posix_getppid()函数的作用就是获取当前进程的父进程进程ID
        echo posix_getppid() . PHP_EOL;
    }
} else {
    echo "fork error." . PHP_EOL;
}

Test übernommen Ergebnisse:

php daemo001.php
Father PID:18046
18046
18046
www@iZ2zec3dge6rwz2uw4tveuZ:~/test$ 1
1
1
1
1
1
1
1

2. Zombie-Prozesse und Gefahren

Führen Sie den folgenden Code aus: php zombie1.php 

$pid = pcntl_fork();
if( $pid > 0 ){
    // 下面这个函数可以更改php进程的名称
    cli_set_process_title(&#39;php father process&#39;);
    // 让主进程休息60秒钟
    sleep(60);
} else if( 0 == $pid ) {
    cli_set_process_title(&#39;php child process&#39;);
    // 让子进程休息10秒钟,但是进程结束后,父进程不对子进程做任何处理工作,这样这个子进程就会变成僵尸进程
    sleep(10);
} else {
    exit(&#39;fork error.&#39;.PHP_EOL);
}

Ausführungsergebnisse, ein weiteres Terminalfenster

www@iZ2zec3dge6rwz2uw4tveuZ:~$ ps -aux|grep -v "grep\|nginx\|php-fpm" | grep php
www      18458  0.5  1.2 204068 25920 pts/1    S+   16:34   0:00 php father process
www      18459  0.0  0.3 204068  6656 pts/1    S+   16:34   0:00 php child process
www@iZ2zec3dge6rwz2uw4tveuZ:~$ ps -aux|grep -v "grep\|nginx\|php-fpm" | grep php
www      18458  0.0  1.2 204068 25920 pts/1    S+   16:34   0:00 php father process
www      18459  0.0  0.0      0     0 pts/1    Z+   16:34   0:00 [php] <defunct>

Durch Ausführen des Befehls ps -aux können Sie sehen, dass der Status des untergeordneten PHP-Prozesses als [S+] aufgeführt wird, wenn das Programm innerhalb der ersten zehn Sekunden ausgeführt wird ändert sich zu [Z+], außerdem wird es zu einem Zombie-Prozess, der dem System schadet.

Also, hier ist die Frage? Wie vermeide ich Zombie-Prozesse?

PHP通过 pcntl_wait() pcntl_waitpid() 两个函数来帮我们解决这个问题。了解Linux系统编程的应该知道,看名字就知道这其实就是PHP把C语言中的 wait() waitpid() 包装了一下。

通过代码演示 pcntl_wait() 来避免僵尸进程。

pcntl_wait() 函数:

这个函数的作用就是 “ 等待或者返回子进程的状态 ”,当父进程执行了该函数后,就会阻塞挂起等待子进程的状态一直等到子进程已经由于某种原因退出或者终止。

换句话说就是如果子进程还没结束,那么父进程就会一直等等等,如果子进程已经结束,那么父进程就会立刻得到子进程状态。这个函数返回退出的子进程的进程 ID 或者失败返回 -1。

执行以下代码 zombie2.php

$pid = pcntl_fork();
if ($pid > 0) {
    // 下面这个函数可以更改php进程的名称
    cli_set_process_title(&#39;php father process&#39;);
    // 返回$wait_result,就是子进程的进程号,如果子进程已经是僵尸进程则为0
    // 子进程状态则保存在了$status参数中,可以通过pcntl_wexitstatus()等一系列函数来查看$status的状态信息是什么
    $wait_result = pcntl_wait($status);
    print_r($wait_result);
    print_r($status);
    // 让主进程休息60秒钟
    sleep(60);
} else if (0 == $pid) {
    cli_set_process_title(&#39;php child process&#39;);
    // 让子进程休息10秒钟,但是进程结束后,父进程不对子进程做任何处理工作,这样这个子进程就会变成僵尸进程
    sleep(10);
} else {
    exit(&#39;fork error.&#39; . PHP_EOL);
}

在另外一个终端中通过ps -aux查看,可以看到在前十秒内,php child process 是 [S+] 状态,然后十秒钟过后进程消失了,也就是被父进程回收了,没有变成僵尸进程。

www@iZ2zec3dge6rwz2uw4tveuZ:~/test$ ps -aux|grep -v "grep\|nginx\|php-fpm" | grep php
www@iZ2zec3dge6rwz2uw4tveuZ:~/test$ ps -aux|grep -v "grep\|nginx\|php-fpm" | grep php
www      18519  0.5  1.2 204068 25576 pts/1    S+   16:42   0:00 php father process
www      18520  0.0  0.3 204068  6652 pts/1    S+   16:42   0:00 php child process
www@iZ2zec3dge6rwz2uw4tveuZ:~/test$ ps -aux|grep -v "grep\|nginx\|php-fpm" | grep php
www      18519  0.0  1.2 204068 25576 pts/1    S+   16:42   0:00 php father process

但是,pcntl_wait() 有个很大的问题,就是阻塞。父进程只能挂起等待子进程结束或终止,在此期间父进程什么都不能做,这并不符合多快好省原则,所以 pcntl_waitpid() 闪亮登场。pcntl_waitpid( pid, &status, $option = 0 )的第三个参数如果设置为WNOHANG,那么父进程不会阻塞一直等待到有子进程退出或终止,否则将会和pcntl_wait()的表现类似。

修改第三个案例的代码,但是,我们并不添加WNOHANG,演示说明pcntl_waitpid()功能:

$pid = pcntl_fork();
if ($pid > 0) {
    // 下面这个函数可以更改php进程的名称
    cli_set_process_title(&#39;php father process&#39;);
    // 返回值保存在$wait_result中
    // $pid参数表示 子进程的进程ID
    // 子进程状态则保存在了参数$status中
    // 将第三个option参数设置为常量WNOHANG,则可以避免主进程阻塞挂起,此处父进程将立即返回继续往下执行剩下的代码
    $wait_result = pcntl_waitpid($pid, $status);
    var_dump($wait_result);
    var_dump($status);
    // 让主进程休息60秒钟
    sleep(60);
} else if (0 == $pid) {
    cli_set_process_title(&#39;php child process&#39;);
    // 让子进程休息10秒钟,但是进程结束后,父进程不对子进程做任何处理工作,这样这个子进程就会变成僵尸进程
    sleep(10);
} else {
    exit(&#39;fork error.&#39; . PHP_EOL);
}

下面是运行结果,一个执行php zombie3.php 程序的终端窗口

www@iZ2zec3dge6rwz2uw4tveuZ:~/test$ php zombie3.php
int(18586)
int(0)
^C  

ctrl-c 发送 SIGINT 信号给前台进程组中的所有进程。常用于终止正在运行的程序。

下面是ps -aux终端窗口

www@iZ2zec3dge6rwz2uw4tveuZ:~$ ps -aux|grep -v "grep\|nginx\|php-fpm" | grep php
www      18605  0.3  1.2 204068 25756 pts/1    S+   16:52   0:00 php father process
www      18606  0.0  0.3 204068  6636 pts/1    S+   16:52   0:00 php child process
www@iZ2zec3dge6rwz2uw4tveuZ:~$ ps -aux|grep -v "grep\|nginx\|php-fpm" | grep php
www      18605  0.1  1.2 204068 25756 pts/1    S+   16:52   0:00 php father process
www@iZ2zec3dge6rwz2uw4tveuZ:~$ ps -aux|grep -v "grep\|nginx\|php-fpm" | grep php
www      18605  0.0  1.2 204068 25756 pts/1    S+   16:52   0:00 php father process
www@iZ2zec3dge6rwz2uw4tveuZ:~$ ps -aux|grep -v "grep\|nginx\|php-fpm" | grep php  // ctrl-c 后不再被阻塞
www@iZ2zec3dge6rwz2uw4tveuZ:~$

实际上可以看到主进程是被阻塞的,一直到第十秒子进程退出了,父进程不再阻塞  

修改第四段代码,添加第三个参数WNOHANG,代码如下:

$pid = pcntl_fork();
if ($pid > 0) {
    // 下面这个函数可以更改php进程的名称
    cli_set_process_title(&#39;php father process&#39;);
    // 返回值保存在$wait_result中
    // $pid参数表示 子进程的进程ID
    // 子进程状态则保存在了参数$status中
    // 将第三个option参数设置为常量WNOHANG,则可以避免主进程阻塞挂起,此处父进程将立即返回继续往下执行剩下的代码
    $wait_result = pcntl_waitpid($pid, $status, WNOHANG);
    var_dump($wait_result);
    var_dump($status);
    echo "不阻塞,运行到这里" . PHP_EOL;
    // 让主进程休息60秒钟
    sleep(60);
} else if (0 == $pid) {
    cli_set_process_title(&#39;php child process&#39;);
    // 让子进程休息10秒钟,但是进程结束后,父进程不对子进程做任何处理工作,这样这个子进程就会变成僵尸进程
    sleep(10);
} else {
    exit(&#39;fork error.&#39; . PHP_EOL);
}

执行 php zombie4.php 

www@iZ2zec3dge6rwz2uw4tveuZ:~/test$ php zombie4.php
int(0)
int(0)
不阻塞,运行到这里  

另一个ps -aux终端窗口

www@iZ2zec3dge6rwz2uw4tveuZ:~$ ps -aux|grep -v "grep\|nginx\|php-fpm" | grep php
www      18672  0.3  1.2 204068 26284 pts/1    S+   17:00   0:00 php father process
www      18673  0.0  0.3 204068  6656 pts/1    S+   17:00   0:00 php child process
www@iZ2zec3dge6rwz2uw4tveuZ:~$ ps -aux|grep -v "grep\|nginx\|php-fpm" | grep php
www      18672  0.0  1.2 204068 26284 pts/1    S+   17:00   0:00 php father process
www      18673  0.0  0.0      0     0 pts/1    Z+   17:00   0:00 [php] <defunct>

实际上可以看到主进程是被阻塞的,一直到第十秒子进程退出了,父进程不再阻塞。  

问题出现了,竟然php child process进程状态竟然变成了[Z+],这是怎么搞得?回头分析一下代码:
我们看到子进程是睡眠了十秒钟,而父进程在执行pcntl_waitpid()之前没有任何睡眠且本身不再阻塞,所以,主进程自己先执行下去了,而子进程在足足十秒钟后才结束,进程状态自然无法得到回收。

如果我们将代码修改一下,就是在主进程的pcntl_waitpid()前睡眠15秒钟,这样就可以回收子进程了。但是即便这样修改,细心想的话还是会有个问题,那就是在子进程结束后,在父进程执行pcntl_waitpid()回收前,有五秒钟的时间差,在这个时间差内,php child process也将会是僵尸进程。那么,pcntl_waitpid()如何正确使用啊?这样用,看起来毕竟不太科学。
那么,是时候引入信号学了!

Das obige ist der detaillierte Inhalt vonPHP7-Waisenprozess und Zombie-Prozess. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Dieser Artikel ist reproduziert unter:cnblogs.com. Bei Verstößen wenden Sie sich bitte an admin@php.cn löschen