每個程序都有乙個非負整型表示的唯一程序 id。雖說是唯一的,但程序 id 是可復用的,當乙個程序終止時,其程序 id 就成為復用的候選者。多數 unix 系統使用延遲復用演算法,使得賦予新建程序的 id 不同於最近終止程序的 id,以免將新程序誤認為是使用同一 id 的某個已終止的先前程序。
系統中有一些專用程序,但具體細節隨實現而不同。id 為 0 的程序通常是排程程序,常常被稱為交換程序。該程序是核心的一部分,並不執行任何磁碟上的程式,因此也被稱為系統程序。程序 id 1 通常是 init 程序(在 mac os x 10.4 中是 launchd 程序),在自舉過程結束時由核心呼叫,以啟動乙個 unix 系統。該程序的程式檔案一般是 /etc/init 或 /sbin/init,它通常讀取與系統有關的初始化檔案,如 /etc/rc* 檔案、/etc/inittab 檔案 和 /etc/init.d 中的檔案等,並將系統引導到乙個狀態(如多使用者)。init 程序不會終止,它是乙個普通的使用者程序而非核心中的系統程序,但它是以超級使用者特權執行的。此外,每個 unix 系統實現都有它自己的一套提供作業系統服務的核心程序,例如,在某些 unix 的虛擬儲存器實現中,程序 id 2 是頁守護程序,負責支援虛擬儲存器系統的分頁操作。
除了程序 id,每個程序還有其他一些識別符號。下列函式可返回這些識別符號(它們都沒有出錯返回)。
#include
pid_t getpid(void); /* 返回值:呼叫程序的程序 id */
pid_t getppid(void); /* 返回值:呼叫程序的父程序 id */
uid_t getuid(void); /* 返回值:呼叫程序的實際使用者 id */
uid_t geteuid(void); /* 返回值:呼叫程序的有效使用者 id */
gid_t getgid(void); /* 返回值:呼叫程序的實際組 id */
gid_t getegid(void); /* 返回值:呼叫程序的有效組 id */
乙個現有程序可以呼叫 fork 函式建立乙個新的子程序(某些平台提供了 fork 的幾種變體,比如 vfork 以及 linux 3.2.0 提供的 clone 系統呼叫,它允許呼叫者控制哪些部分由父程序和子程序共享)。
#include
pid_t fork(void); /* 返回值:子程序返回 0,父程序返回子程序 id;若出錯,返回 -1 */
fork 函式被呼叫一次,但返回兩次:子程序返回 0,而父程序返回新建子程序的程序 id。子程序和父程序會繼續執行 fork 呼叫之後的指令。子程序是父程序的副本,例如,子程序獲得父程序的資料空間、堆和棧的副本,而不是共享這些儲存空間部分,但子程序和父程序共享正文段。不過由於在 fork 之後經常跟隨著 exec,所以現在很多實現並不執行乙個父程序資料段、堆和棧的完全副本,而是使用了寫時複製(copy-on-write,cow)技術。這些區域由父程序和子程序共享,而且核心將它們的訪問許可權改變為唯讀。如果父程序和子程序中的任乙個試圖修改這些區域,則核心只為修改區域的那塊記憶體製作乙個副本,通常是虛擬儲存系統中的一頁。
下面是乙個 fork 函式使用示例,從中可以看到子程序對變數的修改並不影響父程序。
#include
#include
#include
int globval = 6; // external variable in initialized data.
char buf = "a write to stdout\n";
int main(void)
printf("before fork\n"); // we don't flush stdout
if((pid=fork()) < 0)else if(pid == 0)else
printf("pid=%ld, glob=%d, var=%d\n", (long)getpid(), globval, var);
exit(0);
}
執行結果如下。
$ ./forkdemo.out
a write to stdout
before fork
pid=430, glob=7, var=89 # 子程序的變數值改變了
pid=429, blob=6, var=88
$$ ./forkdemo.out > temp.out
$ cat temp.out
a write to stdout
before fork # 子程序輸出一次
pid=432, blob=7, var=89
before fork # 父程序輸出一次
pid=431, blob=6, var=88
$
一般來說,fork 之後父程序和子程序的執行先後順序是不確定的,這取決於核心所使用的排程演算法。如果要求父程序和子程序之間相互同步,則要求某種形式的程序間通訊。
本程式中需要注意 fork 與 i/o 函式之間的互動關係。由於 write 函式是不帶緩衝的,write 又是在 fork 之前呼叫,所以其資料寫到標準輸出一次。但是標準 i/o 庫是帶緩衝的,如果標準輸出連到終端裝置,則它是行緩衝的;否則它是全緩衝的。所以當以互動方式執行該程式時,只得到該 printf 輸出的行一次,因為標準輸出緩衝區由換行符沖洗。而當將標準輸出重定向到乙個檔案時,卻得到 printf 輸出行兩次。這是因為在 fork 之前呼叫了 printf 一次,但當呼叫 fork 時,該行仍在緩衝區中,然後在將父程序資料空間複製到子程序中時,該緩衝區資料也被複製到子程序中,此時父程序和子程序各自有了該行內容的緩衝區。在 exit 之前的第二個 printf 將其資料追加到已有的緩衝區中。當每個程序終止時,其緩衝區中的內容都被寫到相應檔案中。
另外,還需要注意的是,fork 的乙個特性是父程序的所有開啟檔案描述符都會被複製到子程序中。重要的一點是,父程序和子程序共享同乙個檔案偏移量(具體可參考[url=檔案共享[/url]一節)。所以如果上面程式中若沒有呼叫 sleep() 之類的函式來等待子程序退出的話,它們的輸出就可能是相互混合的(當然這裡呼叫 sleep 其實也不一定能保證)。
除了開啟檔案之外,父程序的其它大部分屬性也由子程序繼承,比如程序組 id、實際組 id、儲存映像和資源限制等。
父程序和子程序的區別主要如下:
* fork 的返回值不同。
* 程序 id 不同。
* 各自的父程序 id 不同。
* 子程序的 tms_utime、tms_stime、tms_cutime 和 tms_ustime 的值設定為 0。
* 子程序不繼承父程序設定的檔案鎖。
* 子程序的未處理鬧鐘被清除。
* 子程序的未處理訊號集設定為空集。
一般使 fork 失敗的兩個主要原因是:(a)系統中已經有了太多的程序,(b)該實際使用者 id 的程序總數超過了限制。
程序的狀態與識別符號
核心將所有程序存放在雙向迴圈鍊錶 程序鍊錶 中,鍊錶的節點都是task struct,稱為程序控制塊的結構。該結構包含了與乙個程序相關的所有資訊,如程序的狀態 程序的基本資訊 程序識別符號 記憶體相關資訊 父程序相關資訊 與程序相關的終端資訊 當前工作目錄 開啟的檔案資訊 所接收的訊號資訊等。下面將...
Scala《識別符號》
val scala.math.sqrt 2 val val 42 println val 1 to10 等價於 1.to 10 1 10 等價於 1 10 1 tostring 等價於 1.tostring val a 42 a等價於 a.unary a 1 等價於 a a 1 構造列表list 1...
python 識別符號
在 python 裡,識別符號由字母 數字 下劃線組成。在 python 中,所有識別符號可以包括英文 數字以及下劃線 但不能以數字開頭。python 中的識別符號是區分大小寫的。以下劃線開頭的識別符號是有特殊意義的。以單下劃線開頭 foo 的代表不能直接訪問的類屬性,需通過類提供的介面進行訪問,不...