# fork函數(shù)解析

fork()函數(shù)由linux系統(tǒng)調(diào)用,創(chuàng)建一個(gè)與原來(lái)進(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)一般(不絕對(duì))會(huì)優(yōu)先給子進(jìn)程分配資源,例如存儲(chǔ)數(shù)據(jù)和代碼的空間,然后把父進(jìn)程的所有值都復(fù)制到子進(jìn)程中,只有少數(shù)值與原來(lái)的進(jìn)程的值不同。也就是父進(jìn)程與子進(jìn)程的代碼和數(shù)據(jù)是一致的,只有通過(guò)判斷fork()的返回值來(lái)判斷父子進(jìn)程從而在父子進(jìn)程中實(shí)現(xiàn)不能的功能(下面細(xì)說(shuō))

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

fork()

fork函數(shù)執(zhí)行一次,返回兩個(gè)值,在父進(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)程中的不同返回值,可以實(shí)現(xiàn)父子進(jìn)程的不同功能,如下偽代碼:

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

注:

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

回收SIG_CHLD信號(hào)

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

#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)程捕獲信號(hào)
    {
        struct sigaction act;
        act.sa_handler = catch_child;  //捕獲函數(shù)綁定
        act.sa_flags = 0;
        sigaction(SIGCHLD,&act,NULL);  //捕獲SIG_CHLD函數(shù)
       
    }

    }
  
    getchar();
    return 0;
}

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

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

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

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

#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;                        //聲明信號(hào)集
    sigemptyset(&set);                   //將信號(hào)集置空
    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)程捕獲信號(hào)
    {
        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信號(hào)
        sleep(1);                      
    }

    }
  
    getchar();
    return 0;
}

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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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