Linux作業系統下的多進程編程詳細解析

來源:互聯網
上載者:User
作者:wildwolf 來源:賽迪網技術社區
 (一) 理解Linux下進程的結構

   Linux下一個進程在記憶體裡有三部份的資料,就是 “資料區段”,“堆棧段”和“程式碼片段”,其實學過組合語言的人一定知道,一般的CPU象I386,都有上述三種段寄存器,以方便作業系統的運行。“代碼 段”,顧名思義,就是存放了程式碼的資料,假如機器中有數個進程運行相同的一個程式,那麼它們就可以使用同一個程式碼片段。    堆棧段存放的就是子程式的返回地址、子程式的參數以及程式的局部變數。而資料區段則存放程式的全域變數,常數以及動態資料分配的資料空間(比如用 malloc之類的函數取得的空間)。這其中有許多細節問題,這裡限於篇幅就不多介紹了。系統如果同時運行數個相同的程式,它們之間就不能使用同一個堆棧 段和資料區段。   (二) 如何使用fork    在 Linux下產生新的進程的系統調用就是fork函數,這個函數名是英文中“分叉”的意思。為什麼取這個名字呢?因為一個進程在運行中,如果使用了 fork,就產生了另一個進程,於是進程就“分叉”了,所以這個名字取得很形象。下面就看看如何具體使用fork,這段程式示範了使用fork的基本框 架:


void main(){ 
int i;
if ( fork() == 0 ) {
/* 子進程程式 */
for ( i = 1; i <1000; i ++ )
printf("This is child process/n");
}
else {
/* 父進程程式*/
for ( i = 1; i <1000; i ++ )
printf("This is process process/n");
}
}

   程式運行後,你就能看到螢幕上交替出現子進程與父進程各列印出的一千條資訊了。如果程式還在運行中,你用ps命令就能看到系統中有兩個它在運行了。    那麼調用這個fork函數時發生了什麼呢?一個程式一調用fork函數,系統就為一個新的進程準備了前述三個段,首先,系統讓新的進程與舊的進程使用同 一個程式碼片段,因為它們的程式還是相同的,對於資料區段和堆棧段,系統則複製一份給新的進程,這樣,父進程的所有資料都可以留給子進程,但是,子進程一旦開始 運行,雖然它繼承了父進程的一切資料,但實際上資料卻已經分開,相互之間不再有影響了,也就是說,它們之間不再共用任何資料了。而如果兩個進程要共用什麼 資料的話,就要使用另一套函數(shmget,shmat,shmdt等)來操作。現在,已經是兩個進程了,對於父進程,fork函數返回了子程式的進程 號,而對於子程式,fork函數則返回零,這樣,對於程式,只要判斷fork函數的傳回值,就知道自己是處於父進程還是子進程中。    讀者也許會問,如果一個大程式在運行中,它的資料區段和堆棧都很大,一次fork就要複製一次,那麼fork的系統開銷不是很大嗎?其實UNIX自有其解 決的辦法,大家知道,一般CPU都是以“頁”為單位分配空間的,象INTEL的CPU,其一頁在通常情況下是4K位元組大小,而無論是資料區段還是堆棧段都是 由許多“頁”構成的,fork函數複製這兩個段,只是“邏輯”上的,並非“物理”上的,也就是說,實際執行fork時,物理空間上兩個進程的資料區段和堆棧 段都還是共用著的,當有一個進程寫了某個資料時,這時兩個進程之間的資料才有了區別,系統就將有區別的“頁”從物理上也分開。系統在空間上的開銷就可以達 到最小。    一個小幽默:下面示範一個足以"搞死"Linux的小程式,其原始碼非常簡單:


void main() 
{
for(;;) fork();
}

   這個程式什麼也不做,就是死迴圈地fork,其結果是程式不斷產生進程,而這些進程又不斷產生新的進程,很快,系統的進程就滿了,系統就被這麼多不斷產 生的進程"撐死了"。用不著是root,任何人運行上述程式都足以讓系統死掉。哈哈,但這不是Linux不安全的理由,因為只要系統管理員足夠聰明,他 (或她)就可以預先給每個使用者佈建可啟動並執行最大進程數,這樣,只要不是root,任何能啟動並執行進程數也許不足系統總的能運行和進程數的十分之一,這樣,系 統管理員就能對付上述惡意的程式了。

   (三) 如何啟動另一程式的執行    下面我們來看看一個進程如何來啟動另一個程式的執行。在Linux中要使用exec類的函數,exec類的函數不止一個,但大致相同,在Linux中, 它們分別是:execl,execlp,execle,execv,execve和execvp,下面我只以execlp為例,其它函數究竟與 execlp有何區別,請通過manexec命令來瞭解它們的具體情況。   一個進程一旦調用exec類 函數,它本身就“死亡”了,系統把程式碼片段替換成新的程式的代碼,廢棄原有的資料區段和堆棧段,並為新程式分配新的資料區段與堆棧段,唯一留下的,就是進程號, 也就是說,對系統而言,還是同一個進程,不過已經是另一個程式了。(不過exec類函數中有的還允許繼承環境變數之類的資訊。)   那麼如果我的程式想啟動另一程式的執行但自己仍想繼續啟動並執行話,怎麼辦呢?那就是結合fork與exec的使用。下面一段代碼顯示如何啟動運行其它程式:


char command[256];
void main()
{
int rtn; /*子進程的返回數值*/
while(1) {
/* 從終端讀取要執行的命令 */
printf( ">" );
fgets( command, 256, stdin );
command[strlen(command)-1] = 0;
if ( fork() == 0 ) {
/* 子進程執行此命令 */ execlp( command, command );
/* 如果exec函數返回,表明沒有正常執行命令,列印錯誤資訊*/
perror( command );
exit( errorno );
}
else {
/* 父進程, 等待子進程結束,並列印子進程的傳回值 */
wait ( &rtn );
printf( " child process return %d/n",. rtn );
}
}
}


   此程式從終端讀入命令並執行之,執行完成後,父進程繼續等待從終端讀入命令。熟悉DOS和WINDOWS系統調用的朋友一定知道DOS/WINDOWS 也有exec類函數,其使用方法是類似的,但DOS/WINDOWS還有spawn類函數,因為DOS是單任務的系統,它只能將“父進程”駐留在機器內再 執行“子進程”,這就是spawn類的函數。WIN32已經是多任務的系統了,但還保留了spawn類函數,WIN32中實現spawn函數的方法同前述 UNIX中的方法差不多,開設子進程後父進程等待子進程結束後才繼續運行。UNIX在其一開始就是多任務的系統,所以從核心角度上講不需要spawn類函 數。   另外,有一個更簡單的執行其它程式的函數system,它是一個較高層的函數,實際上相當於在SHELL環境下執行一條命令,而exec類函數則是低層的系統調用。   (四) Linux的進程與Win32的進程/線程有何區別   熟悉WIN32編程的人一定知道,WIN32的進程管理方式與UNIX上有著很大區別,在UNIX裡,只有進程的概念,但在WIN32裡卻還有一個“線程”的概念,那麼UNIX和WIN32在這裡究竟有著什麼區別呢?   UNIX裡的fork是七十年代UNIX早期的開發人員經過長期在理論和實踐上的艱苦探索後取得的成果,一方面,它使作業系統在進程管理上付出了最小的代價,另一方面,又為程式員提供了一個簡潔明了的多進程方法。    WIN32裡的進程/線程是繼承自OS/2的。在WIN32裡,“進程”是指一個程式,而“線程”是一個“進程”裡的一個執行“線索”。從核心上講, WIN32的多進程與UNIX並無多大的區別,在WIN32裡的線程才相當於UNIX的進程,是一個實際正在執行的代碼。但是,WIN32裡同一個進程裡 各個線程之間是共用資料區段的。這才是與UNIX的進程最大的不同。   下面這段程式顯示了WIN32下一個進程如何啟動一個線程:(請注意,這是個終端方式程式,沒有圖形介面)


int g;
DWORD WINAPI ChildProcess( LPVOID lpParameter ){
int i;
for ( i = 1; i <1000; i ++) {
g ++;
printf( "This is Child Thread: %d/n", g );
}
ExitThread( 0 );
}; void main()
{
int threadID;
int i;
g = 0;
CreateThread( NULL, 0, ChildProcess, NULL, 0, &threadID );
for ( i = 1; i <1000; i ++) {
g ++;
printf( "This is Parent Thread: %d/n", g );
}
}

   在WIN32下,使用CreateThread函數建立線程,與UNIX不同,線程不是從建立處開始啟動並執行,而是由CreateThread指定一個函 數,線程就從那個函數處開始運行。此程式同前面的UNIX程式一樣,由兩個線程各列印1000條資訊。threadID是子線程的線程號,另外,全域變數 g是子線程與父線程共用的,這就是與UNIX最大的不同之處。大家可以看出,WIN32的進程/線程要比UNIX複雜,在UNIX裡要實作類別似WIN32 的線程並不難,只要fork以後,讓子進程調用ThreadProc函數,並且為全域變數開設共用資料區就行了,但在WIN32下就無法實作類別似fork 的功能了。所以現在WIN32下的C語言編譯器所提供的庫函數雖然已經能相容大多數UNIX的庫函數,但卻仍無法實現fork。    對於多任務系統,共用資料區是必要的,但也是一個容易引起混亂的問題,在WIN32下,一個程式員很容易忘記線程之間的資料是共用的這一情況,一個線程 修改過一個變數後,另一個線程卻又修改了它,結果引起程式出問題。但在UNIX下,由於變數本來並不共用,而由程式員來顯式地指定要共用的資料,使程式變 得更清晰與安全。   Linux還有自己的一個函數叫clone,這個函數是其它UNIX所沒有的,而且 通常的Linux也並不提供此函數(要使用此函數需自己重新編譯核心,並設定CLONE_ACTUALLY_WORKS_OK選項),clone函數提供 了更多的建立新進程的功能,包括象完全共用資料區段這樣的功能。 

至於WIN32的“進程”概念,其含義則是“應用程式”,也就是相當於UNIX下的exec了。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.