回收子進(jìn)程

大綱

  • 孤兒進(jìn)程
  • 僵尸進(jìn)程
  • wait函數(shù)
  • waitpid函數(shù)

在Linux中正常情況下,子進(jìn)程是通過父進(jìn)程創(chuàng)建的悲酷,子進(jìn)程再創(chuàng)建新的進(jìn)程。子進(jìn)程的結(jié)束和父進(jìn)程的運(yùn)行是一個異步過程,也就是說,父進(jìn)程永遠(yuǎn)無法預(yù)知到子進(jìn)程會在什么時候結(jié)束肮塞。當(dāng)一個進(jìn)程完成工作終止后,它的父進(jìn)程需要調(diào)用wait()waitpid()系統(tǒng)調(diào)用來獲取子進(jìn)程的終止?fàn)顟B(tài)姻锁。

子進(jìn)程有兩種比較重要的狀態(tài)枕赵,分別是孤兒進(jìn)程和僵尸進(jìn)程。

孤兒進(jìn)程

孤兒進(jìn)程是指父進(jìn)程先于子進(jìn)程結(jié)束屋摔,通俗來講也就是爹比兒子死得早烁设,此時子進(jìn)程成為孤兒進(jìn)程,此時父進(jìn)程會成為init進(jìn)程(進(jìn)程號pid為1的進(jìn)程)钓试,init進(jìn)程可視為進(jìn)程孤兒院用于領(lǐng)養(yǎng)孤兒進(jìn)程。

$ ps aux
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  0.1  0.4 225808  9512 ?        Ss   00:43   0:02 /sbin/init splash
$ ps aux | grep init
root         1  0.1  0.4 225808  9512 ?        Ss   00:43   0:02 /sbin/init splash
jc        3613  0.0  0.0  21536  1000 pts/0    S+   01:18   0:00 grep --color=auto init

當(dāng)一個父進(jìn)程退出而它的子進(jìn)程還在運(yùn)行副瀑,此時這些子進(jìn)程將會成為孤兒進(jìn)程弓熏,孤兒進(jìn)程將被init進(jìn)程收養(yǎng),并由init進(jìn)程對它們完成狀態(tài)收集工作糠睡。

危害

孤兒進(jìn)程是沒有父進(jìn)程的子進(jìn)程挽鞠,因此孤兒進(jìn)程的管理重任會落到init進(jìn)程身上,init進(jìn)程好像一個民政局或孤兒所,專門負(fù)責(zé)處理孤兒進(jìn)程的善后工作信认。每當(dāng)出現(xiàn)一個孤兒進(jìn)程時內(nèi)核就會把孤兒進(jìn)程的父進(jìn)程設(shè)置為init進(jìn)程材义,init進(jìn)程會循環(huán)地wait已經(jīng)退出的子進(jìn)程。這樣嫁赏,當(dāng)一個孤兒進(jìn)程的生命周期結(jié)束時其掂,init進(jìn)程會代表黨和政府處理善后工作,因此孤兒進(jìn)程不會有什么危害潦蝇。

實(shí)例1

$ vim orphan.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(void)
{
    pid_t pid;
    pid = fork();//創(chuàng)建子進(jìn)程
        //如果當(dāng)前進(jìn)程是init進(jìn)程則報錯退出
    if(pid == 1)
    {
        perror("init process");
        exit(1);
    }
    else if(pid > 0)
    {
        sleep(1);
        printf("parent pid = %d, parent parent pid is %d\n", getpid(), getppid());
    }
    else if(pid == 0)
    {
        printf("child pid is %d, parent pid is %d\n", getpid(), getppid());
        sleep(3);
        printf("child pid is %d, parent pid is %d\n", getpid(), getppid());

    }

    return 0;
}
$ gcc orphan.c -o orphan -Wall -g
$ ./orphan
child pid is 3652, parent pid is 3651
parent pid = 3651, parent parent pid is 3633
root@junchow:/home/jc/projects/c# pachild pid is 3652, parent pid is 2700
$ ps aux | grep 2700
jc        2700  0.0  0.3  77120  8292 ?        Ss   00:57   0:00 /lib/systemd/systemd --user
root      3672  0.0  0.0  21536  1052 pts/0    S+   01:21   0:00 grep --color=auto 2700

實(shí)例2

$ vim orphan.c
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>

int main(void)
{
    pid_t pid;
    pid = fork();
    if(pid == 0)
    {
        while(1)
        {
            printf("child: parent pid=%d\n", getppid());
            sleep(1);
        }
    }
    else if(pid > 0)
    {
        printf("parent: pid=%d\n", getpid());
        sleep(10);
        printf("parent: going to die\n");
    }
    else
    {
        perror("fork");
        return 1;
    }
    return 0;
}
$ gcc orphan.c -o orphan -Wall -g
$ ./orphan

僵尸進(jìn)程

僵尸進(jìn)程是如何產(chǎn)生的呢款熬?

當(dāng)運(yùn)行一個程序時會產(chǎn)生一個父進(jìn)程以及多個子進(jìn)程,所有子進(jìn)程都會消耗內(nèi)核分配的內(nèi)存和CPU資源攘乒。子進(jìn)程完成執(zhí)行后會發(fā)送一個exit信號然后死掉贤牛。這個exit型號需要被父進(jìn)程讀取。父進(jìn)程需要隨后調(diào)用wait命令來讀取進(jìn)程的退出狀態(tài)则酝,并將子進(jìn)程從進(jìn)程表中移除殉簸。

在UNIX系統(tǒng)中一旦進(jìn)程結(jié)束,如果父進(jìn)程沒有使用waitwaitpid系統(tǒng)調(diào)用等待子進(jìn)程結(jié)束沽讹,又沒有顯式的忽略SIGCHILD信號喂链,那么它將變成一個僵尸進(jìn)程并一直存在。僵尸進(jìn)程是一個早已死亡的進(jìn)程妥泉,但在進(jìn)程表中任占據(jù)著一個位置椭微。

如果子進(jìn)程的父進(jìn)程已先行結(jié)束,那么子進(jìn)程就不會變成僵尸進(jìn)程盲链,因?yàn)槊總€進(jìn)程結(jié)束的時候蝇率,系統(tǒng)會掃描當(dāng)前操作系統(tǒng)中所有運(yùn)行的進(jìn)程,檢查有沒有哪個進(jìn)程是剛剛結(jié)束的進(jìn)程的子進(jìn)程刽沾。若有則由init進(jìn)程來接管并作為它的父進(jìn)程本慕,從而保證每個進(jìn)程都會有一個父進(jìn)程。init進(jìn)程會自動wait其子進(jìn)程侧漓,因此被init接管的進(jìn)程都不會變成僵尸進(jìn)程锅尘。

僵尸進(jìn)程

僵尸進(jìn)程是指進(jìn)程終止,父進(jìn)程尚未回收布蔗,子進(jìn)程殘留資源PCB存放于內(nèi)核kernel中變?yōu)榻┦?code>zombie進(jìn)程藤违。

父進(jìn)程有義務(wù)將子進(jìn)程回收,如果子進(jìn)程死亡后父進(jìn)程不幫子進(jìn)程收尸纵揍,此時子進(jìn)程就會變成僵尸顿乒。

如何查找僵尸進(jìn)程呢?

$ ps -ef | grep defunct
$ ps aux | grep Z

僵尸進(jìn)程存在會有什么樣的危害呢泽谨?

正常進(jìn)程死亡以后璧榄,0到4G的進(jìn)程地址空間會主動釋放特漩,但是PCB依然會殘留在內(nèi)核中,其目的是為了讓父進(jìn)程為它報仇骨杂。如果子進(jìn)程死亡后沒有任何痕跡留下的話涂身,父進(jìn)程將無法知道子進(jìn)程是由于什么原因造成的死亡,是自殺還是他殺呢搓蚪?子進(jìn)程死亡后會在內(nèi)核中殘留PCB蛤售,父進(jìn)程通過PCB可以獲取子進(jìn)程的死亡狀態(tài)。

任何一個子進(jìn)程(init進(jìn)程除外)在exit退出后并非立即消失陕凹,而會留下一個稱為僵尸進(jìn)程的數(shù)據(jù)結(jié)構(gòu)悍抑,以等待父進(jìn)程處理。

UNIX提供了一種機(jī)制讓父進(jìn)程獲取子進(jìn)程結(jié)束時的狀態(tài)信息杜耙,這種機(jī)制就是在每個子進(jìn)程退出的時候搜骡,內(nèi)核會釋放該進(jìn)程所有的資源,包括打開的文件佑女、占用的內(nèi)存等记靡。但內(nèi)核仍然會保留一定的信息,如進(jìn)程號团驱、退出狀態(tài)摸吠、運(yùn)行時間等信息。直到父進(jìn)程通過waitwaitpid系統(tǒng)調(diào)用來獲取時才完全釋放嚎花。

這樣就導(dǎo)致了一個問題寸痢,如果進(jìn)程不調(diào)用 waitwaitpid,那么保留在內(nèi)核中的殘留信息將不會主動被釋放紊选,進(jìn)程號會一直被占用啼止。由于系統(tǒng)所能使用的進(jìn)程號是有限的,如果大量產(chǎn)生僵尸進(jìn)程的話兵罢,系統(tǒng)將沒有可用的進(jìn)程號献烦,從而導(dǎo)致系統(tǒng)不能產(chǎn)生新的進(jìn)程。

此時如果使用ps命令查看會發(fā)現(xiàn)子進(jìn)程的狀態(tài)為Z卖词,即僵尸進(jìn)程巩那。

嚴(yán)格來說,僵尸進(jìn)程并非罪魁禍?zhǔn)状蓑冢瑔栴}的根源在于產(chǎn)生大量僵尸進(jìn)程的那個父進(jìn)程即横。可以使用kill命令發(fā)送信號SIGTERMSIGKILL信號槍斃元兇進(jìn)程舶替。槍斃完成后令境,僵尸進(jìn)程將會變成了孤兒進(jìn)程,這些孤兒進(jìn)程會被ini進(jìn)程接管顾瞪。init進(jìn)程會wait`這些孤兒進(jìn)程舔庶,釋放他們占用的系統(tǒng)資源。

需要注意的是僵尸進(jìn)程不能使用kill命令清除陈醒,因?yàn)?code>kill命令只是用來終止進(jìn)程惕橙,而僵尸進(jìn)程已經(jīng)是終止的。

如何避免僵尸進(jìn)程呢钉跷?

僵尸進(jìn)程的產(chǎn)生是因?yàn)楦高M(jìn)程沒有wait子進(jìn)程弥鹦,當(dāng)系統(tǒng)中出現(xiàn)了僵尸進(jìn)程,是無法通過kill命令來清除的爷辙,但可以殺死僵尸進(jìn)程的父進(jìn)程彬坏,讓其變成孤兒進(jìn)程,系統(tǒng)會統(tǒng)一管理和清理孤兒進(jìn)程膝晾。

有什么辦法可以清除掉僵尸進(jìn)程呢栓始?

正常情況下可以使用SIGKILL信號來殺死進(jìn)程,由于僵尸進(jìn)程已經(jīng)早死了血当,所以不能使用kill命令殺死已經(jīng)死掉的進(jìn)程幻赚。

$ kill -s SIGCHILD pid

可使用waitwaitpid回收僵尸進(jìn)程

實(shí)例

$ vim zoombie.c
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>

int main(void)
{
    pid_t pid;
    
    //創(chuàng)建子進(jìn)程
    pid = fork();
    printf("pid=%d\n", pid);

    //若子進(jìn)程為調(diào)度進(jìn)程
    if(pid == 0)
    {
        printf("child: parent=%d\n", getppid());
        printf("child: going to sleep\n");
        sleep(10);
        printf("child: child die\n");
    }
    else if(pid > 0)
    {
        while(1)
        {
            printf("parent: pid=%d child=%d\n", getpid(), pid);
            sleep(1);
        }
    }
    else
    {
        perror("fork error");
        return 1;
    }

    return 0;
}

$ gcc zoombie.c -o zoombie -Wall -g
$ ./zoombie

運(yùn)行結(jié)果

pid=3654
parent: pid=3653 child=3654
pid=0
child: parent=3653
child: going to sleep
parent: pid=3653 child=3654
parent: pid=3653 child=3654
parent: pid=3653 child=3654
parent: pid=3653 child=3654
parent: pid=3653 child=3654
parent: pid=3653 child=3654
parent: pid=3653 child=3654
parent: pid=3653 child=3654
parent: pid=3653 child=3654
child: child die
parent: pid=3653 child=3654
parent: pid=3653 child=3654
parent: pid=3653 child=3654
...

注意若運(yùn)行出現(xiàn)implicit declaration of function ‘fork’ [-Wimplicit-function-declaration]則需注意是否引入了unistd.h頭文件。

wait函數(shù)

一個進(jìn)程在終止時會關(guān)閉所文件描述符臊旭,釋放用戶空間并分配內(nèi)存落恼,只是子進(jìn)程的PCB還保留著,內(nèi)核在其中保持努著一些信息离熏。如果是正常終止或保存退出狀態(tài)佳谦。如果是異常終止則保存著導(dǎo)致該進(jìn)程的信號,這個進(jìn)程可使用Shell中特殊滋戳。

父進(jìn)程調(diào)用wait系統(tǒng)調(diào)用可以回收子進(jìn)程的終止信息钻蔑,然后徹底清除掉這個進(jìn)程,一個進(jìn)程的狀態(tài)可以在Shell中的特殊變量$?來查看胧瓜。

wait函數(shù)的三個功能分別是

  1. 阻塞等待子進(jìn)程退出
  2. 回收子進(jìn)程殘留的信息
  3. 獲取子進(jìn)程結(jié)束狀態(tài)與退出原因
pid_t wait(int *status)

wait函數(shù)成功則清理掉子進(jìn)程ID矢棚,若失敗則返回-1表示沒有子進(jìn)程。

當(dāng)進(jìn)程終止時操作系統(tǒng)的隱式回收機(jī)制

  1. 關(guān)閉所有文件描述符
  2. 釋放用戶空間分配的內(nèi)存

進(jìn)程終止時內(nèi)核的PCB仍舊存在府喳,其中保存該進(jìn)程的退出狀態(tài)蒲肋,簡單來說,正常終止時返回退出值钝满,異常終止時返回終止信號兜粘。

實(shí)例:父進(jìn)程回收子進(jìn)程

$ vim wait.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>

int main(void)
{
    pid_t pid;//創(chuàng)建子進(jìn)程的進(jìn)程ID
    pid_t wpid;//回收子進(jìn)程的返回值
    
    //創(chuàng)建子進(jìn)程
    pid = fork();
    printf("pid=%d\n", pid);

    //若子進(jìn)程為調(diào)度進(jìn)程
    if(pid == 0)
    {
        //子進(jìn)程休眠10秒后死亡
        printf("child: parent=%d\n", getppid());
        printf("child: going to sleep\n");
        sleep(10);
        printf("child: child die\n");
    }
    else if(pid > 0)
    {
        //回收子進(jìn)程若失敗則提示錯誤
        wpid = wait(NULL);
        if(wpid == -1)
        {
            perror("wait error");
            exit(1 );
        }

        while(1)
        {
            printf("parent: pid=%d child=%d\n", getpid(), pid);
            sleep(1);
        }
    }
    else
    {
        perror("fork error");
        return 1;
    }

    return 0;
}
$ gcc wait.c -o wait -Wall -g
$ ./wait
pid=3757
pid=0
child: parent=3756
child: going to sleep
child: child die
parent: pid=3756 child=3757
parent: pid=3756 child=3757
parent: pid=3756 child=3757
...

此時若查看進(jìn)程狀態(tài)會發(fā)現(xiàn)并未出現(xiàn)僵尸進(jìn)程

$ ps aux|grep wait
jc        3756  0.0  0.0   4508   740 pts/0    S+   22:42   0:00 ./wait
jc        3767  0.0  0.0  21536  1060 pts/1    S+   22:42   0:00 grep --color=auto wait

子進(jìn)程死亡狀態(tài)

當(dāng)進(jìn)程終止時刻使用wait函數(shù)傳出參數(shù)status來保存進(jìn)程的推出狀態(tài),借助宏函數(shù)來進(jìn)一步判斷進(jìn)程終止的具體原因弯蚜。宏函數(shù)可分為三組:

  1. 正常退出
  • WIFEXITED(status) :全稱wait if exited表示判斷是否是正常退出的
    若非0則表示進(jìn)程正常退出
  • WEXITSTATUS(status) :全稱wait exit status表示退出狀態(tài)
    WIFEXITED(status)為真則使用宏WEXITSTATUS(status)以獲取進(jìn)程退出的狀態(tài)孔轴,即exit的參數(shù)。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>

int main(void)
{
    pid_t pid;//創(chuàng)建子進(jìn)程的進(jìn)程ID
    pid_t wpid;//回收子進(jìn)程的返回值
    int status;//回收子進(jìn)程返回的狀態(tài)
    //創(chuàng)建子進(jìn)程
    pid = fork();
    printf("pid=%d\n", pid);

    //若子進(jìn)程為調(diào)度進(jìn)程
    if(pid == 0)
    {
        //子進(jìn)程休眠3秒后死亡
        printf("child-%d: parent=%d\n", pid, getppid());
        printf("child-%d: going to sleep 3 seconds\n", pid);
        sleep(3);
        printf("child-%d: child die\n", pid);
        exit(76);//退出值不超過128
    }
    else if(pid > 0)
    {
        //回收子進(jìn)程
        wpid = wait(&status);
        //回收子進(jìn)程若失敗則提示錯誤
        if(wpid == -1)
        {
            perror("wait error");
            exit(1);
        }
        //若子進(jìn)程回收成功
        printf("child-%d: wait if existed with %d\n", pid, WIFEXITED(status));
        if(WIFEXITED(status) != 0)
        {
            //獲取子進(jìn)程成功退出的狀態(tài)值
            printf("child-%d: wait exit status with %d\n", pid, WEXITSTATUS(status));
        }


        while(1)
        {
            printf("parent-%d: child=%d\n", getpid(), pid);
            sleep(1);
        }
    }
    else
    {
        perror("fork error");
        return 1;
    }

    return 0;
}

編譯運(yùn)行程序并查看狀態(tài)

$ gcc wait.c -o wait -Wall -g
$ ./wait
pid=3934
pid=0
child-0: parent=3933
child-0: going to sleep 3 seconds
child-0: child die
child-3934: wait if existed with 1
child-3934: wait exit status with 76
parent-3933: child=3934
parent-3933: child=3934
parent-3933: child=3934
...

注意:此處使用exit(76);也可以使用return 76;進(jìn)行替換碎捺。

  1. 異常退出
  • WIFSIGNALED(status) :全稱wait if signaled
    若非0則表示進(jìn)程異常終止
  • WTERMSIG(status):全稱wait term signal
    WIFSIGNALED(status)為真則使用宏WTERMSIG(status)以獲得使進(jìn)程終止的那個信號的編號路鹰。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>

int main(void)
{
    pid_t pid;//創(chuàng)建子進(jìn)程的進(jìn)程ID
    pid_t wpid;//回收子進(jìn)程的返回值
    int status;//回收子進(jìn)程返回的狀態(tài)
    //創(chuàng)建子進(jìn)程
    pid = fork();
    printf("pid=%d\n", pid);

    //若子進(jìn)程為調(diào)度進(jìn)程
    if(pid == 0)
    {
        //子進(jìn)程休眠后死亡
        printf("child-%d: parent=%d\n", pid, getppid());
        printf("child-%d: going to sleep 60 seconds\n", pid);
        sleep(60);
        printf("child-%d: child die\n", pid);
        exit(76);//退出值不超過128
    }
    else if(pid > 0)
    {
        //回收子進(jìn)程
        wpid = wait(&status);
        //回收子進(jìn)程若失敗則提示錯誤
        if(wpid == -1)
        {
            perror("wait error");
            exit(1 );
        }
        //若子進(jìn)程正常退出
        printf("child-%d: wait if existed with %d\n", pid, WIFEXITED(status));
        if(WIFEXITED(status) != 0)
        {
            //獲取子進(jìn)程成功退出的狀態(tài)值
            printf("child-%d: wait exit status with %d\n", pid, WEXITSTATUS(status));
        }
        //若子進(jìn)程異常退出
        printf("child-%d: wait if signaled with %d\n", pid, WIFSIGNALED(status));
        if(WIFSIGNALED(status) != 0)
        {
            printf("child-%d: wait term sig with %d\n", pid, WTERMSIG(status));
        }


        while(1)
        {
            printf("parent-%d: child=%d\n", getpid(), pid);
            sleep(1);
        }
    }
    else
    {
        perror("fork error");
        return 1;
    }

    return 0;
}

編譯并運(yùn)行程序

$ gcc wait.c -o wait -Wall -g
$ ./wait

此時讓子進(jìn)程休眠60秒贷洲,為什么呢,在60秒內(nèi)新開命令行窗口并殺死子進(jìn)程和父進(jìn)程查看輸出狀態(tài)晋柱。

$ ps aux|grep wait
root      1139  0.0  0.7 194320 17560 ?        Ssl  22:03   0:00 /usr/bin/python3 /usr/share/unattended-upgrades/unattended-upgrade-shutdown --wait-for-signal
jc        4030  0.0  0.0   4508   856 pts/0    S+   23:44   0:00 ./wait
jc        4031  0.0  0.0   4508    72 pts/0    S+   23:44   0:00 ./wait
jc        4034  0.0  0.0  21536  1148 pts/1    S+   23:44   0:00 grep --color=auto wait

此時會發(fā)現(xiàn)有兩個進(jìn)程正在運(yùn)行优构,4030對應(yīng)的應(yīng)該是父進(jìn)程,4031對應(yīng)的應(yīng)該是子進(jìn)程雁竞,接下來分別殺死進(jìn)程钦椭。

$ kill -9 4031
$ kill -9 4030

最后查看日志輸出信息

pid=4031
pid=0
child-0: parent=4030
child-0: going to sleep 60 seconds
child-4031: wait if existed with 0
child-4031: wait if signaled with 1
child-4031: wait term sig with 9
parent-4030: child=4031
parent-4030: child=4031
parent-4030: child=4031
parent-4030: child=4031
parent-4030: child=4031
parent-4030: child=4031
parent-4030: child=4031
parent-4030: child=4031
parent-4030: child=4031
parent-4030: child=4031
已殺死

此處需注意child-4031: wait term sig with 9中的9是由于使用kill -9 4031,也就是向進(jìn)程發(fā)送的9號信號碑诉。表示什么意思呢彪腔?

$ kill -L
 1) SIGHUP   2) SIGINT   3) SIGQUIT  4) SIGILL   5) SIGTRAP
 6) SIGABRT  7) SIGBUS   8) SIGFPE   9) SIGKILL 10) SIGUSR1
11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM
16) SIGSTKFLT   17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
21) SIGTTIN 22) SIGTTOU 23) SIGURG  24) SIGXCPU 25) SIGXFSZ
26) SIGVTALRM   27) SIGPROF 28) SIGWINCH    29) SIGIO   30) SIGPWR
31) SIGSYS  34) SIGRTMIN    35) SIGRTMIN+1  36) SIGRTMIN+2  37) SIGRTMIN+3
38) SIGRTMIN+4  39) SIGRTMIN+5  40) SIGRTMIN+6  41) SIGRTMIN+7  42) SIGRTMIN+8
43) SIGRTMIN+9  44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9  56) SIGRTMAX-8  57) SIGRTMAX-7
58) SIGRTMAX-6  59) SIGRTMAX-5  60) SIGRTMAX-4  61) SIGRTMAX-3  62) SIGRTMAX-2
63) SIGRTMAX-1  64) SIGRTMAX    

根據(jù)命令可以知道9號信號表示SIGKILL

  1. 暫停狀態(tài)
  • WIFSTOPPED(status):全稱wait if stopped進(jìn)程處于暫停狀態(tài)
    若非0則表示進(jìn)程處于暫停狀態(tài)
  • WSTOPSIG(status)
    WIFSTOPPED(status)為真則使用宏WSTOPSIG(status)以獲取使進(jìn)程暫停的那個信號的編號。
  • WIFCONTINUED(status)
    WIFCONTINUED(status)為真則表示進(jìn)程暫停后已經(jīng)繼續(xù)運(yùn)行

子進(jìn)程死亡一般會存在兩種情況进栽,一種是正常死亡壽終正寢德挣,此時程序?qū)⒎祷?。另一種情況是異常退出泪幌,在Linux中所有的異常退出都是由于信號導(dǎo)致的盲厌,由于子進(jìn)程收到了某個特殊信號它才異常退出。所以異常終止時父進(jìn)程需要回收子進(jìn)程異常終止的信號祸泪。

waitpid函數(shù)

waitpid函數(shù)作用于wait函數(shù)相同吗浩,但可以指定pid進(jìn)程清理且不阻塞。

pid_t waitpid(pid_t pid, int *status, int options)

waitpid若成功則返回清理掉的進(jìn)程ID没隘,若失敗則返回-1表示無子進(jìn)程懂扼。

wait函數(shù)與waitpid的區(qū)別在于一次wait調(diào)用只能回收一個子進(jìn)程,而waitpid可以指定進(jìn)程ID進(jìn)行回收更加靈活右蒲。

waitpid參數(shù)pid返回情況

  • pid小于-1表示回收指定進(jìn)程組中的任意子進(jìn)程
  • pid等于0表示回收和當(dāng)前調(diào)用waitpid一個組的所有子進(jìn)程
  • pid等于-1表示回收任意子進(jìn)程阀湿,作用相當(dāng)于wait函數(shù)。
  • pid大于0表示回收指定進(jìn)程ID的子進(jìn)程

waitpid參數(shù)options可以設(shè)置是否阻塞瑰妄,設(shè)置非阻塞可使用WNOHANG宏陷嘴,設(shè)置非阻塞后需要使用輪詢的方式定時查看子進(jìn)程是否回收成功。

$ vim waipid.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>

int main(int argc, char *argv[])
{
    int n = 5;//默認(rèn)創(chuàng)建5個子進(jìn)程
    int i;//循環(huán)變量
    pid_t pid;//子進(jìn)程ID
    pid_t tmp;

    //若命令行參數(shù)個數(shù)是2個
    if(argc == 2)
    {
        n = atoi(argv[1]);
    }
    //循環(huán)創(chuàng)建子進(jìn)程
    for(i=0; i<n; i++)
    {
        pid = fork();
        //若進(jìn)程為系統(tǒng)進(jìn)程
        if(pid == 0)
        {
            break;
        }
        else if(i ==3)
        {
            tmp = pid;
        }
    }
    printf("tmp=%d\n", tmp);
    if(n == i)
    {
        sleep(n);//多少個循環(huán)則休眠多少秒
        printf("parent: pid=%d\n", getpid());//打印父進(jìn)程
        //子進(jìn)程死亡后回收
        wait(NULL);//隨機(jī)回收子進(jìn)程且只回收一個
        while(1);//死循環(huán)
    }
    else
    {
        sleep(i);//休眠
        printf("child%d: pid=%d\n", i+1, getpid());//打印子進(jìn)程
        //while(1);//死循環(huán)
    }

    return 0;
}
$ gcc waitpid.c -o waitpid -Wall -g
$ ./waitpid
tmp=5043
tmp=5043
tmp=0
tmp=0
tmp=0
tmp=0
child1: pid=5040
child2: pid=5041
child3: pid=5042
child4: pid=5043
child5: pid=5044
parent: pid=5039
$ ps aux|grep waitpid
jc        5039 81.6  0.0   4508   756 pts/0    R+   03:52   0:25 ./waitpid
jc        5041  0.0  0.0      0     0 pts/0    Z+   03:52   0:00 [waitpid] <defunct>
jc        5042  0.0  0.0      0     0 pts/0    Z+   03:52   0:00 [waitpid] <defunct>
jc        5043  0.0  0.0      0     0 pts/0    Z+   03:52   0:00 [waitpid] <defunct>
jc        5044  0.0  0.0      0     0 pts/0    Z+   03:52   0:00 [waitpid] <defunct>

可以查看到當(dāng)前存在4個僵尸進(jìn)程Z+间坐,使用wait回收時每次只回收了一個進(jìn)程灾挨,如果需要同時回收5個進(jìn)程,應(yīng)該怎么辦呢竹宋?

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>

int main(int argc, char *argv[])
{
    int n = 5;//默認(rèn)創(chuàng)建5個子進(jìn)程
    int i;//循環(huán)變量
    pid_t pid;//子進(jìn)程ID
    pid_t tmp;

    //若命令行參數(shù)個數(shù)是2個
    if(argc == 2)
    {
        n = atoi(argv[1]);
    }
    //循環(huán)創(chuàng)建子進(jìn)程
    for(i=0; i<n; i++)
    {
        pid = fork();
        //若進(jìn)程為系統(tǒng)進(jìn)程
        if(pid == 0)
        {
            break;
        }
        else if(i ==3)
        {
            tmp = pid;
        }
    }
    printf("tmp=%d\n", tmp);
    if(n == i)
    {
        sleep(n);//多少個循環(huán)則休眠多少秒
        printf("parent: pid=%d\n", getpid());//打印父進(jìn)程
        //子進(jìn)程死亡后回收
        while(wait(NULL));//循環(huán)回收子進(jìn)程劳澄,每次只能回收一個。
        while(1);//死循環(huán)
    }
    else
    {
        sleep(i);//休眠
        printf("child%d: pid=%d\n", i+1, getpid());//打印子進(jìn)程
        //while(1);//死循環(huán)
    }

    return 0;
}

此處會發(fā)現(xiàn)如果需要回收多個進(jìn)程蜈七,可使用while循環(huán)的方式秒拔。

如果現(xiàn)在需要指定某個進(jìn)程進(jìn)行回收呢?應(yīng)該怎么辦飒硅?

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>

int main(int argc, char *argv[])
{
    int n = 5;//默認(rèn)創(chuàng)建5個子進(jìn)程
    int i;//循環(huán)變量
    pid_t pid;//子進(jìn)程ID
    pid_t tmp;

    //若命令行參數(shù)個數(shù)是2個
    if(argc == 2)
    {
        n = atoi(argv[1]);
    }
    //循環(huán)創(chuàng)建子進(jìn)程
    for(i=0; i<n; i++)
    {
        pid = fork();
        //若進(jìn)程為系統(tǒng)進(jìn)程
        if(pid == 0)
        {
            break;
        }
        else if(i ==3)
        {
            tmp = pid;//獲取第三個子進(jìn)程的pid砂缩,為了指定進(jìn)程進(jìn)行回收使用作谚。
        }
    }
    printf("tmp=%d\n", tmp);
    if(n == i)
    {
        sleep(n);//多少個循環(huán)則休眠多少秒
        printf("parent: pid=%d\n", getpid());//打印父進(jìn)程
        //子進(jìn)程死亡后回收
        waitpid(tmp, NULL, 0);//指定回收某個子進(jìn)程
        while(1);//死循環(huán)
    }
    else
    {
        sleep(i);//休眠
        printf("child%d: pid=%d\n", i+1, getpid());//打印子進(jìn)程
        //while(1);//死循環(huán)
    }

    return 0;
}
$ gcc waitpid.c -o waitpid -Wall -g
$ ./waitpid
tmp=5097
tmp=5097
tmp=0
tmp=0
tmp=0
tmp=0
child1: pid=5094
child2: pid=5095
child3: pid=5096
child4: pid=5097
child5: pid=5098
parent: pid=5093
$ ps aux|grep waitpid
jc        5093 54.5  0.0   4508   740 pts/0    R+   04:05   0:06 ./waitpid
jc        5094  0.0  0.0      0     0 pts/0    Z+   04:05   0:00 [waitpid] <defunct>
jc        5095  0.0  0.0      0     0 pts/0    Z+   04:05   0:00 [waitpid] <defunct>
jc        5096  0.0  0.0      0     0 pts/0    Z+   04:05   0:00 [waitpid] <defunct>
jc        5098  0.0  0.0      0     0 pts/0    Z+   04:05   0:00 [waitpid] <defunct>
jc        5100  0.0  0.0  21536  1028 pts/1    S+   04:05   0:00 grep --color=auto waitpid

此處會發(fā)現(xiàn)pid=5087的子進(jìn)程被回收了

未完待續(xù)...

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市梯轻,隨后出現(xiàn)的幾起案子食磕,更是在濱河造成了極大的恐慌尽棕,老刑警劉巖喳挑,帶你破解...
    沈念sama閱讀 219,427評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異滔悉,居然都是意外死亡伊诵,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評論 3 395
  • 文/潘曉璐 我一進(jìn)店門回官,熙熙樓的掌柜王于貴愁眉苦臉地迎上來曹宴,“玉大人,你說我怎么就攤上這事歉提〉烟梗” “怎么了?”我有些...
    開封第一講書人閱讀 165,747評論 0 356
  • 文/不壞的土叔 我叫張陵苔巨,是天一觀的道長版扩。 經(jīng)常有香客問我,道長侄泽,這世上最難降的妖魔是什么礁芦? 我笑而不...
    開封第一講書人閱讀 58,939評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮悼尾,結(jié)果婚禮上柿扣,老公的妹妹穿的比我還像新娘。我一直安慰自己闺魏,他們只是感情好未状,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,955評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著析桥,像睡著了一般司草。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上烹骨,一...
    開封第一講書人閱讀 51,737評論 1 305
  • 那天翻伺,我揣著相機(jī)與錄音,去河邊找鬼沮焕。 笑死吨岭,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的峦树。 我是一名探鬼主播辣辫,決...
    沈念sama閱讀 40,448評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼旦事,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了急灭?” 一聲冷哼從身側(cè)響起姐浮,我...
    開封第一講書人閱讀 39,352評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎葬馋,沒想到半個月后卖鲤,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,834評論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡畴嘶,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,992評論 3 338
  • 正文 我和宋清朗相戀三年蛋逾,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片窗悯。...
    茶點(diǎn)故事閱讀 40,133評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡区匣,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出蒋院,到底是詐尸還是另有隱情亏钩,我是刑警寧澤,帶...
    沈念sama閱讀 35,815評論 5 346
  • 正文 年R本政府宣布欺旧,位于F島的核電站姑丑,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏切端。R本人自食惡果不足惜彻坛,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,477評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望踏枣。 院中可真熱鬧昌屉,春花似錦、人聲如沸茵瀑。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,022評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽马昨。三九已至竞帽,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間鸿捧,已是汗流浹背屹篓。 一陣腳步聲響...
    開封第一講書人閱讀 33,147評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留匙奴,地道東北人堆巧。 一個月前我還...
    沈念sama閱讀 48,398評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親谍肤。 傳聞我的和親對象是個殘疾皇子啦租,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,077評論 2 355

推薦閱讀更多精彩內(nèi)容

  • 孤兒進(jìn)程 孤兒進(jìn)程: 父進(jìn)程先于子進(jìn)程結(jié)束,則子進(jìn)程成為孤兒進(jìn)程荒揣,子進(jìn)程的父進(jìn)程成為init進(jìn)程篷角,稱為init進(jìn)程...
    溫暖春陽閱讀 1,654評論 0 0
  • 我們知道在unix/linux中,子進(jìn)程的結(jié)束和父進(jìn)程的運(yùn)行是一個異步過程, 當(dāng)一個進(jìn)程完成它的工作終止之后系任,它的...
    丶Em1tu0F閱讀 877評論 0 3
  • Linux 進(jìn)程管理與程序開發(fā) 進(jìn)程是Linux事務(wù)管理的基本單元恳蹲,所有的進(jìn)程均擁有自己獨(dú)立的處理環(huán)境和系統(tǒng)資源,...
    JamesPeng閱讀 2,468評論 1 14
  • 1.內(nèi)存的頁面置換算法 (1)最佳置換算法(OPT)(理想置換算法):從主存中移出永遠(yuǎn)不再需要的頁面;如無這樣的...
    杰倫哎呦哎呦閱讀 3,254評論 1 9
  • 現(xiàn)在棱貌,80開始曬娃,90開始創(chuàng)業(yè)箕肃。對于70年代的人婚脱,已經(jīng)被動的無聲無息退出歷史的舞臺。做為70年代的人勺像,我承認(rèn)這樣...
    走在雨的縫中閱讀 255評論 0 2