linux下fork與exec使用

一、引言

  對于沒有接觸過Unix/Linux操作系統(tǒng)的人來說,fork是最難理解的概念之一:它執(zhí)行一次卻返回兩個值。fork函數(shù)是Unix系統(tǒng)最杰出的成就之一,它是七十年代UNIX早期的開發(fā)者經(jīng)過長期在理論和實踐上的艱苦探索后取得的成果怀跛,一方面,它使操作系統(tǒng)在進(jìn)程管理上付出了最小的代價柄冲,另一方面吻谋,又為程序員提供了一個簡潔明了的多進(jìn)程方法。與DOS和早期的Windows不同现横,Unix/Linux系統(tǒng)是真正實現(xiàn)多任務(wù)操作的系統(tǒng)漓拾,可以說阁最,不使用多進(jìn)程編程,就不能算是真正的Linux環(huán)境下編程骇两。

  多線程程序設(shè)計的概念早在六十年代就被提出速种,但直到八十年代中期,Unix系統(tǒng)中才引入多線程機(jī)制低千,如今配阵,由于自身的許多優(yōu)點,多線程編程已經(jīng)得到了廣泛的應(yīng)用示血。

下面棋傍,我們將介紹在Linux下編寫多進(jìn)程和多線程程序的一些初步知識。

二难审、多進(jìn)程編程

什么是一個進(jìn)程瘫拣?進(jìn)程這個概念是針對系統(tǒng)而不是針對用戶的,對用戶來說剔宪,他面對的概念是程序。當(dāng)用戶敲入命令執(zhí)行一個程序的時候壹无,對系統(tǒng)而言葱绒,它將啟動一個進(jìn)程。但和程序不同的是斗锭,在這個進(jìn)程中地淀,系統(tǒng)可能需要再啟動一個或多個進(jìn)程來完成獨立的多個任務(wù)。多進(jìn)程編程的主要內(nèi)容包括進(jìn)程控制和進(jìn)程間通信岖是,在了解這些之前帮毁,我們先要簡單知道進(jìn)程的結(jié)構(gòu)。

2.1 Linux下進(jìn)程的結(jié)構(gòu)

  Linux下一個進(jìn)程在內(nèi)存里有三部分的數(shù)據(jù)豺撑,就是"代碼段"烈疚、"堆棧段"和"數(shù)據(jù)段"。其實學(xué)過匯編語言的人一定知道聪轿,一般的CPU都有上述三種段寄存器爷肝,以方便操作系統(tǒng)的運(yùn)行。這三個部分也是構(gòu)成一個完整的執(zhí)行序列的必要的部分陆错。

  "代碼段"灯抛,顧名思義,就是存放了程序代碼的數(shù)據(jù)音瓷,假如機(jī)器中有數(shù)個進(jìn)程運(yùn)行相同的一個程序对嚼,那么它們就可以使用相同的代碼段。"堆棧段"存放的就是子程序的返回地址绳慎、子程序的參數(shù)以及程序的局部變量纵竖。而數(shù)據(jù)段則存放程序的全局變量漠烧,常數(shù)以及動態(tài)數(shù)據(jù)分配的數(shù)據(jù)空間(比如用malloc之類的函數(shù)取得的空間)。這其中有許多細(xì)節(jié)問題磨确,這里限于篇幅就不多介紹了沽甥。系統(tǒng)如果同時運(yùn)行數(shù)個相同的程序,它們之間就不能使用同一個堆棧段和數(shù)據(jù)段乏奥。

2.2 Linux下的進(jìn)程控制

在傳統(tǒng)的Unix環(huán)境下摆舟,有兩個基本的操作用于創(chuàng)建和修改進(jìn)程:函數(shù)fork( )用來創(chuàng)建一個新的進(jìn)程,該進(jìn)程幾乎是當(dāng)前進(jìn)程的一個完全拷貝邓了;函數(shù)族exec( )用來啟動另外的進(jìn)程以取代當(dāng)前運(yùn)行的進(jìn)程恨诱。Linux的進(jìn)程控制和傳統(tǒng)的Unix進(jìn)程控制基本一致,只在一些細(xì)節(jié)的地方有些區(qū)別骗炉,例如在Linux系統(tǒng)中調(diào)用vfork和fork完全相同照宝,而在有些版本的Unix系統(tǒng)中,vfork調(diào)用有不同的功能句葵。由于這些差別幾乎不影響我們大多數(shù)的編程厕鹃,在這里我們不予考慮。

2.2.1 fork()

  fork在英文中是"分叉"的意思乍丈。為什么取這個名字呢剂碴?因為一個進(jìn)程在運(yùn)行中,如果使用了fork轻专,就產(chǎn)生了另一個進(jìn)程忆矛,于是進(jìn)程就"分叉"了,所以這個名字取得很形象请垛。下面就看看如何具體使用fork催训,這段程序演示了使用fork的基本框架:

1void main()

2{

3? ? int i;

4? ? if ( fork() == 0 )

5? ? {

6? ? ? /* 子進(jìn)程程序 */

7? ? ? for ( i = 1; i <1000; i ++ )

8? ? ? ? ? printf("This is child process\n");

9? ? }

10? ? else

11? ? {

12? ? ? /* 父進(jìn)程程序*/

13? ? ? for ( i = 1; i <1000; i ++ )

14? ? ? printf("This is process process\n");

15? ? }

16}

 程序運(yùn)行后,你就能看到屏幕上交替出現(xiàn)子進(jìn)程與父進(jìn)程各打印出的一千條信息了宗收。如果程序還在運(yùn)行中漫拭,你用ps命令就能看到系統(tǒng)中有兩個它在運(yùn)行了。

  那么調(diào)用這個fork函數(shù)時發(fā)生了什么呢混稽?fork函數(shù)啟動一個新的進(jìn)程嫂侍,前面我們說過,這個進(jìn)程幾乎是當(dāng)前進(jìn)程的一個拷貝:子進(jìn)程和父進(jìn)程使用相同的代碼段荚坞;子進(jìn)程復(fù)制父進(jìn)程的堆棧段和數(shù)據(jù)段挑宠。這樣,父進(jìn)程的所有數(shù)據(jù)都可以留給子進(jìn)程颓影,但是各淀,子進(jìn)程一旦開始運(yùn)行,雖然它繼承了父進(jìn)程的一切數(shù)據(jù)诡挂,但實際上數(shù)據(jù)卻已經(jīng)分開碎浇,相互之間不再有影響了临谱,也就是說,它們之間不再共享任何數(shù)據(jù)了奴璃。它們再要交互信息時悉默,只有通過進(jìn)程間通信來實現(xiàn),這將是我們下面的內(nèi)容苟穆。既然它們?nèi)绱讼嘞蟪危到y(tǒng)如何來區(qū)分它們呢?這是由函數(shù)的返回值來決定的雳旅。對于父進(jìn)程跟磨, fork函數(shù)返回了子程序的進(jìn)程號,而對于子程序攒盈,fork函數(shù)則返回零抵拘。在操作系統(tǒng)中,我們用ps函數(shù)就可以看到不同的進(jìn)程號型豁,對父進(jìn)程而言僵蛛,它的進(jìn)程號是由比它更低層的系統(tǒng)調(diào)用賦予的,而對于子進(jìn)程而言迎变,它的進(jìn)程號即是fork函數(shù)對父進(jìn)程的返回值充尉。在程序設(shè)計中,父進(jìn)程和子進(jìn)程都要調(diào)用函數(shù)fork()下面的代碼氏豌,而我們就是利用fork()函數(shù)對父子進(jìn)程的不同返回值用if...else...語句來實現(xiàn)讓父子進(jìn)程完成不同的功能喉酌,正如我們上面舉的例子一樣热凹。我們看到泵喘,上面例子執(zhí)行時兩條信息是交互無規(guī)則的打印出來的,這是父子進(jìn)程獨立執(zhí)行的結(jié)果,雖然我們的代碼似乎和串行的代碼沒有什么區(qū)別。

  讀者也許會問新荤,如果一個大程序在運(yùn)行中提澎,它的數(shù)據(jù)段和堆棧都很大,一次fork就要復(fù)制一次晴楔,那么fork的系統(tǒng)開銷不是很大嗎?其實UNIX自有其解決的辦法,大家知道芜繁,一般CPU都是以"頁"為單位來分配內(nèi)存空間的,每一個頁都是實際物理內(nèi)存的一個映像绒极,象INTEL的CPU骏令,其一頁在通常情況下是 4086字節(jié)大小,而無論是數(shù)據(jù)段還是堆棧段都是由許多"頁"構(gòu)成的垄提,fork函數(shù)復(fù)制這兩個段榔袋,只是"邏輯"上的周拐,并非"物理"上的,也就是說凰兑,實際執(zhí)行fork時妥粟,物理空間上兩個進(jìn)程的數(shù)據(jù)段和堆棧段都還是共享著的,當(dāng)有一個進(jìn)程寫了某個數(shù)據(jù)時吏够,這時兩個進(jìn)程之間的數(shù)據(jù)才有了區(qū)別勾给,系統(tǒng)就將有區(qū)別的" 頁"從物理上也分開。系統(tǒng)在空間上的開銷就可以達(dá)到最小稿饰。

  下面演示一個足以"搞死"Linux的小程序锦秒,其源代碼非常簡單:

1void main()?

2{

3for( ; ; )

4{

5fork();

6}

7}

  這個程序什么也不做,就是死循環(huán)地fork喉镰,其結(jié)果是程序不斷產(chǎn)生進(jìn)程旅择,而這些進(jìn)程又不斷產(chǎn)生新的進(jìn)程,很快侣姆,系統(tǒng)的進(jìn)程就滿了生真,系統(tǒng)就被這么多不斷產(chǎn)生 的進(jìn)程"撐死了"。當(dāng)然只要系統(tǒng)管理員預(yù)先給每個用戶設(shè)置可運(yùn)行的最大進(jìn)程數(shù)捺宗,這個惡意的程序就完成不了企圖了柱蟀。

? ? ? ? fork有兩個典型的用法:(引自:https://blog.csdn.net/bad_good_man/article/details/49364947)

  1.一個進(jìn)程創(chuàng)建一個自身的拷貝,這樣每個拷貝都可以在另一個拷貝執(zhí)行其他任務(wù)的同時處理各自的某個操作蚜厉。這是網(wǎng)絡(luò)服務(wù)器的典型用法长已。 ? ? ? ?

2.一個進(jìn)程想要執(zhí)行另一個程序。既然創(chuàng)建新進(jìn)程的唯一方法為調(diào)用fork昼牛,該進(jìn)程于是首先調(diào)用fork創(chuàng)建一個自身的拷貝术瓮,然后其中一個拷貝(通常為子進(jìn)程)調(diào)用exec把自身替換成新的程序。這是諸如shell之類程序的典型用法贰健。

2.2.2 exec( )函數(shù)族

下面我們來看看一個進(jìn)程如何來啟動另一個程序的執(zhí)行胞四。在Linux中要使用exec函數(shù)族。系統(tǒng)調(diào)用execve()對當(dāng)前進(jìn)程進(jìn)行替換伶椿,替換者為一個指定的程序辜伟,其參數(shù)包括文件名(filename)、參數(shù)列表(argv)以及環(huán)境變量(envp)脊另。exec函數(shù)族當(dāng)然不止一個导狡,但它們大致相同,在 Linux中偎痛,它們分別是:execl旱捧,execlp,execle看彼,execv廊佩,execve和execvp囚聚,下面我只以execlp為例,其它函數(shù)究竟與execlp有何區(qū)別标锄,請通過manexec命令來了解它們的具體情況顽铸。

  一個進(jìn)程一旦調(diào)用exec類函數(shù),它本身就"死亡"了料皇,系統(tǒng)把代碼段替換成新的程序的代碼谓松,廢棄原有的數(shù)據(jù)段和堆棧段,并為新程序分配新的數(shù)據(jù)段與堆棧段践剂,唯一留下的鬼譬,就是進(jìn)程號,也就是說逊脯,對系統(tǒng)而言优质,還是同一個進(jìn)程,不過已經(jīng)是另一個程序了军洼。(不過exec類函數(shù)中有的還允許繼承環(huán)境變量之類的信息巩螃。)

那么如果我的程序想啟動另一程序的執(zhí)行但自己仍想繼續(xù)運(yùn)行的話,怎么辦呢匕争?那就是結(jié)合fork與exec的使用避乏。下面一段代碼顯示如何啟動運(yùn)行其它程序:


  此程序從終端讀入命令并執(zhí)行之,執(zhí)行完成后甘桑,父進(jìn)程繼續(xù)等待從終端讀入命令拍皮。熟悉DOS和WINDOWS系統(tǒng)調(diào)用的朋友一定知道DOS/WINDOWS也有exec類函數(shù),其使用方法是類似的跑杭,但DOS/WINDOWS還有spawn類函數(shù)铆帽,因為DOS是單任務(wù)的系統(tǒng),它只能將"父進(jìn)程"駐留在機(jī)器內(nèi)再執(zhí)行"子進(jìn)程"艘蹋,這就是spawn類的函數(shù)锄贼。WIN32已經(jīng)是多任務(wù)的系統(tǒng)了票灰,但還保留了spawn類函數(shù)女阀,WIN32中實現(xiàn)spawn函數(shù)的方法同前述 UNIX中的方法差不多,開設(shè)子進(jìn)程后父進(jìn)程等待子進(jìn)程結(jié)束后才繼續(xù)運(yùn)行屑迂。UNIX在其一開始就是多任務(wù)的系統(tǒng)浸策,所以從核心角度上講不需要spawn類函數(shù)。

  在這一節(jié)里惹盼,我們還要講講system()和popen()函數(shù)庸汗。system()函數(shù)先調(diào)用fork(),然后再調(diào)用exec()來執(zhí)行用戶的登錄 shell手报,通過它來查找可執(zhí)行文件的命令并分析參數(shù)蚯舱,最后它么使用wait()函數(shù)族之一來等待子進(jìn)程的結(jié)束改化。函數(shù)popen()和函數(shù) system()相似,不同的是它調(diào)用pipe()函數(shù)創(chuàng)建一個管道陈肛,通過它來完成程序的標(biāo)準(zhǔn)輸入和標(biāo)準(zhǔn)輸出兄裂。這兩個函數(shù)是為那些不太勤快的程序員設(shè)計的,在效率和安全方面都有相當(dāng)?shù)娜毕萏溉觯诳赡艿那闆r下匾南,應(yīng)該盡量避免。

2.3 Linux下的進(jìn)程間通信

  詳細(xì)的講述進(jìn)程間通信在這里絕對是不可能的事情立宜,而且筆者很難有信心說自己對這一部分內(nèi)容的認(rèn)識達(dá)到了什么樣的地步臊岸,所以在這一節(jié)的開頭首先向大家推薦著名作者Richard Stevens的著名作品:《Advanced Programming in the UNIX Environment》帅戒,它的中文譯本《UNIX環(huán)境高級編程》已有機(jī)械工業(yè)出版社出版,原文精彩逻住,譯文同樣地道瞎访,如果你的確對在Linux下編程有濃厚的興趣,那么趕緊將這本書擺到你的書桌上或計算機(jī)旁邊來播演。說這么多實在是難抑心中的景仰之情伴奥,言歸正傳,在這一節(jié)里拾徙,我們將介紹進(jìn)程間通信最最初步和最最簡單的一些知識和概念。

  首先暂衡,進(jìn)程間通信至少可以通過傳送打開文件來實現(xiàn)询微,不同的進(jìn)程通過一個或多個文件來傳遞信息狂巢,事實上,在很多應(yīng)用系統(tǒng)里代态,都使用了這種方法疹吃。但一般說來,進(jìn)程間通信(IPC:InterProcess Communication)不包括這種似乎比較低級的通信方法萨驶。Unix系統(tǒng)中實現(xiàn)進(jìn)程間通信的方法很多腔呜,而且不幸的是,極少方法能在所有的Unix系統(tǒng)中進(jìn)行移植(唯一一種是半雙工的管道膝但,這也是最原始的一種通信方式)跟束。而Linux作為一種新興的操作系統(tǒng)丑孩,幾乎支持所有的Unix下常用的進(jìn)程間通信方法:管道温学、消息隊列、共享內(nèi)存逃延、信號量箩帚、套接口等等黄痪。下面我們將逐一介紹。

2.3.1 管道

  管道是進(jìn)程間通信中最古老的方式,它包括無名管道和有名管道兩種是嗜,前者用于父進(jìn)程和子進(jìn)程間的通信愈案,后者用于運(yùn)行于同一臺機(jī)器上的任意兩個進(jìn)程間的通信鹅搪。

  無名管道由pipe()函數(shù)創(chuàng)建:

  #include <unistd.h>

  int pipe(int filedis[2])站绪;


  參數(shù)filedis返回兩個文件描述符:filedes[0]為讀而打開丽柿,filedes[1]為寫而打開恢准。filedes[1]的輸出是filedes[0]的輸入。下面的例子示范了如何在父進(jìn)程和子進(jìn)程間實現(xiàn)通信馁筐。

#define INPUT 0

#define OUTPUT 1

void main() {

? int file_descriptors[2];

? /*定義子進(jìn)程號 */

? pid_t pid;

? char buf[256];

? int returned_count;

? /*創(chuàng)建無名管道*/

? pipe(file_descriptors);

? /*創(chuàng)建子進(jìn)程*/

? if((pid = fork()) == -1) {

? ? ? printf("Error in fork\n");

? ? ? exit(1);

? }

? /*執(zhí)行子進(jìn)程*/

? if(pid == 0) {

? ? ? printf("in the spawned (child) process...\n");

? ? ? /*子進(jìn)程向父進(jìn)程寫數(shù)據(jù),關(guān)閉管道的讀端*/

? ? ? close(file_descriptors[INPUT]);

? ? ? write(file_descriptors[OUTPUT], "test data", strlen("test data"));

? ? ? exit(0);

? } else {

? ? ? /*執(zhí)行父進(jìn)程*/

? ? ? printf("in the spawning (parent) process...\n");

? ? ? /*父進(jìn)程從管道讀取子進(jìn)程寫的數(shù)據(jù)坠非,關(guān)閉管道的寫端*/

? ? ? close(file_descriptors[OUTPUT]);

? ? ? returned_count = read(file_descriptors[INPUT], buf, sizeof(buf));

? ? ? printf("%d bytes of data received from spawned process: %s\n",

? ? ? returned_count, buf);

? }

}


 在Linux系統(tǒng)下敏沉,有名管道可由兩種方式創(chuàng)建:命令行方式mknod系統(tǒng)調(diào)用和函數(shù)mkfifo。下面的兩種途徑都在當(dāng)前目錄下生成了一個名為myfifo的有名管道:

  方式一:mkfifo("myfifo","rw");

  方式二:mknod myfifo p

  生成了有名管道后炎码,就可以使用一般的文件I/O函數(shù)如open、close潦闲、read歉闰、write等來對它進(jìn)行操作。下面即是一個簡單的例子赵辕,假設(shè)我們已經(jīng)創(chuàng)建了一個名為myfifo的有名管道还惠。

/* 進(jìn)程一:讀有名管道*/

#include <stdio.h>

#include <unistd.h>

void main() {

? FILE * in_file;

? int count = 1;

? char buf[80];

? in_file = fopen("mypipe", "r");

? if (in_file == NULL) {

? ? ? printf("Error in fdopen.\n");

? ? ? exit(1);

? }

? while ((count = fread(buf, 1, 80, in_file)) > 0)

? ? ? printf("received from pipe: %s\n", buf);

? fclose(in_file);

}

/* 進(jìn)程二:寫有名管道*/

#include <stdio.h>

#include <unistd.h>

void main() {

? FILE * out_file;

? int count = 1;

? char buf[80];

? out_file = fopen("mypipe", "w");

? if (out_file == NULL) {

? ? ? printf("Error opening pipe.");

? ? ? exit(1);

? }

? sprintf(buf,"this is test data for the named pipe example\n");

? fwrite(buf, 1, 80, out_file);

? fclose(out_file);

}


2.3.2 消息隊列

    消息隊列用于運(yùn)行于同一臺機(jī)器上的進(jìn)程間通信,它和管道很相似蚕键,事實上锣光,它是一種正逐漸被淘汰的通信方式,我們可以用流管道或者套接口的方式來取代它铝耻,所以誊爹,我們對此方式也不再解釋蹬刷,也建議讀者忽略這種方式。

2.3.3 共享內(nèi)存

  共享內(nèi)存是運(yùn)行在同一臺機(jī)器上的進(jìn)程間通信最快的方式频丘,因為數(shù)據(jù)不需要在不同的進(jìn)程間復(fù)制办成。通常由一個進(jìn)程創(chuàng)建一塊共享內(nèi)存區(qū),其余進(jìn)程對這塊內(nèi)存區(qū)進(jìn)行讀寫搂漠。得到共享內(nèi)存有兩種方式:映射/dev/mem設(shè)備和內(nèi)存映像文件迂卢。前一種方式不給系統(tǒng)帶來額外的開銷,但在現(xiàn)實中并不常用桐汤,因為它控制存取的將是實際的物理內(nèi)存而克,在Linux系統(tǒng)下,這只有通過限制Linux系統(tǒng)存取的內(nèi)存才可以做到怔毛,這當(dāng)然不太實際拍摇。常用的方式是通過shmXXX函數(shù)族來實現(xiàn)利用共享內(nèi)存進(jìn)行存儲的。

  首先要用的函數(shù)是shmget馆截,它獲得一個共享存儲標(biāo)識符充活。

  #include <sys/types.h>

  #include <sys/ipc.h>

  #include <sys/shm.h>

  int shmget(key_t key, int size, int flag);

  這個函數(shù)有點類似大家熟悉的malloc函數(shù),系統(tǒng)按照請求分配size大小的內(nèi)存用作共享內(nèi)存蜡娶。Linux系統(tǒng)內(nèi)核中每個IPC結(jié)構(gòu)都有的一個非負(fù)整數(shù)的標(biāo)識符混卵,這樣對一個消息隊列發(fā)送消息時只要引用標(biāo)識符就可以了。這個標(biāo)識符是內(nèi)核由IPC結(jié)構(gòu)的關(guān)鍵字得到的窖张,這個關(guān)鍵字幕随,就是上面第一個函數(shù)的 key。數(shù)據(jù)類型key_t是在頭文件sys/types.h中定義的宿接,它是一個長整形的數(shù)據(jù)赘淮。在我們后面的章節(jié)中,還會碰到這個關(guān)鍵字睦霎。

  當(dāng)共享內(nèi)存創(chuàng)建后梢卸,其余進(jìn)程可以調(diào)用shmat()將其連接到自身的地址空間中。

  void *shmat(int shmid, void *addr, int flag);

  shmid為shmget函數(shù)返回的共享存儲標(biāo)識符副女,addr和flag參數(shù)決定了以什么方式來確定連接的地址蛤高,函數(shù)的返回值即是該進(jìn)程數(shù)據(jù)段所連接的實際地址,進(jìn)程可以對此進(jìn)程進(jìn)行讀寫操作碑幅。

  使用共享存儲來實現(xiàn)進(jìn)程間通信的注意點是對數(shù)據(jù)存取的同步戴陡,必須確保當(dāng)一個進(jìn)程去讀取數(shù)據(jù)時,它所想要的數(shù)據(jù)已經(jīng)寫好了沟涨。通常恤批,信號量被要來實現(xiàn)對共享存儲數(shù)據(jù)存取的同步,另外裹赴,可以通過使用shmctl函數(shù)設(shè)置共享存儲內(nèi)存的某些標(biāo)志位如SHM_LOCK喜庞、SHM_UNLOCK等來實現(xiàn)诀浪。

 2.3.4 信號量

  信號量又稱為信號燈,它是用來協(xié)調(diào)不同進(jìn)程間的數(shù)據(jù)對象的赋荆,而最主要的應(yīng)用是前一節(jié)的共享內(nèi)存方式的進(jìn)程間通信笋妥。本質(zhì)上懊昨,信號量是一個計數(shù)器窄潭,它用來記錄對某個資源(如共享內(nèi)存)的存取狀況。一般說來酵颁,為了獲得共享資源嫉你,進(jìn)程需要執(zhí)行下列操作:

  (1) 測試控制該資源的信號量躏惋。

 ∮奈邸(2) 若此信號量的值為正,則允許進(jìn)行使用該資源簿姨。進(jìn)程將進(jìn)號量減1距误。

  (3) 若此信號量為0扁位,則該資源目前不可用准潭,進(jìn)程進(jìn)入睡眠狀態(tài),直至信號量值大于0域仇,進(jìn)程被喚醒刑然,轉(zhuǎn)入步驟(1)。

 ∠疚瘛(4) 當(dāng)進(jìn)程不再使用一個信號量控制的資源時泼掠,信號量值加1。如果此時有進(jìn)程正在睡眠等待此信號量垦细,則喚醒此進(jìn)程择镇。

  維護(hù)信號量狀態(tài)的是Linux內(nèi)核操作系統(tǒng)而不是用戶進(jìn)程。我們可以從頭文件/usr/src/linux/include /linux /sem.h 中看到內(nèi)核用來維護(hù)信號量狀態(tài)的各個結(jié)構(gòu)的定義括改。信號量是一個數(shù)據(jù)集合沐鼠,用戶可以單獨使用這一集合的每個元素。要調(diào)用的第一個函數(shù)是semget叹谁,用以獲得一個信號量ID饲梭。

  #include <sys/types.h>

  #include <sys/ipc.h>

  #include <sys/sem.h>

  int semget(key_t key, int nsems, int flag);

  key是前面講過的IPC結(jié)構(gòu)的關(guān)鍵字,它將來決定是創(chuàng)建新的信號量集合焰檩,還是引用一個現(xiàn)有的信號量集合憔涉。    nsems是該集合中的信號量數(shù)析苫。如果是創(chuàng)建新集合(一般在服務(wù)器中)兜叨,則必須指定nsems穿扳;如果是引用一個現(xiàn)有的信號量集合(一般在客戶機(jī)中)則將nsems指定為0。

  semctl函數(shù)用來對信號量進(jìn)行操作国旷。

  int semctl(int semid, int semnum, int cmd, union semun arg);

  不同的操作是通過cmd參數(shù)來實現(xiàn)的矛物,在頭文件sem.h中定義了7種不同的操作,實際編程時可以參照使用跪但。

semop函數(shù)自動執(zhí)行信號量集合上的操作數(shù)組履羞。

  int semop(int semid, struct sembuf semoparray[], size_t nops);

  semoparray是一個指針,它指向一個信號量操作數(shù)組屡久。nops規(guī)定該數(shù)組中操作的數(shù)量忆首。

  下面,我們看一個具體的例子被环,它創(chuàng)建一個特定的IPC結(jié)構(gòu)的關(guān)鍵字和一個信號量糙及,建立此信號量的索引,修改索引指向的信號量的值筛欢,最后我們清除信號量浸锨。在下面的代碼中,函數(shù)ftok生成我們上文所說的唯一的IPC關(guān)鍵字版姑。

#include <stdio.h>

#include <sys/types.h>

#include <sys/sem.h>

#include <sys/ipc.h>

void main() {

? key_t unique_key; /* 定義一個IPC關(guān)鍵字*/

? int id;

? struct sembuf lock_it;

? union semun options;

? int i;

? ? unique_key = ftok(".", 'a'); /* 生成關(guān)鍵字柱搜,字符'a'是一個隨機(jī)種子*/

? /* 創(chuàng)建一個新的信號量集合*/

? id = semget(unique_key, 1, IPC_CREAT | IPC_EXCL | 0666);

? printf("semaphore id=%d\n", id);

? options.val = 1; /*設(shè)置變量值*/

? semctl(id, 0, SETVAL, options); /*設(shè)置索引0的信號量*/

? ? /*打印出信號量的值*/

? i = semctl(id, 0, GETVAL, 0);

? printf("value of semaphore at index 0 is %d\n", i);

? ? /*下面重新設(shè)置信號量*/

? lock_it.sem_num = 0; /*設(shè)置哪個信號量*/

? lock_it.sem_op = -1; /*定義操作*/

? lock_it.sem_flg = IPC_NOWAIT; /*操作方式*/

? if (semop(id, &lock_it, 1) == -1) {

? ? ? printf("can not lock semaphore.\n");

? ? ? exit(1);

? }

? ? i = semctl(id, 0, GETVAL, 0);

? printf("value of semaphore at index 0 is %d\n", i);

? ? /*清除信號量*/

? semctl(id, 0, IPC_RMID, 0);

}


  2.3.5 套接口

  套接口(socket)編程是實現(xiàn)Linux系統(tǒng)和其他大多數(shù)操作系統(tǒng)中進(jìn)程間通信的主要方式之一。我們熟知的WWW服務(wù)漠酿、FTP服務(wù)冯凹、TELNET服務(wù)等都是基于套接口編程來實現(xiàn)的。除了在異地的計算機(jī)進(jìn)程間以外炒嘲,套接口同樣適用于本地同一臺計算機(jī)內(nèi)部的進(jìn)程間通信宇姚。關(guān)于套接口的經(jīng)典教材同樣是 Richard Stevens編著的《Unix網(wǎng)絡(luò)編程:聯(lián)網(wǎng)的API和套接字》,清華大學(xué)出版社出版了該書的影印版夫凸。它同樣是Linux程序員的必備書籍之一浑劳。

  關(guān)于這一部分的內(nèi)容,可以參照本文作者的另一篇文章《設(shè)計自己的網(wǎng)絡(luò)螞蟻》夭拌,那里由常用的幾個套接口函數(shù)的介紹和示例程序魔熏。這一部分或許是Linux進(jìn)程間通信編程中最須關(guān)注和最吸引人的一部分,畢竟鸽扁,Internet 正在我們身邊以不可思議的速度發(fā)展著蒜绽,如果一個程序員在設(shè)計編寫他下一個程序的時候,根本沒有考慮到網(wǎng)絡(luò)桶现,考慮到Internet躲雅,那么,可以說骡和,他的設(shè)計很難成功相赁。


3 Linux的進(jìn)程和Win32的進(jìn)程/線程比較

  熟悉WIN32編程的人一定知道相寇,WIN32的進(jìn)程管理方式與Linux上有著很大區(qū)別,在UNIX里钮科,只有進(jìn)程的概念唤衫,但在WIN32里卻還有一個"線程"的概念,那么Linux和WIN32在這里究竟有著什么區(qū)別呢绵脯?

  WIN32里的進(jìn)程/線程是繼承自O(shè)S/2的佳励。在WIN32里,"進(jìn)程"是指一個程序桨嫁,而"線程"是一個"進(jìn)程"里的一個執(zhí)行"線索"植兰。從核心上講份帐,WIN32的多進(jìn)程與Linux并無多大的區(qū)別璃吧,在WIN32里的線程才相當(dāng)于Linux的進(jìn)程,是一個實際正在執(zhí)行的代碼废境。但是畜挨,WIN32里同一個進(jìn)程里各個線程之間是共享數(shù)據(jù)段的。這才是與Linux的進(jìn)程最大的不同噩凹。

  下面這段程序顯示了WIN32下一個進(jìn)程如何啟動一個線程巴元。

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函數(shù)創(chuàng)建線程驮宴,與Linux下創(chuàng)建進(jìn)程不同逮刨,WIN32線程不是從創(chuàng)建處開始運(yùn)行的,而是由 CreateThread指定一個函數(shù)堵泽,線程就從那個函數(shù)處開始運(yùn)行修己。此程序同前面的UNIX程序一樣,由兩個線程各打印1000條信息迎罗。 threadID是子線程的線程號睬愤,另外,全局變量g是子線程與父線程共享的纹安,這就是與Linux最大的不同之處尤辱。大家可以看出,WIN32的進(jìn)程/線程要比Linux復(fù)雜厢岂,在Linux要實現(xiàn)類似WIN32的線程并不難光督,只要fork以后,讓子進(jìn)程調(diào)用ThreadProc函數(shù)塔粒,并且為全局變量開設(shè)共享數(shù)據(jù)區(qū)就行了结借,但在WIN32下就無法實現(xiàn)類似fork的功能了。所以現(xiàn)在WIN32下的C語言編譯器所提供的庫函數(shù)雖然已經(jīng)能兼容大多數(shù) Linux/UNIX的庫函數(shù)窗怒,但卻仍無法實現(xiàn)fork映跟。



對于多任務(wù)系統(tǒng)蓄拣,共享數(shù)據(jù)區(qū)是必要的,但也是一個容易引起混亂的問題努隙,在WIN32下球恤,一個程序員很容易忘記線程之間的數(shù)據(jù)是共享的這一情況,一個線程修改過一個變量后荸镊,另一個線程卻又修改了它咽斧,結(jié)果引起程序出問題。但在Linux下躬存,由于變量本來并不共享张惹,而由程序員來顯式地指定要共享的數(shù)據(jù),使程序變得更清晰與安全岭洲。

至于WIN32的"進(jìn)程"概念宛逗,其含義則是"應(yīng)用程序",也就是相當(dāng)于UNIX下的exec了盾剩。

Linux也有自己的多線程函數(shù)pthread雷激,它既不同于Linux的進(jìn)程,也不同于WIN32下的進(jìn)程告私,關(guān)于pthread的介紹和如何在Linux環(huán)境下編寫多線程程序我們將在另一篇文章《Linux下的多線程編程》中講述屎暇。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市驻粟,隨后出現(xiàn)的幾起案子根悼,更是在濱河造成了極大的恐慌,老刑警劉巖蜀撑,帶你破解...
    沈念sama閱讀 222,183評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件挤巡,死亡現(xiàn)場離奇詭異,居然都是意外死亡屯掖,警方通過查閱死者的電腦和手機(jī)玄柏,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,850評論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來贴铜,“玉大人粪摘,你說我怎么就攤上這事∩馨樱” “怎么了徘意?”我有些...
    開封第一講書人閱讀 168,766評論 0 361
  • 文/不壞的土叔 我叫張陵,是天一觀的道長轩褐。 經(jīng)常有香客問我椎咧,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,854評論 1 299
  • 正文 為了忘掉前任勤讽,我火速辦了婚禮蟋座,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘脚牍。我一直安慰自己向臀,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 68,871評論 6 398
  • 文/花漫 我一把揭開白布诸狭。 她就那樣靜靜地躺著券膀,像睡著了一般。 火紅的嫁衣襯著肌膚如雪驯遇。 梳的紋絲不亂的頭發(fā)上芹彬,一...
    開封第一講書人閱讀 52,457評論 1 311
  • 那天,我揣著相機(jī)與錄音叉庐,去河邊找鬼舒帮。 笑死,一個胖子當(dāng)著我的面吹牛眨唬,可吹牛的內(nèi)容都是我干的会前。 我是一名探鬼主播好乐,決...
    沈念sama閱讀 40,999評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼匾竿,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了蔚万?” 一聲冷哼從身側(cè)響起岭妖,我...
    開封第一講書人閱讀 39,914評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎反璃,沒想到半個月后昵慌,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,465評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡淮蜈,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,543評論 3 342
  • 正文 我和宋清朗相戀三年斋攀,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片梧田。...
    茶點故事閱讀 40,675評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡淳蔼,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出裁眯,到底是詐尸還是另有隱情鹉梨,我是刑警寧澤,帶...
    沈念sama閱讀 36,354評論 5 351
  • 正文 年R本政府宣布穿稳,位于F島的核電站存皂,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏逢艘。R本人自食惡果不足惜旦袋,卻給世界環(huán)境...
    茶點故事閱讀 42,029評論 3 335
  • 文/蒙蒙 一骤菠、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧疤孕,春花似錦娩怎、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,514評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至柬讨,卻和暖如春崩瓤,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背踩官。 一陣腳步聲響...
    開封第一講書人閱讀 33,616評論 1 274
  • 我被黑心中介騙來泰國打工却桶, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人蔗牡。 一個月前我還...
    沈念sama閱讀 49,091評論 3 378
  • 正文 我出身青樓颖系,卻偏偏與公主長得像,于是被迫代替她去往敵國和親辩越。 傳聞我的和親對象是個殘疾皇子嘁扼,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,685評論 2 360

推薦閱讀更多精彩內(nèi)容