本文轉(zhuǎn)載自實(shí)驗(yàn)樓:多進(jìn)程(一)
概述
進(jìn)程的概念這里就不再過(guò)多的贅述了,市面上幾乎關(guān)于計(jì)算機(jī)操作系統(tǒng)的書(shū)都有詳細(xì)的描述。 在基本的概念里我們學(xué)習(xí)一下Linux進(jìn)程狀態(tài):
R (TASK_RUNNING),可執(zhí)行狀態(tài)。
只有在該狀態(tài)的進(jìn)程才可能在CPU上運(yùn)行。而同一時(shí)刻可能有多個(gè)進(jìn)程處于可執(zhí)行狀態(tài),這些進(jìn)程的task_struct結(jié)構(gòu)(進(jìn)程控制塊)被放入對(duì)應(yīng)CPU的可執(zhí)行隊(duì)列中(一個(gè)進(jìn)程最多只能出現(xiàn)在一個(gè)CPU的可執(zhí)行隊(duì)列中)。進(jìn)程調(diào)度器的任務(wù)就是從各個(gè)CPU的可執(zhí)行隊(duì)列中分別選擇一個(gè)進(jìn)程在該CPU上運(yùn)行。
很多操作系統(tǒng)教科書(shū)將正在CPU上執(zhí)行的進(jìn)程定義為RUNNING狀態(tài)、而將可執(zhí)行但是尚未被調(diào)度執(zhí)行的進(jìn)程定義為READY狀態(tài),這兩種狀態(tài)在linux下統(tǒng)一為 TASK_RUNNING狀態(tài)。
S (TASK_INTERRUPTIBLE),可中斷的睡眠狀態(tài)。
處于這個(gè)狀態(tài)的進(jìn)程因?yàn)榈却衬呈录陌l(fā)生(比如等待socket連接、等待信號(hào)量),而被掛起。這些進(jìn)程的task_struct結(jié)構(gòu)被放入對(duì)應(yīng)事件的等待隊(duì)列中。當(dāng)這些事件發(fā)生時(shí)(由外部中斷觸發(fā)、或由其他進(jìn)程觸發(fā)),對(duì)應(yīng)的等待隊(duì)列中的一個(gè)或多個(gè)進(jìn)程將被喚醒。
通過(guò)ps命令我們會(huì)看到,一般情況下,進(jìn)程列表中的絕大多數(shù)進(jìn)程都處于TASK_INTERRUPTIBLE狀態(tài)(除非機(jī)器的負(fù)載很高)。畢竟CPU就這么一兩個(gè),進(jìn)程動(dòng)輒幾十上百個(gè),如果不是絕大多數(shù)進(jìn)程都在睡眠,CPU又怎么響應(yīng)得過(guò)來(lái)。
D (TASK_UNINTERRUPTIBLE),不可中斷的睡眠狀態(tài)。
與TASK_INTERRUPTIBLE狀態(tài)類(lèi)似,進(jìn)程處于睡眠狀態(tài),但是此刻進(jìn)程是不可中斷的。不可中斷,指的并不是CPU不響應(yīng)外部硬件的中斷,而是指進(jìn)程不響應(yīng)異步信號(hào)。 絕大多數(shù)情況下,進(jìn)程處在睡眠狀態(tài)時(shí),總是應(yīng)該能夠響應(yīng)異步信號(hào)的。否則你將驚奇的發(fā)現(xiàn),kill -9竟然殺不死一個(gè)正在睡眠的進(jìn)程了!于是我們也很好理解,為什么ps命令看到的進(jìn)程幾乎不會(huì)出現(xiàn)TASK_UNINTERRUPTIBLE狀態(tài),而總是TASK_INTERRUPTIBLE狀態(tài)。
而TASK_UNINTERRUPTIBLE狀態(tài)存在的意義就在于,內(nèi)核的某些處理流程是不能被打斷的。如果響應(yīng)異步信號(hào),程序的執(zhí)行流程中就會(huì)被插入一段用于處理異步信號(hào)的流程(這個(gè)插入的流程可能只存在于內(nèi)核態(tài),也可能延伸到用戶態(tài)),于是原有的流程就被中斷了。(參見(jiàn)《linux內(nèi)核異步中斷淺析》) 在進(jìn)程對(duì)某些硬件進(jìn)行操作時(shí)(比如進(jìn)程調(diào)用read系統(tǒng)調(diào)用對(duì)某個(gè)設(shè)備文件進(jìn)行讀操作,而read系統(tǒng)調(diào)用最終執(zhí)行到對(duì)應(yīng)設(shè)備驅(qū)動(dòng)的代碼,并與對(duì)應(yīng)的物理設(shè)備進(jìn)行交互),可能需要使用TASK_UNINTERRUPTIBLE狀態(tài)對(duì)進(jìn)程進(jìn)行保護(hù),以避免進(jìn)程與設(shè)備交互的過(guò)程被打斷,造成設(shè)備陷入不可控的狀態(tài)。這種情況下的TASK_UNINTERRUPTIBLE狀態(tài)總是非常短暫的,通過(guò)ps命令基本上不可能捕捉到。
linux系統(tǒng)中也存在容易捕捉的TASK_UNINTERRUPTIBLE狀態(tài)。執(zhí)行vfork系統(tǒng)調(diào)用后,父進(jìn)程將進(jìn)入TASK_UNINTERRUPTIBLE狀態(tài),直到子進(jìn)程調(diào)用exit或exec(參見(jiàn)《神奇的vfork》)。 通過(guò)下面的代碼就能得到處于TASK_UNINTERRUPTIBLE狀態(tài)的進(jìn)程:
$ ps -ax | grep a\.out
4371 pts/0 D+ 0:00 ./a.out
4372 pts/0 S+ 0:00 ./a.out
4374 pts/1 S+ 0:00 grep a.out
然后我們可以試驗(yàn)一下TASK_UNINTERRUPTIBLE狀態(tài)的威力。不管kill還是kill -9,這個(gè)TASK_UNINTERRUPTIBLE狀態(tài)的父進(jìn)程依然屹立不倒。
T (TASK_STOPPED or TASK_TRACED),暫停狀態(tài)或跟蹤狀態(tài)。
向進(jìn)程發(fā)送一個(gè)SIGSTOP信號(hào),它就會(huì)因響應(yīng)該信號(hào)而進(jìn)入TASK_STOPPED狀態(tài)(除非該進(jìn)程本身處于TASK_UNINTERRUPTIBLE狀態(tài)而不響應(yīng)信號(hào))。(SIGSTOP與SIGKILL信號(hào)一樣,是非常強(qiáng)制的。不允許用戶進(jìn)程通過(guò)signal系列的系統(tǒng)調(diào)用重新設(shè)置對(duì)應(yīng)的信號(hào)處理函數(shù)。) 向進(jìn)程發(fā)送一個(gè)SIGCONT信號(hào),可以讓其從TASK_STOPPED狀態(tài)恢復(fù)到TASK_RUNNING狀態(tài)。
當(dāng)進(jìn)程正在被跟蹤時(shí),它處于TASK_TRACED這個(gè)特殊的狀態(tài)?!罢诒桓櫋敝傅氖沁M(jìn)程暫停下來(lái),等待跟蹤它的進(jìn)程對(duì)它進(jìn)行操作。比如在gdb中對(duì)被跟蹤的進(jìn)程下一個(gè)斷點(diǎn),進(jìn)程在斷點(diǎn)處停下來(lái)的時(shí)候就處于TASK_TRACED狀態(tài)。而在其他時(shí)候,被跟蹤的進(jìn)程還是處于前面提到的那些狀態(tài)。
對(duì)于進(jìn)程本身來(lái)說(shuō),TASK_STOPPED和TASK_TRACED狀態(tài)很類(lèi)似,都是表示進(jìn)程暫停下來(lái)。 而TASK_TRACED狀態(tài)相當(dāng)于在TASK_STOPPED之上多了一層保護(hù),處于TASK_TRACED狀態(tài)的進(jìn)程不能響應(yīng)SIGCONT信號(hào)而被喚醒。只能等到調(diào)試進(jìn)程通過(guò)ptrace系統(tǒng)調(diào)用執(zhí)行PTRACE_CONT、PTRACE_DETACH等操作(通過(guò)ptrace系統(tǒng)調(diào)用的參數(shù)指定操作),或調(diào)試進(jìn)程退出,被調(diào)試的進(jìn)程才能恢復(fù)TASK_RUNNING狀態(tài)。
Z (TASK_DEAD – EXIT_ZOMBIE),退出狀態(tài),進(jìn)程成為僵尸進(jìn)程。
進(jìn)程在退出的過(guò)程中,處于TASK_DEAD狀態(tài)。
在這個(gè)退出過(guò)程中,進(jìn)程占有的所有資源將被回收,除了task_struct結(jié)構(gòu)(以及少數(shù)資源)以外。于是進(jìn)程就只剩下task_struct這么個(gè)空殼,故稱(chēng)為僵尸。 之所以保留task_struct,是因?yàn)閠ask_struct里面保存了進(jìn)程的退出碼、以及一些統(tǒng)計(jì)信息。而其父進(jìn)程很可能會(huì)關(guān)心這些信息。比如在shell中,$?變量就保存了最后一個(gè)退出的前臺(tái)進(jìn)程的退出碼,而這個(gè)退出碼往往被作為if語(yǔ)句的判斷條件。 當(dāng)然,內(nèi)核也可以將這些信息保存在別的地方,而將task_struct結(jié)構(gòu)釋放掉,以節(jié)省一些空間。但是使用task_struct結(jié)構(gòu)更為方便,因?yàn)樵趦?nèi)核中已經(jīng)建立了從pid到task_struct查找關(guān)系,還有進(jìn)程間的父子關(guān)系。釋放掉task_struct,則需要建立一些新的數(shù)據(jù)結(jié)構(gòu),以便讓父進(jìn)程找到它的子進(jìn)程的退出信息。
父進(jìn)程可以通過(guò)wait系列的系統(tǒng)調(diào)用(如wait4、waitid)來(lái)等待某個(gè)或某些子進(jìn)程的退出,并獲取它的退出信息。然后wait系列的系統(tǒng)調(diào)用會(huì)順便將子進(jìn)程的尸體(task_struct)也釋放掉。 子進(jìn)程在退出的過(guò)程中,內(nèi)核會(huì)給其父進(jìn)程發(fā)送一個(gè)信號(hào),通知父進(jìn)程來(lái)“收尸”。這個(gè)信號(hào)默認(rèn)是SIGCHLD,但是在通過(guò)clone系統(tǒng)調(diào)用創(chuàng)建子進(jìn)程時(shí),可以設(shè)置這個(gè)信號(hào)。
$ ps -ax | grep a.out10410 pts/0 S+ 0:00 ./a.out10411 pts/0 Z+ 0:00 [a.out]0413 pts/1 S+ 0:00 grep a.out
只要父進(jìn)程不退出,這個(gè)僵尸狀態(tài)的子進(jìn)程就一直存在。那么如果父進(jìn)程退出了呢,誰(shuí)又來(lái)給子進(jìn)程“收尸”? 當(dāng)進(jìn)程退出的時(shí)候,會(huì)將它的所有子進(jìn)程都托管給別的進(jìn)程(使之成為別的進(jìn)程的子進(jìn)程)。托管給誰(shuí)呢?可能是退出進(jìn)程所在進(jìn)程組的下一個(gè)進(jìn)程(如果存在的話),或者是1號(hào)進(jìn)程。所以每個(gè)進(jìn)程、每時(shí)每刻都有父進(jìn)程存在。除非它是1號(hào)進(jìn)程。
1號(hào)進(jìn)程,pid為1的進(jìn)程,又稱(chēng)init進(jìn)程。 linux系統(tǒng)啟動(dòng)后,第一個(gè)被創(chuàng)建的用戶態(tài)進(jìn)程就是init進(jìn)程。它有兩項(xiàng)使命: 1、執(zhí)行系統(tǒng)初始化腳本,創(chuàng)建一系列的進(jìn)程(它們都是init進(jìn)程的子孫); 2、在一個(gè)死循環(huán)中等待其子進(jìn)程的退出事件,并調(diào)用waitid系統(tǒng)調(diào)用來(lái)完成“收尸”工作; init進(jìn)程不會(huì)被暫停、也不會(huì)被殺死(這是由內(nèi)核來(lái)保證的)。它在等待子進(jìn)程退出的過(guò)程中處于TASK_INTERRUPTIBLE狀態(tài),“收尸”過(guò)程中則處于TASK_RUNNING狀態(tài)。
X (TASK_DEAD – EXIT_DEAD),退出狀態(tài),進(jìn)程即將被銷(xiāo)毀。
而進(jìn)程在退出過(guò)程中也可能不會(huì)保留它的task_struct。比如這個(gè)進(jìn)程是多線程程序中被detach過(guò)的進(jìn)程(進(jìn)程?線程?參見(jiàn)《linux線程淺析》)?;蛘吒高M(jìn)程通過(guò)設(shè)置SIGCHLD信號(hào)的handler為SIG_IGN,顯式的忽略了SIGCHLD信號(hào)。(這是posix的規(guī)定,盡管子進(jìn)程的退出信號(hào)可以被設(shè)置為SIGCHLD以外的其他信號(hào)。) 此時(shí),進(jìn)程將被置于EXIT_DEAD退出狀態(tài),這意味著接下來(lái)的代碼立即就會(huì)將該進(jìn)程徹底釋放。所以EXIT_DEAD狀態(tài)是非常短暫的,幾乎不可能通過(guò)ps命令捕捉到。
以上內(nèi)容均摘自博文:http://blog.csdn.net/huzia/article/details/18946491
進(jìn)程標(biāo)識(shí)
獲取進(jìn)程標(biāo)志號(hào)(pid)的API,主要有兩個(gè)函數(shù):getpid和getppid
需要包含的頭文件:
- <sys/types.h>
- <unistd.h>
函數(shù)原型:pid_t getpid(void) 功能:獲取當(dāng)前進(jìn)程ID 返回值:調(diào)用進(jìn)程的進(jìn)程ID
函數(shù)原型:pid_t getppid(void) 功能:獲取父進(jìn)程ID 返回值:調(diào)用進(jìn)程的父進(jìn)程ID
Linux 下 C 進(jìn)程內(nèi)存布局
1.C 進(jìn)程內(nèi)存布局說(shuō)明
text:代碼段。存放的是程序的全部代碼(指令),來(lái)源于二進(jìn)制可執(zhí)行文件中的代碼部分
initialized data(簡(jiǎn)稱(chēng)data段)和uninitialized data(簡(jiǎn)稱(chēng)bss段)組成了數(shù)據(jù)段。
其中data段存放的是已初始化全局變量和已初始化static局部變量,來(lái)源于二進(jìn)制可執(zhí)行文件中的數(shù)據(jù)部分;bss段存放的是未初始化全局變量和未初始化static局部變量,其內(nèi)容不來(lái)源于二進(jìn)制可執(zhí)行文件中的數(shù)據(jù)部分(也就是說(shuō):二進(jìn)制可執(zhí)行文件中的數(shù)據(jù)部分沒(méi)有未初始化全局變量和未初始化static局部變量)。根據(jù)C語(yǔ)言標(biāo)準(zhǔn)規(guī)定,他們的初始值必須為0,因此bss段存放的是全0。將bss段清0的工作是由系統(tǒng)在加載二進(jìn)制文件后,開(kāi)始執(zhí)行程序前完成的,系統(tǒng)執(zhí)行這個(gè)清0操作是由內(nèi)核的一段代碼完成的,這段代碼就是即將介紹的exec系統(tǒng)調(diào)用。至于exec從內(nèi)存什么地方開(kāi)始清0以及要清0多少空間,則是由記錄在二進(jìn)制可執(zhí)行文件中的信息決定的(即:二進(jìn)制文件中記錄了text、data、bss段的大?。?br>
malloc是從heap(堆)中分配空間的。
stack(棧)存放的是動(dòng)態(tài)局部變量。
當(dāng)子函數(shù)被調(diào)用時(shí),系統(tǒng)會(huì)從棧中分配空間給該子函數(shù)的動(dòng)態(tài)局部變量(注意:此時(shí)棧向內(nèi)存低地址延伸);當(dāng)子函數(shù)返回時(shí),系統(tǒng)的棧會(huì)向內(nèi)存高地址延伸,這相當(dāng)于釋放子函數(shù)的動(dòng)態(tài)局部變量的內(nèi)存空間。我們假設(shè)一下,main函數(shù)在調(diào)用子函數(shù)A后立即調(diào)用子函數(shù)B,那么子函數(shù)B的動(dòng)態(tài)局部變量會(huì)覆蓋原來(lái)子函數(shù)A的動(dòng)態(tài)局部變量的存儲(chǔ)空間,這就是子函數(shù)不能互相訪問(wèn)對(duì)方動(dòng)態(tài)局部變量的根本物理原因。
內(nèi)存的最高端存放的是命令行參數(shù)和環(huán)境變量,將命令行參數(shù)和環(huán)境變量放到指定位置這個(gè)操作是由OS的一段代碼(exec系統(tǒng)調(diào)用)在加載二進(jìn)制文件到內(nèi)存后,開(kāi)始運(yùn)行程序前完成的。
Linux下C進(jìn)程內(nèi)存布局可以由下面的程序的運(yùn)行結(jié)果來(lái)獲得驗(yàn)證:
//memery.c
1 #include <stdio.h>
2 #include <stdlib.h>
3
4 int global_init_val = 100;
5 int global_noninit_val;
6 extern char **environ;
7
8 int main(int argc, char *argv[], char *envp[])
9 {
10 static int localstaticval = 10;
11 char *localval;
12 localval = malloc(10);
13 printf("address of text is : %p\n", main);
14 printf("address of data is : %p, %p\n", &global_init_val, &localstaticval);
15 printf("address of bss is : %p\n", &global_noninit_val);
16 printf("address of heap is : %p\n", localval);
17 printf("address of stack is : %p\n", &localval);
18 free(localval);
19
20 printf("&environ = %p, environ = %p\n", &envp, envp);
21 printf("&argv = %p, argv = %p\n", &argv, argv);
22 return 0;
23 }
運(yùn)行結(jié)果,如下:
1 address of text is : 0x8048454
2 address of data is : 0x804a01c, 0x804a020
3 address of bss is : 0x804a02c
4 address of heap is : 0x96e1008
5 address of stack is : 0xbffca8bc
6 &environ = 0xbffca8d8, environ = 0xbffca97c
7 &argv = 0xbffca8d4, argv = 0xbffca974
運(yùn)行結(jié)果分析: 運(yùn)行結(jié)果的第1(2、3、4、5、6、7)行是由程序的第13(14、15、16、17、20、21)行打印的。 由運(yùn)行結(jié)果的第1、2、3、4行可知,存放的是程序代碼的text段位于進(jìn)程地址空間的最低端;往上是存放已初始化全局變量和已初始化static局部變量的data段;往上是存放未初始化全局變量的bss段;往上是堆區(qū)(heap)。 由運(yùn)行結(jié)果的第7、6、5行可知,命令行參數(shù)和環(huán)境變量存放在進(jìn)程地址空間的最高端;往下是存放動(dòng)態(tài)局部變量的棧區(qū)(stack)。
- 環(huán)境變量的獲取與設(shè)置
壞境變量在內(nèi)存中通常是一字符串環(huán)境變量名=環(huán)境變量值的形式存放。對(duì)壞境變量含義的急事依賴于具體的應(yīng)用程序。我們的程序可能會(huì)調(diào)用Linux系統(tǒng)的環(huán)境變量,甚至修改環(huán)境變量,所以,Linux向我們提供了這種API。
需要包含的頭文件:<stdlib.h>
函數(shù)原型: char * getenc(const char * name) 返回字符指針,該指針指向變量名為name的環(huán)境變量的值字符串。
int putenv(const char * str) 將“環(huán)境變量=環(huán)境變量值”形式的字符創(chuàng)增加到環(huán)境變量列表中;如果該環(huán)境變量已存在,則更新已有的值。
int setenv(const char * name, const char * value, int rewrite) 設(shè)置名字為name的環(huán)境變量的值為value;如果該環(huán)境變量已存在,且rewrite不為0,用新值替換舊值;rewrite為0,就不做任何事。
//env.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char *argv[], char *envp[])
{
char **ptr;
for (ptr = envp; *ptr != 0; ptr++) /* and all env strings */
printf ("%s\n", *ptr);
printf ("\n\n--------My environment variable-------\n\n");
printf ("USERNAME is %s\n", getenv("USERNAME"));
putenv ("USERNAME=shiyanlou");
printf ("USERNAME is %s\n", getenv("USERNAME"));
setenv ("USERNAME", "shiyanlou-2", 0);
printf ("USERNAME is %s\n", getenv("USERNAME"));
setenv ("USERNAME", "shiyanlou-2", 1);
printf ("USERNAME is %s\n", getenv("USERNAME"));
return 0;
}
進(jìn)程控制 -- fork
1.fork 的機(jī)制與特性
父進(jìn)程調(diào)用fork將會(huì)產(chǎn)生一個(gè)子進(jìn)程。此時(shí)會(huì)有2個(gè)問(wèn)題:
子進(jìn)程的代碼從哪里來(lái)?
子進(jìn)程首次被OS調(diào)度時(shí),執(zhí)行的第1條代碼是哪條代碼?
子進(jìn)程的代碼是父進(jìn)程代碼的一個(gè)完全相同拷貝。事實(shí)上不僅僅是text段,子進(jìn)程的全部進(jìn)程空間(包括:text/data/bss/heap/stack/command line/environment variables)都是父進(jìn)程空間的一個(gè)完全拷貝。 下一個(gè)問(wèn)題是:誰(shuí)為子進(jìn)程分配了內(nèi)存空間?誰(shuí)拷貝了父進(jìn)程空間的內(nèi)容到子進(jìn)程的內(nèi)存空間?fork當(dāng)仁不讓?zhuān)∈聦?shí)上,查看fork實(shí)現(xiàn)的源代碼,由4部分工作組成:首先,為子進(jìn)程分配內(nèi)存空間;然后,將父進(jìn)程空間的全部?jī)?nèi)容拷貝到分配給子進(jìn)程的內(nèi)存空間;然后在內(nèi)核數(shù)據(jù)結(jié)構(gòu)中創(chuàng)建并正確初始化子進(jìn)程的PCB(包括2個(gè)重要信息:子進(jìn)程pid,PC的值=善后代碼的第1條指令地址);最后是一段善后代碼。 由于子進(jìn)程的PCB已經(jīng)產(chǎn)生,所以子進(jìn)程已經(jīng)出生,因此子進(jìn)程就可以被OS調(diào)度到來(lái)運(yùn)行。子進(jìn)程首次被OS調(diào)度時(shí),執(zhí)行的第1條代碼在fork內(nèi)部,不過(guò)從應(yīng)用程序的角度來(lái)看,子進(jìn)程首次被OS調(diào)度時(shí),執(zhí)行的第1條代碼是從fork返回。這就導(dǎo)致了fork被調(diào)用1次,卻返回2次:父、子進(jìn)程中各返回1次。對(duì)于應(yīng)用程序員而言,最重要的是fork的2次返回值不一樣,父進(jìn)程返回值是子進(jìn)程的pid,子進(jìn)程的返回值是0。 至于子進(jìn)程產(chǎn)生后,父、子進(jìn)程誰(shuí)先運(yùn)行,取決于OS調(diào)度策略,應(yīng)用程序員無(wú)法控制。 以上分析了fork的內(nèi)部實(shí)現(xiàn)以及對(duì)應(yīng)用程序的影響。如果應(yīng)用程序員覺(jué)得難以理解的話,可以暫時(shí)拋開(kāi),只要記住3個(gè)結(jié)論即可:
fork函數(shù)被調(diào)用1次(在父進(jìn)程中被調(diào)用),但返回2次(父、子進(jìn)程中各返回一次)。兩次返回的區(qū)別是子進(jìn)程的返回值是0,而父進(jìn)程的返回值則是子進(jìn)程的進(jìn)程ID。
父、子進(jìn)程完全一樣(代碼、數(shù)據(jù)),子進(jìn)程從fork內(nèi)部開(kāi)始執(zhí)行;父、子進(jìn)程從fork返回后,接著執(zhí)行下一條語(yǔ)句。
一般來(lái)說(shuō),在fork之后是父進(jìn)程先執(zhí)行還是子進(jìn)程先執(zhí)行是不確定的,應(yīng)用程序員無(wú)法控制。
2.fork 實(shí)例分析
//fork.c
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <unistd.h>
4 #include <sys/types.h>
5
6 #define err_sys(info) \
7 { \
8 printf ("%s\n", info);\
9 exit(0); \
10 }
11
12 int glob = 6; /* external variable in initialized data */
13 char buf[ ] = "a write to stdout\n";
14
15 int main(void)
16 {
17 int var; /* automatic variable on the stack */
18 pid_t pid;
19 var = 88;
20
21 if ((write(STDOUT_FILENO, buf, sizeof(buf)-1) != sizeof(buf)-1))
22 err_sys("write error");
23
24 printf("before fork\n"); /* we don't flush stdout */
25
26 if ( (pid = fork()) < 0) {
27 err_sys("fork error");
28 } else if (pid == 0) { /* child */
29 glob++; /* modify variables */
30 var++;
31 } else {
32 sleep(2); /* parent */
33 }
34
35 printf("pid = %d, ppid = %d, glob = %d, var = %d\n", getpid(),getppid(), glob, var);
36 exit(0);
37 }
運(yùn)行結(jié)果:
1 a write to stdout
2 before fork
3 pid = 9009, ppid = 9008, glob = 7, var = 89
4 pid = 9008, ppid = 8979, glob = 6, var = 88
運(yùn)行結(jié)果分析: 結(jié)果的第1行是由父進(jìn)程的21行打??; 結(jié)果的第2行是由父進(jìn)程的24行打??; 由于父進(jìn)程在24行睡眠了2秒,因此fork返回后,子進(jìn)程先于父進(jìn)程運(yùn)行是大概率事件,所以子進(jìn)程運(yùn)行到25行打印出結(jié)果中的第3行。由于子進(jìn)程會(huì)拷貝父進(jìn)程的整個(gè)進(jìn)程空間(這其中包括數(shù)據(jù)),因此當(dāng)子進(jìn)程26行從fork返回后,子進(jìn)程中的glob=6,var=88(拷貝自父進(jìn)程的數(shù)據(jù))。此時(shí)子進(jìn)程中pid=0,因此子進(jìn)程會(huì)執(zhí)行29、30行,當(dāng)子進(jìn)程到達(dá)35行時(shí),將打印glob=7,var=89。
雖然,子進(jìn)程改變了glob和var的值,但它僅僅是改變了子進(jìn)程中的glob和var,而影響不了父進(jìn)程中的glob和var。在子進(jìn)程出生后,父、子進(jìn)程的進(jìn)程空間(代碼、數(shù)據(jù)等)就是獨(dú)立,互不干擾的。因此當(dāng)父進(jìn)程運(yùn)行到35行,將會(huì)打印父進(jìn)程中的glob和var的值,他們分別是6和88,這就是運(yùn)行結(jié)果的第4行。