1. 程式人生 > >linux進程原語之fork()(原創!)

linux進程原語之fork()(原創!)

子進程 過去 create 中間 系統調用 鏈表 font 為什麽 什麽是

一.用法解析:

fork()這個函數,可以說是名如其人了,眾所周知fork這個單詞本意為叉子,老外取學術名字的時候總會有一些象形的想法,於是就有了下圖~

技術分享圖片

fork()函數是計算機程序設計中的分叉函數。也就是一個父進程會對應創建一個子進程。

那麽問題來了,我們平常學的函數大多都是只有一個返回值,但fork()特別就特別在調用一次可以產生兩個返回值!

fork()第一次返回的是子進程的ID(在父進程中返回子進程ID)

  第二次返回的是0(在子進程中返回0)

總的來說,fork()可能有三種不同的返回值:
(1) 在父進程中,fork()返回新創建子進程的進程ID;
(2)在子進程中,fork()返回0;
(3)如果出現錯誤,fork()返回一個負值;

引用一位網友的話來解釋pid的值為什麽在父子進程中不同。

“其實就相當於鏈表,進程形成了鏈表,父進程的fpid(p 意味point)指向子進程的進程id, 因為子進程沒有子進程,所以其pid為0.”

那麽fork()是如何返回兩次的呢?

第一步:create(創建子進程)

第二步:clone(相當於父進程對子進程進行初始化,把自己的0-3G用戶空間完全的復制給子進程,下面會詳細說)

    return child_pid;(把自己的信息傳遞給子進程後,返回子進程ID)

接下來我們可以理解成在fork()函數完成一半時,父進程中斷,切換到子進程,子進程接著走完剩下的一半fork()函數,即完成return 0,

這也就解釋了為什麽fork()函數返回子進程ID說明當前進程是父進程,返回0說明當前進程是子進程。

問題又來了,是否在子進程在創建時就進行clone操作呢?

當然不是,當我們發出fork( )系統調用時,內核原樣復制父進程的整個地址空間並把復制的那一份分配給子進程。這種行為是非常耗時的,於是現在的linux提出了

父進程和子進程共享頁面而不是復制頁面,只有在子進程有寫操作的時候,才進行clone操作,即讀時共享,寫時復制原則。具體的可以看這篇文章,大佬說的非常詳細http://www.cnblogs.com/wuchanming/p/4495479.html

那麽在進行父子進程的拷貝的時候,都拷貝哪些東西呢?

技術分享圖片

fork出錯可能有兩種原因:
1)當前的進程數已經達到了系統規定的上限,這時errno的值被設置為EAGAIN。
2)系統內存不足,這時errno的值被設置為ENOMEM。
創建新進程成功後,系統中出現兩個基本完全相同的進程,這兩個進程執行沒有固定的先後順序,哪個進程先執行要看系統的進程調度策略
每個進程都有一個獨特(互不相同)的進程標識符(process ID),可以通過getpid()函數獲得,還有一個記錄父進程pid的變量,可以通過getppid()函數獲得變量的值。

二.練手小題

1.fork()的連續調用:

下面的程序創建出多少進程?

fork();

fork();

fork();

fork();

有人想通過調用printf("+");來統計創建了幾個進程,這是不妥當的。具體原因如下代碼:

 1 #include <unistd.h>  
 2 #include <stdio.h>  
 3 int main() {  
 4     pid_t fpid;//fpid表示fork函數返回的值  
 5     //printf("fork!");  
 6     printf("fork!/n");  
 7     fpid = fork();  
 8     if (fpid < 0)  
 9         printf("error in fork!");  
10     else if (fpid == 0)  
11         printf("I am the child process, my process id is %d/n", getpid());  
12     else  
13         printf("I am the parent process, my process id is %d/n", getpid());  
14     return 0;  
15 }  

執行結果如下:
fork!
I am the parent process, my process id is 3361
I am the child process, my process id is 3362
如果把語句printf("fork!/n");註釋掉,執行printf("fork!");
則新的程序的執行結果是:
fork!I am the parent process, my process id is 3298
fork!I am the child process, my process id is 3299
程序的唯一的區別就在於一個/n回車符號,為什麽結果會相差這麽大呢?
這就跟printf的緩沖機制有關了,printf某些內容時,操作系統僅僅是把該內容放到了stdout的緩沖隊列裏了,並沒有實際的寫到屏幕上。但是,只要看到有/n 則會立即刷新stdout,因此就馬上能夠打印了。
運行了printf("fork!")後,“fork!”僅僅被放到了緩沖裏,程序運行到fork時緩沖裏面的“fork!” 被子進程復制過去了。因此在子進程度stdout緩沖裏面就也有了fork! 。所以,你最終看到的會是fork! 被printf了2次!!!!
而運行printf("fork! /n")後,“fork!”被立即打印到了屏幕上,之後fork到的子進程裏的stdout緩沖裏不會有fork! 內容。因此你看到的結果會是fork! 被printf了1次!!!!
所以說printf("+");不能正確地反應進程的數量。

2.

1 fork();
2 
3 fork()&&fork()||fork();
4 
5 fork();

答案是總共20個進程,除去main進程,還有19個進程。
我們再來仔細分析一下,為什麽是還有19個進程。
第一個fork和最後一個fork肯定是會執行的。
主要在中間3個fork上,可以畫一個圖進行描述。
這裏就需要註意&&和||運算符。
A&&B,如果A=0,就沒有必要繼續執行&&B了;A非0,就需要繼續執行&&B。
A||B,如果A非0,就沒有必要繼續執行||B了,A=0,就需要繼續執行||B。
fork()對於父進程和子進程的返回值是不同的,按照上面的A&&B和A||B的分支進行畫圖,可以得出5個分支。

技術分享圖片

參考資料:

http://blog.csdn.net/jason314/article/details/5640969

linux進程原語之fork()(原創!)