深入理解Linux fork函式

2022-12-26 23:54:02 字數 4708 閱讀 4870

2014/08/08

2015/02/12

程序必須的4個要點

a) 要有一段程式供該程序執行,就像一場戲劇要有乙個劇本一樣。該程式是可以被多個程序共享的,多場戲劇用乙個劇本一樣

b) 有起碼的私有財產,就是程序專用的系統堆疊空間

c) 有「戶口」,既作業系統所說的程序控制塊,在linux中具體實現是task_struct

d) 有獨立的儲存空間

當乙個程序缺少d條件時候,我們稱其為執行緒。

三者都是linux的系統呼叫,用來建立子程序的

確切說vfork創造出來的是執行緒

程序是乙個指令執行流及其執行環境,其執行環境是乙個系統資源的集合,這些資源在linux中被抽象成各種資料物件:程序控制塊、虛存空間、檔案系統,檔案i/o、訊號處理函式。所以建立乙個程序的過程就是這些資料物件的建立過程。

在呼叫系統呼叫fork建立乙個程序時,子程序只是完全複製父程序的資源,這樣得到的子程序獨立于父程序,具有良好的併發性,但是二者之間的通訊需要通過專門的通訊機制,如:pipe,fifo,system v,ipc機制等,另外通過fork建立子程序系統開銷很大,需要將上面描述的每種資源都複製乙個副本。這樣看來,fork是乙個開銷十分大的系統呼叫,這些開銷並不是所有的情況下都是必須的,比如某程序fork出乙個子程序後,其子程序僅僅是為了呼叫exec執行另乙個執行檔案,那麼在fork過程中對於虛擬儲存空間的複製將是乙個多餘的過程(由於linux中採取了copy-on-write技術,所以這一步驟的所做的工作只是虛擬記憶體管理部分的複製以及頁表的建立,而並沒有包括物理頁面的拷貝)

另外,有時乙個程序中具有幾個獨立的計算單元,可以在相同的位址空間上基本無衝突進行運算,但是為了把這些計算單元分配到不同的處理器上,需要建立幾個子程序,然後各個子程序分別計算最後通過一定的程序間通訊和同步機制把計算結果彙總,這樣做往往有許多格外的開銷,而且這種開銷有時足以抵消平行計算帶來的好處。

這說明了把計算單元抽象到程序上是不充分的,這也就是許多系統中都引入了執行緒的概念的原因。在講述執行緒前首先介紹以下vfork系統呼叫,vfork系統呼叫不同於fork,用vfork建立的子程序共享位址空間,也就是說子程序完全執行在父程序的位址空間上,子程序對虛擬位址空間任何資料的修改同樣為父程序所見。但是用vfork建立子程序後,父程序會被阻塞直到子程序呼叫exec或exit。

這樣的好處是在子程序被建立後僅僅是為了呼叫exec執行另乙個程式時,因為它就不會對父程序的位址空間有任何引用,所以對位址空間的複製是多餘的,通過vfork可以減少不必要的開銷。

在linux中, fork和vfork都是呼叫同乙個核心函式

do_fork(unsigned long clone_flag, unsigned long usp, struct pt_regs)

其中clone_flag包括clone_vm, clone_fs, clone_files, clone_sighand, clone_pid,clone_vfork等等標誌位,任何一位被置1了則表明建立的子程序和父程序共享該位對應的資源。所以在vfork的實現中,cloneflags = clone_vfork | clone_vm | sigchld,這表示子程序和父程序共享位址空間,同時do_fork會檢查clone_vfork,如果該位被置1了,子程序會把父程序的位址空間鎖住,直到子程序退出或執行exec時才釋放鎖。

在講述clone系統呼叫前先簡單介紹執行緒的一些概念

執行緒是在程序的基礎上進一步的抽象,也就是說乙個程序分為兩個部分:執行緒集合和資源集合。執行緒是程序中的乙個動態物件,它應該是一組獨立的指令流,程序中的所有執行緒將共享程序裡的資源。

但是執行緒應該有自己的私有物件:比如程式計數器、堆疊和暫存器上下文。執行緒分為三種型別:

核心執行緒、輕量級程序和使用者執行緒。

核心執行緒:

它的建立和撤消是由核心的內部需求來決定的,用來負責執行乙個指定的函式,乙個核心執行緒不需要和乙個使用者程序聯絡起來。它共享核心的正文段核全域性資料,具有自己的核心堆疊。它能夠單獨的被排程並且使用標準的核心同步機制,可以被單獨的分配到乙個處理器上執行。

核心執行緒的排程由於不需要經過核心態與使用者態的轉換並進行位址空間的重新對映,因此在核心執行緒間做上下文切換比在程序間做上下文切換快得多。

輕量級程序:

輕量級程序是核心支援的使用者執行緒,它在乙個單獨的程序中提供多執行緒控制。這些輕量級程序被單獨的排程,可以在多個處理器上執行,每乙個輕量級程序都被繫結在乙個核心執行緒上,而且在它的生命週期這種繫結都是有效的。輕量級程序被獨立排程並且共享位址空間和程序中的其它資源,但是每個lwp都應該有自己的程式計數器、暫存器集合、核心棧和使用者棧。

使用者執行緒:

使用者執行緒是通過執行緒庫實現的。它們可以在沒有核心參與下建立、釋放和管理。執行緒庫提供了同步和排程的方法。

這樣程序可以使用大量的執行緒而不消耗核心資源,而且省去大量的系統開銷。使用者執行緒的實現是可能的,因為使用者執行緒的上下文可以在沒有核心干預的情況下儲存和恢復。每個使用者執行緒都可以有自己的使用者堆疊,一塊用來儲存使用者級暫存器上下文以及如訊號遮蔽等狀態資訊的記憶體區。

庫通過儲存當前執行緒的堆疊和暫存器內容載入新排程執行緒的那些內容來實現使用者執行緒之間的排程和上下文切換。

核心仍然負責程序的切換,因為只有核心具有修改記憶體管理暫存器的權力。使用者執行緒不是真正的排程實體,核心對它們一無所知,而只是排程使用者執行緒下的程序或者輕量級程序,這些程序再通過執行緒庫函式來排程它們的執行緒。當乙個程序被搶占時,它的所有使用者執行緒都被搶占,當乙個使用者執行緒被阻塞

時,它會阻塞下面的輕量級程序,如果程序只有乙個輕量級程序,則它的所有使用者執行緒都會被阻塞。

明確了這些概念後,來講述linux的執行緒和clone系統呼叫。

在許多實現了mt的作業系統中(如:solaris,digital unix等), 執行緒和程序通過兩種資料結構來抽象表示: 程序表項和執行緒表項,乙個程序表項可以指向若干個執行緒表項, 排程器在程序的時間片內再排程執行緒。

但是在linux中沒有做這種區分, 而是統一使用task_struct來管理所有程序/執行緒,只是執行緒與執行緒之間的資源是共享的,這些資源可是是前面提到過的:虛存、檔案系統、檔案i/o以及訊號處理函式甚至pid中的幾種。

也就是說linux中,每個執行緒都有乙個task_struct,所以執行緒和程序可以使用同一排程器排程。其實linux核心中,輕量級程序和程序沒有質上的差別,因為linux中程序的概念已經被抽象成了計算狀態加資源的集合,這些資源在程序間可以共享。如果乙個task獨佔所有的資源,則是乙個hwp(重量級執行緒),如果乙個task和其它task共享部分資源,則是lwp(輕量級執行緒)。

clone系統呼叫就是乙個建立輕量級程序的系統呼叫

int clone(int (*fn)(void * arg), void *stack, int flags, void * arg);

其中fn是輕量級程序所執行的過程,stack是輕量級程序所使用的堆疊,flags可以是前面提到的clone_vm, clone_fs, clone_files, clone_sighand,clone_pid的組合。clone 和fork,vfork在實現時都是呼叫核心函式do_fork。

do_fork(unsigned long clone_flag, unsigned long usp, struct pt_regs);

● fork時clone_flag = sigchld;

● vfork時clone_flag = clone_vm | clone_vfork | sigchld;

● clone中,clone_flag由使用者給出。

看起來clone的用法和pthread_create有些相似,兩者的最根本的差別在於clone是建立乙個lwp,對核心是可見的,由核心排程,而pthread_create通常只是建立乙個使用者執行緒,對核心是不可見的,由執行緒庫排程。

linux的pthread_create最終呼叫clone, pthread_create呼叫clone,並把開闢乙個stack作為引數thread 建立, 同步,銷毀等由執行緒庫負責。

實際使用者id, 實際組id, 有效使用者id,有效組id

附加組id

程序組id

會話id

控制終端

設定使用者id標誌,設定組id標誌

當前工作目錄

根目錄檔案模式建立遮蔽字

訊號遮蔽和訊號處理函式

所有開啟的檔案描述符

執行時關閉標誌close on exec

環境(資料空間,堆,棧,記憶體)

連線的共享儲存段

儲存限制

資源限制

執行緒鎖也會被繼承,不過兩個程序的鎖就獨立互不相干了

父程序設定的檔案鎖

子程序未處理的鬧鐘alarm將被清除

子程序的未處理訊號集合設定為空集

子程序和父程序共享正文段

不繼承鎖定的記憶體

● #include

● int mlock(const void *addr,size_t length)

● int munlock(void *addr,size_t length)

● int mlockall(int flag)

● int munlockall(void)

不繼承非同步輸入和輸出

nice值

一般的,fork做如下事情

● 父程序的記憶體資料會原封不動的拷貝到子程序中

● 子程序在單執行緒狀態下被生成

● 子程序只拷貝了當前呼叫執行緒,其他執行緒憑空消失

● fork產生的子程序一般用exit結束

● vfork產生的產生的子程序一般用_exit結束

linux的執行緒實現是在核外進行的,核內提供的是建立程序的介面do_fork()。核心提供了兩個系統呼叫clone()和fork(),最終都用不同的引數呼叫do_fork()核內api。

利用助學,深入理解文字

作者 程菊香 新語文學習 教師 2013年第03期 隨著新課改的深入,越來越多的教師在文字閱讀教學過程中開始重視開發和文字相關的資源來幫助學生進行有效閱讀,以期讓學生能更深入地走進文字,與文字 作者 編者進行對話,從而能深入文字的核心,完成真正意義上的閱讀,實現課堂教學的有效生成。與文字相關的資源很...

深入理解C語言指標的奧秘

指標的概念 指標是乙個特殊的變數,它裡面儲存的數值被解釋成為記憶體裡的乙個位址。要搞清乙個指標需要搞清指標的四方面的內容 指標的型別,指標所指向的型別,指標的值或者叫指標所指向的記憶體區,還有指標本身所佔據的記憶體區。讓我們分別說明。先宣告幾個指標放著做例子 例一 1 int ptr 2 char ...

c中如何深入理解「事件與委託」

中如何深入理解 事件與委託 初學者必看 2008年01月19日星期六 22 57 事件是特殊的委託 這是個人理解 首先介紹乙個為什麼要在事件中引入委託這個概念 事件是物件傳送的訊息,以發訊號通知操作的發生。操作可能是由使用者互動 例如滑鼠單擊 引起的,也可能是由某些其他的程式邏輯觸發的。引發事件的物...