fork用法與範例

fork是linux的system call
是用來創造出一個child process的函數

[目錄]
    fork規格與格式
    fork範例


[fork規格與格式]

語法:
        pid_t fork(void);

標頭檔:
        #include <unistd.h>

回傳值:
         0:回傳到child process
        child pid (大於零的整數):回傳到parent process
        -1:如果有error的話會回傳到parent process,此時不創造child process

注意事項:
        1. child process是用copy on write的方式,從parent process複製出一個
            完全相同的process。要注意是process,不是thread,是兩個獨立的process。
            parent process和child process有各自獨立的memory space
        3. 只有process的memory space是獨立的,變數中指向的外部檔案依舊是同一份
        4. parent process和child process都會從fork()的地方繼續向下執行程式碼
        5. child process不會繼承parent process原有的lock、timer以及signal處理
            也就是child process中一切和process相關的設定都是預設的初始值
        6. child process終止後,會回傳一個叫做SIGCHLD的signal給parent process
            parent接收後會讀出child的exit status,有了child的終止狀態後
            系統才會將child process從process table中刪除以釋放資源,稱作回收(reaped)
            若child終止後,其SIGCHLD沒有被parent接收,就會變成zombie process
        7. 若parent process比child process早終止,child就會變成orphan process
            orphan process會自動被init收養為child process,故orphan的parent pid是1
       

[fork範例]

調用fork()的時候最需要注意的點就是不要讓child process變成zombie process
一般來說有三種主流的方式
1. 用wait()來接收SIGCHLD,但parent process就會block在wait()這行程式碼
    (如果調用的是watipid(),就能在第三個參數option中設定non-blocking的wait
    但因為他的參數有點多,所以不在本文的討論範圍中)
2. 用兩層fork()來製造出orphan process,避免產生zombie process
3. 利用signal(int signum, sighandler_t handler)函數
    來明確地指定要忽略SIGCHLD這個訊號(接收它但並不做任何處理)
其中,第三招只有短短一行,又不會block住parent process,所以我通常用第三招

首先我們先來看沒有避免zombie process產生的做法
//fork_test.cpp
#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>
#include <iostream>

int main(){
        std::cout<< "in main" << std::endl;
        if(fork()==0){ /*child process*/
                std::cout<< "in child process, pid=" << getpid() << std::endl;
                exit(0);
        }else{ /*parent process*/
                std::cout<< "in parent process, pid=" << getpid() << std::endl;
                sleep(120);
        }
}

result : 
我用screen開兩個window來檢視結果,首先是window 0
再來是window 1,用ps aux來檢視process狀態,process太多了我只有擷取我們需要的來看
在window 1中我們可以發現,child process(pid 2521) 的process state呈現"Z+"的狀態
"+"號先不管,它只是表示是前景進程而已(foreground process)
重點是那個"Z"表示的就是它是一個zombie process,這時你用kill 2521是殺不掉它的
要等到我們的parent process結束後,zombie process自然地被init給收養
然後init會週期地調用wait()來回收其收養的所有zombie process


接著我們來看有避免zombie process產生的三招,一個一個來
1.用wait()來接收SIGCHLD
//fork_test.cpp
#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <iostream>

int main(){
        std::cout<< "in main" << std::endl;
        int status;
        if(fork()==0){ /*child process*/
                std::cout<< "in child process, pid=" << getpid() << std::endl;
                exit(0);
        }else{ /*parent process*/
                std::cout<< "in parent process, pid=" << getpid() << std::endl;
                wait(&status);
                std::cout << "get child pid, status=" << status << std::endl;
                sleep(120);
        }
}
result:
在window 0中
在window 1中
從window 1的結果我們可以看到已終止的child process(pid 3921)已經不在process table中了


2.用兩層fork()來製造出orphan process,避免產生zombie process
//fork_test.cpp
#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <iostream>
using namespace std;

int main(){
        cout<< "in main" << endl;
        int status;
        if(fork()==0){ /*child process*/
                cout<< "in child process, pid=" << getpid() << endl;
                if(fork()==0){ /*child process*/
                        cout<< "in grandchild process, pid=" << getpid() << endl;
                }else{ /*parent process*/
                        exit(0);
                }
        }else{ /*parent process*/
                cout<< "in parent process, pid=" << getpid() << endl;
                wait(&status);
                cout << "get child, status=" << status << endl;
                sleep(120);
        }

}
result:
在window 0中
在window 1中
此時child(pid 3983)還有已經變成orphan process的grandchild(pid 3984)都已經終止了
前者被parent(pid 3982)用wait()回收,後者被init用wait()回收,故不在process table中


3. 利用signal(int signum, sighandler_t handler)函數
    來明確地指定要忽略SIGCHLD這個訊號(接收它但並不做任何處理)
//fork_test.cpp
#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>
#include <signal.h>
#include <iostream>

int main(){
        std::cout<< "in main" << std::endl;
        signal(SIGCHLD,SIG_IGN);
        if(fork()==0){ /*child process*/
                std::cout<< "in child process, pid=" << getpid() << std::endl;
                exit(0);
        }else{ /*parent process*/
                std::cout<< "in parent process, pid=" << getpid() << std::endl;
                sleep(120);
        }
}

result:
在window 0中
在window 1中

好了,今天的筆記到此結束
希望有幫助未來遺忘這些的自己,以及需要的人

留言

  1. Hi, 有個疑問
    當 process 呼叫 fork 時,child 應該會繼承 parent 對 signal 的處理方式(signal dispositions)吧@@?

    回覆刪除

張貼留言

這個網誌中的熱門文章