關於准備知識:
每個進程都有以下屬性:
1 地址空間
每個進程都有自己的進程地址空間,格式大概是這個樣子:
棧(Stack)以幀為單位,當程序調用函數(假如該函數名為fun01)時,stack會向下增長一幀,這個幀會存儲該函數的參數、局部變量以及返回地址,計算機將控制權交給fun01,fun01處於激活狀態,這時 Global Data 和 該幀中的局部變量共同構成了context也就是環境上下文。當函數又進一步調用另一個函數的時候,一個新的幀會繼續增加到棧的下方,控制權轉移到新的函數中。當激活函數返回的時候,會從棧中彈出(pop,讀取並從棧中刪除)該幀,並根據幀中記錄的返回地址,將控制權交給返回地址所指向的指令。
2 進程元數據
進程元數據可以用來區分進程,了解進程狀態信息
每一個進程都有PID(進程id),PPID(進程的父進程id),PGID(進程組id)等,這些信息描述了進程的各種信息,但他們並不保存在進程的內存空間中。內核會為每個進程在內核自己的空間中分配一個變量(task_struct結構體)以保存上述信息。內核可以通過查看自己空間中的各個進程的附加信息就能知道進程的概況,而不用進入到進程自身的空間 (就好像我們可以通過門牌就可以知道房間的主人是誰一樣,而不用打開房門)。每個進程的附加信息中有位置專門用於保存接收到的信號。
正題:如何創建進程?
一般情況下:
1 當一個程序調用fork的時候,實際上就是將本進程的內存空間,包括text, global data, heap和stack,又復制出來一個,構成一個新的進程。
子進程的棧、數據以及棧段開始時是父進程內存相應各部分的完全拷貝,因此它們互不影響。從性能方面考慮,父 進程到子進程的數據拷貝並不是創建時就拷貝了的,而是采用了寫時拷貝(copy-on -write)技術來處理。
2 同時在內核中為改進程創建新的附加信息 (比如新的PID,而PPID為原進程的PID)。
3 然后,程序調用exec的時候,進程清空自身內存空間的text, global data, heap和stack,並根據新的程序文件重建text, global data, heap和stack (此時heap和stack大小都為0),並開始運行。
擴展:
子進程在fork出來的時候,使用了寫時復制(COW,Copy-On-Write)方式獲得父進程的數據空間、 堆和棧副本,這其中也包括文件描述符。剛剛fork成功時,父子進程中相同的文件描述符指向系統文件表中的同一項(這也意味着他們共享同一文件偏移量)。這其中當然也包含父進程創建的socket。
接着,一般我們會調用exec執行另一個程序,此時會用全新的程序替換子進程的正文,數據,堆和棧等。此時保存文件描述符的變量當然也不存在了,我們就無法關閉無用的文件描述符了。所以通常我們會fork子進程后在子進程中直接執行close關掉無用的文件描述符,然后再執行exec。
但是在復雜系統中,有時我們fork子進程時已經不知道打開了多少個文件描述符(包括socket句柄等),這此時進行逐一清理確實有很大難度。我們期望的是能在fork子進程前打開某個文件句柄時就指定好:“這個句柄我在fork子進程后執行exec時就關閉”。其實時有這樣的方法的:即所謂 的 close-on-exec 文件描述標志(File Descriptors Flag)
我們要對文件描述符,文件描述符標志,文件狀態標識做一下區分:
文件描述符 File Descriptors
文件描述符是一個標示,非負整數,類似於windows里的句柄,
文件描述標志 File Descriptors Flag(目前就只有一個close-on-exec):
它僅僅是一個標志,當進程fork一個子進程的時候,在子進程中調用了exec函數時就用到了這個標志。意義是執行exec前是否要關閉這個文件描述符。要把文件描述符標志和文件狀態標志區分開來。
文件狀態標志 File Status Flag:
在系統內核維護的系統打開文件表中,每一個系統文件表項都有一個關於write、read等的標志
參考:
本文參考:
http://www.cnblogs.com/stemon/p/5242547.html
http://blog.csdn.net/ljxfblog/article/details/41680115
http://www.cnblogs.com/vamei/archive/2012/10/09/2715388.html