程序控制之fork函式

2022-05-04 20:18:27 字數 3846 閱讀 1916

乙個現有程序可以呼叫fork函式建立乙個新程序。

#include pid_t fork( 

void

);返回值:子程序中返回0,父程序中返回子程序id,出錯返回-1

由fork建立的新程序被稱為子程序(child process)。fork函式被呼叫一次,但返回兩次。兩次返回的唯一區別是子程序的返回值是0,而父程序的返回值則是新子程序的程序id。將子程序id返回給父程序的理由是:因為乙個程序的子程序可以有多個,並且沒有乙個函式使乙個程序可以獲得其所有子程序的程序id。fork使子程序得到返回值0的理由是:乙個程序只會有乙個父程序,所以子程序總是可以呼叫getppid以獲得其父程序的程序id(程序id 0總是由核心交換程序使用,所以乙個子程序的程序id不可能為0)。

子程序和父程序繼續執行fork呼叫之後的指令。子程序是父程序的副本。例如,子程序獲得父程序的資料空間、堆和棧的副本注意,這是子程序所擁有的副本。父、子程序並不共享這些儲存空間部分。父、子程序共享正文段(text,**段)。

由於在fork之後經常跟隨著exec,所以現在的很多實現並不執行乙個父程序資料段、棧和堆的完全複製。作為替代,使用了寫時複製(copy-on-write,cow)技術。這些區域由父、子程序共享,而且核心將它們的訪問許可權改變為唯讀的。如果父、子程序中的任乙個試圖修改這些區域,則核心只為修改區域的那塊記憶體製作乙個副本,通常是虛擬儲存器系統中的一「頁」。

linux 2.4.22提供了另一種新程序建立函式——clone(2)系統呼叫。這是一種fork的泛型,它允許呼叫者控制哪些部分由父、子程序共享。

程式清單8-1中的程式演示了fork函式,從中可以看到子程序對變數所作的改變並不影響父程序中該變數的值。

程式清單8-1 fork函式示例

[root@localhost apue]# cat prog8-1

.c#include

"apue.h

"int glob = 6; /*

external variable in initialized data

*/char buf = "

a write to stdout\n";

intmain(

void

)

else

if(pid == 0) /*

child

*/

else

printf(

"pid = %d, glob = %d, var = %d\n

", getpid(), glob, var

); exit(0);

}

如果執行此程式則得到:

[root@localhost apue]# ./prog8-1

a write to stdout

before fork

pid = 13367, glob = 7, var = 89

子程序的變數值改變了

pid = 13366, glob = 6, var = 88

父程序的變數值沒有改變

[root@localhost apue]# ./prog8-1 > tmp.out

[root@localhost apue]# cat tmp.

outa write to stdout

before fork

pid = 13369, glob = 7, var = 89

before fork

pid = 13368, glob = 6, var = 88

一般來說,在fork之後是父程序先執行還是子程序先執行是不確定的。這取決於核心所使用的排程演算法。如果要求父、子程序之間相互同步,則要求某種形式的程序間通訊。

當寫到標準輸出時,我們將buf長度減去1作為輸出位元組數,這是為了避免將終止null位元組寫出。strlen計算不包括終止null位元組的字串長度,而sizeof則計算包括終止null位元組的緩衝區長度。兩者之間的另乙個差別是,使用strlen需進行一次函式呼叫,而對於sizeof而言,因為緩衝區已用已知字串進行了初始化,其長度是固定的,所以sizeof在編譯時計算緩衝區長度。

注意程式清單8-1中fork與i/o函式之間的互動關係。write函式是不帶緩衝的。因為在fork之前呼叫write,所以其資料寫到標準輸出一次。但是標準i/o庫是帶緩衝的(這裡用到了標準i/o庫的printf函式)。如果標準輸出連到終端裝置,則它是行緩衝的,否則它是全緩衝的。當以互動方式執行該程式時(此時是行緩衝的),只得到該printf輸出的行一次,其原因是標準輸出緩衝區在fork之前已由換行符沖洗。但是當將標準輸出重定向到乙個檔案時(此時是全緩衝的),卻得到printf輸出行兩次。其原因是,在fork之前呼叫了printf一次,但當呼叫fork時,該行資料仍在緩衝區中(我們沒有用fflush沖洗緩衝區),然後在將父程序資料空間複製到子程序中時,該緩衝區也被複製到子程序中於是那時父、子程序各自有了帶該行內容的標準i/o緩衝區。(子程序複製父程序緩衝區對程式的影響例項解析可參考:在exit之前的第二個printf將其資料新增到現有的緩衝區中。當每個程序終止時,最終會沖洗其緩衝區中的副本

對程式清單8-1需注意的另一點是:在重定向父程序的標準輸出時,子程序的標準輸出也被重定向。實際上,fork的乙個特性是父程序的所有開啟檔案描述符都被複製到子程序中。父、子程序的每個相同的開啟描述符共享乙個檔案表項

考慮下述情況,乙個程序具有三個不同的開啟檔案,它們是標準輸入、標準輸出和標準出錯。在從fork返回時,我們有了如圖8-1所示的結構。

這種共享檔案的方式使父、子程序對同一檔案使用了乙個檔案偏移量。如果父、子程序寫到同一描述符檔案,但又沒有任何形式的同步(例如使父程序等待子程序),那麼它們的輸出就會相互混合(假定所有的描述符是在fork之前開啟的)。

在fork之後處理檔案描述符有兩種常見的情況

(1)父程序等待子程序完成。在這種情況下,父程序無需對其描述符做任何處理。當子程序終止後,它曾進行過讀、寫操作的任一共享描述符的檔案偏移量已執行了相應的更新。

(2)父、子程序各自執行不同的程式段。在這種情況下,在fork之後,父、子程序各自關閉它們不需要使用的檔案描述符,這樣就不會干擾對方使用的檔案描述符。這種方法是網路服務程序中經常使用的。

除了開啟檔案之外,父程序的很多其他屬性也由子程序繼承(可以理解為共享),包括:

父、子程序之間的區別是:

使fork失敗的兩個主要原因是:系統中已經有了太多的程序(通常意味著某個方面出了問題),或者該實際使用者id的程序總數超過了系統限制(child_max)。

fork有下面兩種用法:

(1)乙個父程序希望複製自己,使父、子程序同時執行不同的**段。這在網路服務程序中是常見的——父程序等待客戶端的服務請求。當這種請求到達時,父程序呼叫fork,使子程序處理此請求。父程序則繼續等待下乙個服務請求到達。

(2)乙個程序要執行乙個不同的程式。這對shell是常見的情況。在這種情況下,子程序從fork返回後立即呼叫exec。

某些作業系統將(2)中的兩個操作(fork之後執行exec)組合成乙個,並稱其為spawn。unix將這兩個操作分開,因為在很多場合需要單獨使用fork,其後並不跟隨exec。另外,將這兩個操作分開,使得子程序在fork和exec之間可以更改自己的屬性。例如i/o重定向、使用者id、訊號安排等。

本篇博文內容摘自《unix環境高階程式設計》(第二版),僅作個人學習記錄所用。關於本書可參考:

fork程序控制

完整了解unix程序控制是十分重要的。對我們而言,必須熟練掌握的幾個函式 fork,exec系列,exit,wait和waitpid waitpid可以等待乙個特定程序的結束 wait相當於 waitpid 1,status,0 關於fork 如果子程序在父程序之前終止,父程序可以通過wait或者w...

程序控制之exec函式

1.exec函式 include int execl const char pathname,const char arg0,char 0 int execv const char pathname,const char argv int execle const char pathname,con...

程序控制之system函式

1.system函式 include int system const char cmd 如果cmd是乙個空指標,則僅僅當命令處理程式可用時,system返回非0值。因為system在其實現中呼叫了fork,exec和waitpid,因此有三種返回值 1 如果fork失敗或者waitpid返回除ei...