首頁 > 軟體

進程管理之fork函數

2020-06-16 17:08:37

fork函數的定義

#include <unistd.h>
#include <sys/types.h>
pid_t fork(void);

fork函數在父進程中返回子進程的pid,在子進程中返回0。注意在子進程中返回的0,並不是子進程的pid,子進程的pid在父進程的返回值中儲存。而子進程的返回值是為了標識它是子進程,用來區分父子進程的。那麼為什麼這樣設計父子進程的返回值呢?我的理解是這樣的:第一,對於父進程來說,它可能同時有多個子進程,並且沒有一個函數可以獲得所有子進程的pid,所以它需要知道每個子進程的pid,這樣便於管理各個子進程;第二,對於子進程來說,它父進程只能有一個,所以它可以不必實現和父進程類似的機制,獲取父進程的pid,它可以通過getpid函數獲取父進程的pid。而在核心中,進程pid為0,總是在核心交換進程時使用,所以進程的真實pid不可能為0。

資源相關問題

讀時共用,寫時複製

fork函數並不是在被呼叫時,為子進程copy一份父進程的副本,而是將父進程資料段、堆區和棧區的許可權改變為唯讀。當父子進程中任何一個進程要修改該區域的值時,核心只會為修改區域的那塊記憶體製作一個副本,通常是虛擬記憶體中的一頁。這樣的機制無需copy所有的父進程資源(這些資源在子進程中不一定有用),提高了程式的效率。但要注意,父子進程共用程式碼段。

複製的資源

  • 能夠複製fork函數會複製資料段、堆區和棧區的資源
  • 能夠複製參數列和環境表
  • 能夠複製各個ID,但不包括PID
  • 不能夠繼承檔案鎖
1)資料段、堆區、棧區、參數列和環境表和上文說的一樣,遵循讀時共用,寫時複製的機制;
2)環境表中只能改變其本身進程和其子進程的環境變數,無法改變其父進程的環境變數;
3)像是實際使用者ID、實際組ID、有效使用者ID、有效組ID、行程群組ID和附屬ID,這些都是進程的屬性,所以存在於PCB中,每個進程都有自己的PCB節點,其中部分資料是繼承父進程的。當然pid不會。
4)對於檔案鎖,子進程是不會被繼承的。子進程雖然複製了檔案描述符表,但都指向同一個檔案表,當然了,檔案表也就指向了同一個i節點。在i節點中儲存著和檔案鎖相關的連結串列頭節點(struct lockf)的地址。i節點資源不屬於進程資源,而是檔案資源,所以不會繼承檔案鎖。

呼叫使用

常見使用方法

  • if...else結構:最常見的使用方法,很簡單。
  • 和管道結合使用:主要用來父子進程間通訊,使用時要注意,子進程會複製父進程的管道檔案描述符,因此,都可以對對管道進行讀寫操作。需要注意的是,在使用管道這個臨界資源時,不要忘記在軟體邏輯上避免進程飢餓現象的產生。
  • 子進程的管理:別忘了了父進程能返回子進程的pid。當有多個子進程時,如果會用到各個子進程的pid的話,不要只是定義一個 pid_t pid
    的變數。這樣的話,只能管理一個子進程喲!

避免僵屍進程的方法

  • 使用wait和waitpid函數:阻塞等待子進程結束,回收進程表資源
  • 安插信號:利用signal函數安插SIGCHLD信號。因為在子進程結束後,父進程會收到該信號。再自己寫個回撥函數,在函數中呼叫wait或waitpid函數,回收進程表資源;如果父進程對子進程結束不感興趣,則可以利用“signal(SIGCHLD,SIG_IGN)”,將回收子進程資源的工作交給核心來做;但要注意,SIGCHLD信號是傳統的不可靠信號,信號處理常式執行期間會暫時阻塞,因此,在這期前,如果又有了SIGCHLD信號,則會被拋棄,即無法處理多個SIGCHLD信號。所以信號處理常式的正確寫法是:

    void handler(int signs)
    {
    int tmp_errno=errno;  
    while(waitpid(-1,&status,WNOHANG)>0)
    {
        //處理返回資訊
    }
    errno=tmp_errno;
    }
    但仍要注意的是,當waitpid返回值為-1時,會改變全域性變數errno的值,如果這是在主程式中檢測errno的值時,就很有可能發生衝突。因此,在進入信號處理常式之前要儲存errno 的值,最後再回復errno的值。
  • 利用孤兒進程的機制:在建立的子進程中在呼叫fork函數,建立一個孫子進程,然後子進程終止,那麼孫子進程就會被init進程收養。再當孫子進程結束時,回收進程資源的工作就交由了init進程去做。當然,有人會問,那麼子進程結束後它的進程資源誰去回收?我說,那當然是父進程回收。這個時候能用父進程去回收子進程資源,是因為這是父子進程已經不在時非同步的關係了。換句直白點的話說就是,父進程知道子進程在不久的將來一定會結束。回頭想想,安插信號的方法的本質,不就是通過信號捕捉,確定了子進程會在不久的將來會結束嗎?即在呼叫wait和外套pid時,不會阻塞等待很長時間就能返回,不會影響父進程自身的工作。

注意:安插信號和利用孤兒進程的機制來避免僵屍進程的方法,不僅僅能避免僵屍進程,還能不影響父進程本身的工作任務,這一點是非常有用的。

本文永久更新連結地址http://www.linuxidc.com/Linux/2017-09/146737.htm


IT145.com E-mail:sddin#qq.com