0前言
上周都在看都在學習unix環境進階編程的第八章——進程式控制制。也就是這一章中,讓我理解了unix中一些進程的原理。下面我就主要按照進程中最重要的三個函數來進行講解。讓大家通過閱讀這一篇文章徹底明白進程這點事。希望對大家有所協助。
1進程環境
在學習進程之前,一定要瞭解一下unix的進程環境。系統如何對進程終止,和一個程式啟動終止,程式啟動並執行原理等,這些都有助於你理解進程的運行原理。這些內容都在我的上一篇文章中,請關註:http://blog.csdn.net/wallwind/article/details/6968323。文章中講的較為詳細。
2進程概念:
一個進程,主要包含三個元素:
a) 一個可以執行的程式;
b) 和該進程相關聯的全部資料(包括變數,記憶體空間,緩衝區等等);
c) 程式的執行內容(execution context)。
不妨簡單理解為,一個進程表示的,就是一個可執行程式的一次執行過程中的一個狀態。作業系統對進程的管理,典型的情況,是通過進程表完成的。進程表中的每一個表項,記錄的是當前作業系統中一個進程的情況。對於單 CPU的情況而言,每一特定時刻只有一個進程佔用 CPU,但是系統中可能同時存在多個活動的(等待執行或繼續執行的)進程。
3 fork()函數
fork()函數是進程的核心函數。當調用fork的時候,系統將建立一個新的進程,成為子進程(child process)。Fork函數定義形式如下:
#include<unistd.h>Pid_t fork(void);//傳回值:子進程返回0,父進程中返回子進程ID,出錯則返回-1
從傳回值我們可以看到,Fork函數調用了一次,但是返回兩次。其區別在於在子進程中傳回值是0,而父進程的傳回值則是新子進程的進程ID.
上邊的概念可能對初學者比較模糊,那麼我們怎麼理解fork呢?
當你看到fork的時候,你可以把fork理解成“分叉”,在分叉的同時,產生的一個子進程複製了父進程的基本是所有的東西,包括代碼、資料和分配給進程的資源。也就是子進程幾乎是和父進程是一模一樣的。但是子進程可能會根據不同情況調用其他函數。比如exec函數。
下面我們用一個經典及比較典型簡單的例子來看看。
#include"apue.h"int glob=6;char buf[]="a write to stdout\n"; int main(void){ int var=88; pid_t pid; if(write(STDOUT_FILENO,buf,sizeof(buf)-1)!=sizeof(buf)-1) printf("writeerror"); printf("before fork with 分行符號\n"); printf("before fork without分行符號"); //printf("\n"); if((pid=fork())<0){ printf("fork error"); }else if(pid==0){ printf("I am is child process,pid=%d\n",getpid()); printf("my parentprocess's pid=%d\n",getppid()); glob++; var++; }else{ printf("this is parentprocess,pid=%d\n",getpid()); //var++; } printf("pid=%d,glob=%d,var=%d\n",getpid(),glob,var); exit(0);}
輸出結果:
希望新手能夠將以上代碼自己敲一遍,然後自己運行一遍。
從上邊的結果我們一步步分析。首先我們看到兩行文字是只輸出一遍的。
既”a write to stdout”
“Befork forkwhit h分行符號”。
也就是只有一個進程執行這兩個輸出語句。我們先暫且不分析不帶分行符號的。
當程式調用fork的時候,這個時候程式就出現了一個進程,也就是兩個進程在執行。
請看下邊的簡易圖形
在上邊我們已經講過fork函數會因為不同調用返回不同的函數值:
1)在父進程中,fork返回新建立子進程的進程ID;
2)在子進程中,fork返回0;
3)如果出現錯誤,fork返回一個負值;
現在我們看程式裡的幾個函數。Write函數是將buf內容寫到標準流中。此處只作為輸出用。
我們關心的是getpid()函數和getppid()函數。
getpid()函數是用來輸出當前成進程的ID,getppid()則得到當前進程的父進程的ID,從輸出結果比對可以看到,子進程得到的父ID和父進程得到自己的ID是相同的。
再來看一下全域變數glob,glob是函數外的變數。子進程將glob++處理,這個時候輸出的是7,父進程沒有進行處理直接輸出6。我們可以知道,這兩個glob變數顯示是兩份的。不相干的。
局部變數var也是同樣的結果。。。。
現在我們來解釋一下為什麼
printf("before fork with 分行符號\n");printf("before fork without分行符號");
上一句只輸出了一次,而下邊這句話就輸出了兩次。
我們可以發現printf("before fork with 分行符號\n");這句帶有\n既分行符號的意思。而
printf("before fork without分行符號");
是沒有帶的。
這個我們要知道printf函數是有緩衝機制的,類似於我們使用的write函數,但我們將想要的東西輸出的時候,系統僅僅是把內容放到stdout標準輸出的緩衝隊列的。當遇到“\n”的時候,系統就把緩衝裡的東西給清掉,輸出到螢幕上。
printf("beforefork with 分行符號\n");
執行後,緩衝裡沒有了資料,自然子進程再次執行的時候沒有內容可輸出了。但是printf("before fork without分行符號");的時候,子進程也會把stdout的內容再次輸出來。也就是導致了內容出處了兩邊。如果換一下書序的,結果是不一樣的哦。
3 fork難度進階
csdn看到一篇比較好的講解fork的文章。較為深入的講解。下面我就經過自己的調試和理解將呈現給大家。首先看一段代碼:
#include "apue.h" int main(void){ int i=0; pid_t pid; for(i=0;i<2;i++) { if((pid=fork())<0){ printf("forkerror\n"); }else if(pid==0){ printf("%d,childself's pid=%d,parent's pid=%d,returnid=%d\n",i,getpid(),getppid(),pid); }else{ printf("%d,parentself's pid=%d,parent's father's pid=%d,returnid=%d\n",i,getpid(),getppid(),pid); sleep(2);//inorder tochild output first } } exit(0);}
輸出的結果為:
好好的分析一下我們的結果。0開始的輸出為2行,1開頭的輸出4行。
然後我們開始觀察PID的結果。從pid的結果我們找到一個關係
既9278-》30361-》30362-》30363
9278-》30361-》30364
要知道,fork函數子進程調用的時候返回0,而父進程調用的時候則返回子進程的ID。
程式運行第一步:
if((pid=fork())<0){ printf("forkerror\n"); }else if(pid==0){ printf("%d,childself's pid=%d,parent's pid=%d,returnid=%d\n",i,getpid(),getppid(),pid); }else{ printf("%d,parentself's pid=%d,parent's father's pid=%d,returnid=%d\n",i,getpid(),getppid(),pid); sleep(2);//inorder tochild output first }
輸出的結果為:
這是第一次迴圈輸出輸出的。
Childpid和return 均為父ID為30361產生的30361。
此時已經有了兩個進程了。
進程ID分別為30362和30361。
第二次迴圈輸出。
我們知道了此時兩個進程30362和30361。這兩個進程分別執行自己的代碼。
ü 這個時候pid為30362的進程進行一次:
迴圈,得到結果為:
產生了一個子進程30363
ü 30361進程再次執行自己的代碼,也就是上述迴圈的部分。
產生了一個子進程30364.
從程式和輸出結果可以看到,其實我們的程式得到了6份的拷貝。
通過這個例子,fork了三次,產生了三個子進程,輸出了6次,你可以深刻的理解了fork工作原理了。
再來一個例子:
這個是典型的迴圈例子:
#include <unistd.h>#include<stdio.h> int main(void){ pid_t pid; int i=0; int c_cout=0; int p_cout=0; for(i=0;i<5;i++) { if((pid=fork())<0){ printf("forkerror\n"); }else if(pid==0){ c_cout++; }else{ p_cout++; } } printf("c_cout=%d,p_cout=%d,pid=%d\n",c_cout,p_cout,getpid()); }
輸出結果為:c_cout=5,p_cout=0,pid=1559
c_cout=4,p_cout=1,pid=1558
c_cout=4,p_cout=1,pid=1560
c_cout=3,p_cout=2,pid=1557
c_cout=4,p_cout=1,pid=1562
c_cout=3,p_cout=2,pid=1561
c_cout=4,p_cout=1,pid=1566
c_cout=3,p_cout=2,pid=1565
c_cout=3,p_cout=2,pid=1569
c_cout=2,p_cout=3,pid=1568
c_cout=4,p_cout=1,pid=1574
c_cout=3,p_cout=2,pid=1573
c_cout=3,p_cout=2,pid=1577
c_cout=2,p_cout=3,pid=1576
c_cout=3,p_cout=2,pid=1581
c_cout=2,p_cout=3,pid=1580
c_cout=2,p_cout=3,pid=1584
c_cout=1,p_cout=4,pid=1583
c_cout=3,p_cout=2,pid=1563
c_cout=2,p_cout=3,pid=1556
c_cout=2,p_cout=3,pid=1564
c_cout=2,p_cout=3,pid=1570
c_cout=1,p_cout=4,pid=1555
c_cout=3,p_cout=2,pid=1575
c_cout=2,p_cout=3,pid=1572
c_cout=2,p_cout=3,pid=1578
c_cout=1,p_cout=4,pid=1571
c_cout=2,p_cout=3,pid=1582
c_cout=1,p_cout=4,pid=1579
c_cout=1,p_cout=4,pid=1585
c_cout=0,p_cout=5,pid=1554
通過推理:引用網路文章可得到:
設f(n)表示程式中迴圈會執行n次時整個程式會產生的進程數,很容易得到遞推公式:
f(n)=1+f(n-1)+f(n-2)+f(n-3)+…+f(0)
比如for i=0;i<N;I++< p>
因為i=0時fork()的子進程下次會繼續迴圈n-1次,i=1時 fork()的子進程下次會僅需迴圈n-2 次。。。。
其中常數1是進程本身。
邊界條件,f(0)=1
這樣,我們就得到了問題的答案:
f(n)=1+f(n-1)+f(n-2)+…+f(0)
f(0)=1
這個可以求出閉形式:
f(0)=1
f(1)=2
f(2)=4
…
用數學歸納法可以得到f(n)=2^n
所以對於程式一,會列印出2^5-1=31行資訊。
對於程式二,總共會產生2^5=32個進程。
不過,我還是不知道為什麼兩個變數的結果怎麼是那種形式的輸出,求指導啊!!!
下面貼一下比較有意思的一段代碼:
#include <stdio.h> #include <unistd.h> int main(int argc, char* argv[]) { fork(); fork() && fork() || fork(); fork(); return 0; }
我們要知道&& 和||運算子。&&有個短路現象。
A&&B,如果A=0,就沒有必要繼續執行&&B了;A非0,就需要繼續執行&&B。
A||B,如果A非0,就沒有必要繼續執行||B了,A=0,就需要繼續執行||B。
通過畫圖我們可以得到如下
加上前面的fork和最後的fork,總共4*5=20個進程,除去main主進程,就是19個進程了。
更多文章,歡迎關注http://blog.csdn.net/wallwind 著作權歸博主所有~~禁止商業用途