# fork函數(shù)解析

fork()函數(shù)由linux系統(tǒng)調(diào)用,創(chuàng)建一個與原來進(jìn)程幾乎完全相同的進(jìn)程。調(diào)用fork的進(jìn)程稱為父進(jìn)程,調(diào)用fork()函數(shù)之后產(chǎn)生的進(jìn)程稱為子進(jìn)程。在調(diào)用fork()函數(shù)之后,系統(tǒng)一般(不絕對)會優(yōu)先給子進(jìn)程分配資源,例如存儲數(shù)據(jù)和代碼的空間,然后把父進(jìn)程的所有值都復(fù)制到子進(jìn)程中,只有少數(shù)值與原來的進(jìn)程的值不同。也就是父進(jìn)程與子進(jìn)程的代碼和數(shù)據(jù)是一致的,只有通過判斷fork()的返回值來判斷父子進(jìn)程從而在父子進(jìn)程中實現(xiàn)不能的功能(下面細(xì)說)

注:父進(jìn)程在執(zhí)行fork()函數(shù)之后,父子進(jìn)程都會執(zhí)行fork()的下一句代碼

fork()

fork函數(shù)執(zhí)行一次,返回兩個值,在父進(jìn)程中返回子進(jìn)程的進(jìn)程ID,在子進(jìn)程中返回0,然后父子進(jìn)程都執(zhí)行fork的下一句代碼。函數(shù)聲明如下:

pid_t fork(void);(其中pid_t為進(jìn)程ID的類型聲明)

根據(jù)fork在父子進(jìn)程中的不同返回值,可以實現(xiàn)父子進(jìn)程的不同功能,如下偽代碼:

int main(){
    ...
    pid_t pid = fork();//調(diào)用fork
    if(pid == 0){      //如果在子進(jìn)程(因為pid為0),執(zhí)行if的代碼
        ...
    } 
    else if(pid > 0){ //如果pid>0,即當(dāng)前進(jìn)程為父進(jìn)程,
        ...           //執(zhí)行else if的代碼
    }
    
    return 0;
}

注:

  • 一般情況下系統(tǒng)會優(yōu)先為子進(jìn)程分配空間資源,然后將父進(jìn)程的數(shù)據(jù)與代碼復(fù)制給子進(jìn)程,而且子進(jìn)程一般會先執(zhí)行完,所以如果在不設(shè)置阻塞集或通過代碼讓子進(jìn)程延時執(zhí)行的話,父進(jìn)程不能捕捉到子進(jìn)程的結(jié)束信號。
  • 子進(jìn)程在調(diào)用exit()函數(shù)之后或者在執(zhí)行完代碼之后會主動發(fā)出SIG_CHLD信號

回收SIG_CHLD信號

子進(jìn)程在退出時會向父進(jìn)程釋放進(jìn)程結(jié)束的信號(SIG_CHLD),在回收該信號(信號回收請看之前的文章)時我們需要注意,由于系統(tǒng)會優(yōu)先為子進(jìn)程分配資源,所以一般情況下,子進(jìn)程會先執(zhí)行完,所以可能父進(jìn)程還沒有對SIG_CHLD信號進(jìn)行捕捉,子進(jìn)程就已經(jīng)把信號釋放了,這樣在父進(jìn)程中就會顯示沒有捕捉到SIG_CHLD信號。如下示例:

#include <unistd.h>
#include <iostream>
using namespace std;

void catch_child(int num){
    std::cout << "1" << std::endl;
    pid_t pid = waitpid(-1,NULL,WNOHANG);   //等待子進(jìn)程終止,
    if(pid > 0){                            
        std::cout << "success kill sun process"<< pid <<std::endl;
    }
}




int main(int argc, const char** argv) {
    for (size_t i = 0; i < 2; i++)
    {
    pid_t pid = fork();  //創(chuàng)建子進(jìn)程
    if(pid<0){
        std::cout << "creat pid erro" << std::endl;
    }
    if(pid == 0)         //子進(jìn)程中執(zhí)行的代碼
    {
        
        std::cout << "son" << "ppid:"<<getppid()<<std::endl;
    }
    else                  //父進(jìn)程捕獲信號
    {
        struct sigaction act;
        act.sa_handler = catch_child;  //捕獲函數(shù)綁定
        act.sa_flags = 0;
        sigaction(SIGCHLD,&act,NULL);  //捕獲SIG_CHLD函數(shù)
       
    }

    }
  
    getchar();
    return 0;
}

執(zhí)行上述程序,你會發(fā)現(xiàn),子進(jìn)程創(chuàng)建成功了,但是在父進(jìn)程中卻沒有捕獲到子進(jìn)程終止的信號,這就是因為子進(jìn)程執(zhí)行的比較快,還沒有等到父進(jìn)程執(zhí)行到sigaction(SIGCHLD,&act,NULL)去捕獲SIG_CHLD信號時,子進(jìn)程就已經(jīng)將SIG_CHLD信號釋放了,如此便導(dǎo)致父進(jìn)程沒有不做到任何子進(jìn)程終止的信號,有興趣的讀者可以嘗試在子進(jìn)程執(zhí)行的代碼里加入sleep(1)讓子進(jìn)程沉睡1秒鐘,然后父進(jìn)程就可以捕獲到信號了,當(dāng)然這是為了證明我給出的出現(xiàn)該現(xiàn)象的原因,在實際解決問題時我們不會這么做,而是采用下面描述的方法。

我們可以通過設(shè)置阻塞集的方法來解決上述問題,具體實現(xiàn)步驟為:

  • 在程序的開始將SIG_CHLD加入阻塞集。
  • 在父進(jìn)程執(zhí)行的代碼中,將SIG_CHLD信號移出阻塞集,然后執(zhí)行sigaction函數(shù)捕捉SIG_CHLD信號。

不難看出上述方法的總體思路就是:將SIG_CHLD信號阻塞至父進(jìn)程開始捕捉該信號為止。具體示例如下:

#include <unistd.h>
#include <iostream>
using namespace std;

void catch_child(int num){
    std::cout << "1" << std::endl;
    pid_t pid = waitpid(-1,NULL,WNOHANG);   //等待子進(jìn)程終止,
    if(pid > 0){                            
        std::cout << "success kill sun process"<< pid <<std::endl;
    }
}




int main(int argc, const char** argv) {
    sigset_t set;                        //聲明信號集
    sigemptyset(&set);                   //將信號集置空
    sigaddset(&set,SIGCHLD);             //將`SIG_CHLD`加入其中
    sigprocmask(SIG_BLOCK,&set,NULL);    //加入阻塞集
    
    
    for (size_t i = 0; i < 2; i++)
    {
    pid_t pid = fork();  //創(chuàng)建子進(jìn)程
    if(pid<0){
        std::cout << "creat pid erro" << std::endl;
    }
    if(pid == 0)         //子進(jìn)程中執(zhí)行的代碼
    {
        std::cout << "son" << "ppid:"<<getppid()<<std::endl;
    }
    else                  //父進(jìn)程捕獲信號
    {
        struct sigaction act;
        act.sa_handler = catch_child;  //捕獲函數(shù)綁定
        act.sa_flags = 0;
        sigemptyset(&set);             //移出阻塞集
        sigaction(SIGCHLD,&act,NULL);  //捕獲SIG_CHLD函數(shù).
        //防止父進(jìn)程退出而不能捕獲子SIG_CHLD信號
        sleep(1);                      
    }

    }
  
    getchar();
    return 0;
}

執(zhí)行上述程序,就會將子進(jìn)程成功捕獲退出的信號打印出來。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容