當線程調用fork時,就為子進程創建了整個進程地址空間的副本?;貞沨ttp://www.CUOXin.com/nufangrensheng/p/3509492.html中討論的寫時復制,子進程與父進程是完全不同的進程,只要兩者都沒有對內存作出改動,父進程和子進程之間還可以共享內存頁的副本。
子進程通過繼承整個地址空間的副本,也從父進程那里繼承了所有互斥量、讀寫鎖和條件變量的狀態。如果父進程包含多個線程,子進程在fork返回以后,如果緊接著不是馬上調用exec的話,就需要清理鎖狀態。
在子進程內部只存在一個線程,它是由父進程中調用fork的線程的副本構成的。如果父進程中的線程占有鎖,子進程同樣占有這些鎖。問題是子進程并不包含占有鎖的線程的副本,所以子進程沒有辦法知道它占有了哪些鎖并且需要釋放哪些鎖。
如果子進程從fork返回以后馬上調用某個exec函數,就可以避免這樣的問題。這種情況下,老的地址空間被丟棄,所以鎖的狀態無關緊要。但如果子進程需要繼續做處理工作的話,這種方法就行不通,還需要使用其他的策略。
要清除鎖狀態,可以通過調用pthread_atfork函數建立fork處理程序。
#include <pthread.h>int pthread_atfork(void (*PRepare)(void), void (*parent)(void), void (*child)(void));返回值:若成功則返回0,否則返回錯誤編號
用pthread_atfork函數最多可以安裝三個幫助清理鎖的函數。prepare fork處理程序由父進程在fork創建子進程前調用,這個fork處理程序的任務是獲取父進程定義的所有鎖。parent fork處理程序是在fork創建了子進程以后,但在fork返回之前在父進程環境中調用的,這個fork處理程序的任務是對prepare fork處理程序獲得的所有鎖進行解鎖。child fork處理程序在fork返回之前在子進程環境中調用,與parent fork處理程序一樣,child fork處理程序也必須釋放prepare fork處理程序獲得的所有鎖。
注意不會出現加鎖一次解鎖兩次的情況,雖然看起來也許會出現。當子進程地址空間創建的時候,它得到了父進程定義的所有鎖的副本。因為prepare fork處理程序獲取所有的鎖,父進程中的內存和子進程中的內存內容在開始的時候是相同的。當父進程和子進程對他們的鎖的副本進行解鎖的時候,新的內存是分配給子進程的,父進程的內存內容被復制到子進程的內存中(寫時復制),所以就會陷入這樣的假象,看起來父進程對它所有的副本進行了加鎖,子進程對它所有的副本進行了加鎖。父進程和子進程對在不同內存位置的重復的鎖都進行了解鎖操作,就好像出現了下列的時間序列:
(1)父進程獲得所有的鎖。
(2)子進程獲得所有的鎖。
(3)父進程釋放它的鎖。
(4)子進程釋放它的鎖。
可以多次調用pthread_atfork函數從而設置多套fork處理程序。如果不需要使用其中某個處理程序,可以給特定的處理程序參數傳入空指針,這樣它們就不會起任何作用。使用多個fork處理程序時,處理程序的調用順序并不相同。parent和child fork處理程序是以它們注冊時的順序進行調用的,而prepare fork處理程序的調用順序與它們注冊時的順序相反。這樣可以允許多個模塊注冊它們自己的fork處理程序,并且保持鎖的層次。
例如,假設模塊A調用模塊B中的函數,而且每個模塊有自己的一套鎖。如果鎖的層次是A在B之前,模塊B必須在模塊A之前設置fork處理程序。當父進程調用fork時,就會執行以下的步驟,假設子進程在父進程之前運行。
(1)調用模塊A的prepare fork處理程序獲取模塊A的所有鎖。
(2)調用模塊B的prepare fork處理程序獲取模塊B的所有鎖。
(3)創建子進程。
(4)調用模塊B中的child fork處理程序釋放子進程中模塊B的所有鎖。
(5)調用模塊A中的child fork處理程序釋放子進程中模塊A的所有鎖。
(6)fork函數返回到子進程。
(7)調用模塊B中的parent fork處理程序釋放父進程中模塊B的所有鎖。
(8)調用模塊A中的parent fork處理程序釋放父進程中模塊A的所有鎖。
(9)fork函數返回到父進程。
如果fork處理程序是為了清理鎖狀態,那么又由誰來負責清理條件變量的狀態呢?在有些操作系統的實現中,條件變量可能并不需要做任何清理。但是有些操作系統實現把鎖作為條件變量實現的一部分,這種情況下的條件變量就需要清理。問題是目前不存在這樣的接口,如果鎖是嵌入到條件變量的數據結構中的,那么在調用fork之后就不能使用條件變量,因為還沒有可移植的方法對其進行狀態清理。另外,如果操作系統的實現是使用全局鎖保護進程中所有的條件變量數據結構,那么操作系統實現本身可以在fork庫例程中做清理鎖的工作,但是應用程序不應該依賴操作系統實現中這樣的細節。
實例
程序清單12-7中的程序描述了如何使用pthread_atfork和fork處理程序。
程序清單12-7 pthread_atfork實例
#include "apue.h"#include <pthread.h>pthread_mutex_t lock1 = PTHREAD_MUTEX_INITIALIZER;pthread_mutex_t lock2 = PTHREAD_MUTEX_INITIALIZER;voidprepare(void){ printf("preparing locks.../n"); pthread_mutex_lock(&lock1); pthread_mutex_lock(&lock2);}void parent(void){ printf("parent unlocking locks.../n"); pthread_mutex_unlock(&lock1); pthread_mutex_unlock(&lock2);}voidchild(void){ printf("child unlocking locks.../n"); pthread_mutex_unlock(&lock1); pthread_mutex_unlock(&lock2);}void *thr_fn(void *arg){ printf("thread started.../n"); pause(); return(0);}intmain(void){ int err; pid_t pid; pthread_t tid; #if defined(BSD) || defined(MACOS) printf("pthread_atfork is unsupported/n");#else if((err = pthread_atfork(prepare, parent, child)) != 0) err_exit(err, "can't install fork handlers"); err = pthread_create(&tid, NULL, thr_fn, 0); if(err != 0) err_exit(err, "can't create thread"); sleep(2); printf("parent about to fork.../n"); if((pid = fork()) < 0) err_quit("fork failed"); else if(pid == 0) /* child */ printf("child returned from fork/n"); else /* parent */ printf("parent returned from fork/n");#endif exit(0);}
程序中定義了兩個互斥量,lock1和lock2,prepare fork處理程序獲取這兩把鎖,child fork處理程序在子進程環境中釋放鎖,parent fork處理程序在父進程中釋放鎖。
運行該程序,得到如下輸出:
可以看出,prepare fork處理程序在調用fork以后運行,child fork處理程序在fork調用返回到子進程之前運行,parent fork處理程序在fork調用返回給父進程前運行。
本篇博文內容摘自《UNIX環境高級編程》(第二版),僅作個人學習記錄所用。關于本書可參考:http://www.apuebook.com/。
新聞熱點
疑難解答