• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            隨筆 - 40, 文章 - 0, 評(píng)論 - 9, 引用 - 0
            數(shù)據(jù)加載中……

            Linux下的多進(jìn)程編程初步

            http://www.bccn.net/Article/czxt/linux/200511/1037.html

            文章摘要:
               多線程程序設(shè)計(jì)的概念早在六十年代就被提出,但直到八十年代中期,Unix系統(tǒng)中才引入多線程機(jī)制,如今,由于自身的許多優(yōu)點(diǎn),多線程編程已經(jīng)得到了廣泛的應(yīng)用。本文我們將介紹在Linux下編寫多進(jìn)程和多線程程序的一些初步知識(shí)。


            --------------------------------------------------------------------------------

            正文:
            Linux下的多進(jìn)程編程初步

            1 引言
            對(duì)于沒有接觸過Unix/Linux操作系統(tǒng)的人來說,fork是最難理解的概念之一:它執(zhí)行一次卻返回兩個(gè)值。fork函數(shù)是Unix系統(tǒng)最杰出的成就 之一,它是七十年代UNIX早期的開發(fā)者經(jīng)過長(zhǎng)期在理論和實(shí)踐上的艱苦探索后取得的成果,一方面,它使操作系統(tǒng)在進(jìn)程管理上付出了最小的代價(jià),另一方面, 又為程序員提供了一個(gè)簡(jiǎn)潔明了的多進(jìn)程方法。與DOS和早期的Windows不同,Unix/Linux系統(tǒng)是真正實(shí)現(xiàn)多任務(wù)操作的系統(tǒng),可以說,不使用 多進(jìn)程編程,就不能算是真正的Linux環(huán)境下編程。
               多線程程序設(shè)計(jì)的概念早在六十年代就被提出,但直到八十年代中期,Unix系統(tǒng)中才引入多線程機(jī)制,如今,由于自身的許多優(yōu)點(diǎn),多線程編程已經(jīng)得到了廣泛的應(yīng)用。
               下面,我們將介紹在Linux下編寫多進(jìn)程和多線程程序的一些初步知識(shí)。

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

              2.1 Linux下進(jìn)程的結(jié)構(gòu)
               Linux下一個(gè)進(jìn)程在內(nèi)存里有三部分的數(shù)據(jù),就是"代碼段"、"堆棧段"和"數(shù)據(jù)段"。其實(shí)學(xué)過匯編語言的人一定知道,一般的CPU都有上述三種段寄存器,以方便操作系統(tǒng)的運(yùn)行。這三個(gè)部分也是構(gòu)成一個(gè)完整的執(zhí)行序列的必要的部分。
            "代碼段",顧名思義,就是存放了程序代碼的數(shù)據(jù),假如機(jī)器中有數(shù)個(gè)進(jìn)程運(yùn)行相同的一個(gè)程序,那么它們就可以使用相同的代碼段。"堆棧段"存放的就是子程 序的返回地址、子程序的參數(shù)以及程序的局部變量。而數(shù)據(jù)段則存放程序的全局變量,常數(shù)以及動(dòng)態(tài)數(shù)據(jù)分配的數(shù)據(jù)空間(比如用malloc之類的函數(shù)取得的空 間)。這其中有許多細(xì)節(jié)問題,這里限于篇幅就不多介紹了。系統(tǒng)如果同時(shí)運(yùn)行數(shù)個(gè)相同的程序,它們之間就不能使用同一個(gè)堆棧段和數(shù)據(jù)段。

              2.2 Linux下的進(jìn)程控制
            在傳統(tǒng)的Unix環(huán)境下,有兩個(gè)基本的操作用于創(chuàng)建和修改進(jìn)程:函數(shù)fork( )用來創(chuàng)建一個(gè)新的進(jìn)程,該進(jìn)程幾乎是當(dāng)前進(jìn)程的一個(gè)完全拷貝;函數(shù)族e(cuò)xec( )用來啟動(dòng)另外的進(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在英文中是"分叉"的意思。為什么取這個(gè)名字呢?因?yàn)橐粋€(gè)進(jìn)程在運(yùn)行中,如果使用了fork,就產(chǎn)生了另一個(gè)進(jìn)程,于是進(jìn)程就"分叉"了,所以這個(gè)名字取得很形象。下面就看看如何具體使用fork,這段程序演示了使用fork的基本框架:

            void main(){
            int i;
            if ( fork() == 0 ) {
            /* 子進(jìn)程程序 */
            for ( i = 1; i <1000; i ++ ) printf("This is child process\n");
            }
            else {
            /* 父進(jìn)程程序*/
            for ( i = 1; i <1000; i ++ ) printf("This is process process\n");
            }
            }
               程序運(yùn)行后,你就能看到屏幕上交替出現(xiàn)子進(jìn)程與父進(jìn)程各打印出的一千條信息了。如果程序還在運(yùn)行中,你用ps命令就能看到系統(tǒng)中有兩個(gè)它在運(yùn)行了。
            那么調(diào)用這個(gè)fork函數(shù)時(shí)發(fā)生了什么呢?fork函數(shù)啟動(dòng)一個(gè)新的進(jìn)程,前面我們說過,這個(gè)進(jìn)程幾乎是當(dāng)前進(jìn)程的一個(gè)拷貝:子進(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í)際上 數(shù)據(jù)卻已經(jīng)分開,相互之間不再有影響了,也就是說,它們之間不再共享任何數(shù)據(jù)了。它們?cè)僖换バ畔r(shí),只有通過進(jìn)程間通信來實(shí)現(xiàn),這將是我們下面的內(nèi)容。 既然它們?nèi)绱讼嘞螅到y(tǒng)如何來區(qū)分它們呢?這是由函數(shù)的返回值來決定的。對(duì)于父進(jìn)程,fork函數(shù)返回了子程序的進(jìn)程號(hào),而對(duì)于子程序,fork函數(shù)則返 回零。在操作系統(tǒng)中,我們用ps函數(shù)就可以看到不同的進(jìn)程號(hào),對(duì)父進(jìn)程而言,它的進(jìn)程號(hào)是由比它更低層的系統(tǒng)調(diào)用賦予的,而對(duì)于子進(jìn)程而言,它的進(jìn)程號(hào)即 是fork函數(shù)對(duì)父進(jìn)程的返回值。在程序設(shè)計(jì)中,父進(jìn)程和子進(jìn)程都要調(diào)用函數(shù)fork()下面的代碼,而我們就是利用fork()函數(shù)對(duì)父子進(jìn)程的不同返 回值用if...else...語句來實(shí)現(xiàn)讓父子進(jìn)程完成不同的功能,正如我們上面舉的例子一樣。我們看到,上面例子執(zhí)行時(shí)兩條信息是交互無規(guī)則的打印出 來的,這是父子進(jìn)程獨(dú)立執(zhí)行的結(jié)果,雖然我們的代碼似乎和串行的代碼沒有什么區(qū)別。
            讀者也許會(huì)問,如果一個(gè)大程序在運(yùn)行中,它的數(shù)據(jù)段和堆棧都很大,一次fork就要復(fù)制一次,那么fork的系統(tǒng)開銷不是很大嗎?其實(shí)UNIX自有其解決 的辦法,大家知道,一般CPU都是以"頁"為單位來分配內(nèi)存空間的,每一個(gè)頁都是實(shí)際物理內(nèi)存的一個(gè)映像,象INTEL的CPU,其一頁在通常情況下是 4086字節(jié)大小,而無論是數(shù)據(jù)段還是堆棧段都是由許多"頁"構(gòu)成的,fork函數(shù)復(fù)制這兩個(gè)段,只是"邏輯"上的,并非"物理"上的,也就是說,實(shí)際執(zhí) 行fork時(shí),物理空間上兩個(gè)進(jìn)程的數(shù)據(jù)段和堆棧段都還是共享著的,當(dāng)有一個(gè)進(jìn)程寫了某個(gè)數(shù)據(jù)時(shí),這時(shí)兩個(gè)進(jìn)程之間的數(shù)據(jù)才有了區(qū)別,系統(tǒng)就將有區(qū)別的" 頁"從物理上也分開。系統(tǒng)在空間上的開銷就可以達(dá)到最小。
               下面演示一個(gè)足以"搞死"Linux的小程序,其源代碼非常簡(jiǎn)單:
               void main()
               {
                 for( ; ; ) fork();
               }
            這個(gè)程序什么也不做,就是死循環(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ù)先給每個(gè)用戶設(shè)置可運(yùn)行的最大進(jìn)程數(shù),這個(gè)惡意的程序就完成不了企圖了。
               2.2.2 exec( )函數(shù)族
            下面我們來看看一個(gè)進(jìn)程如何來啟動(dòng)另一個(gè)程序的執(zhí)行。在Linux中要使用exec函數(shù)族。系統(tǒng)調(diào)用execve()對(duì)當(dāng)前進(jìn)程進(jìn)行替換,替換者為一個(gè)指 定的程序,其參數(shù)包括文件名(filename)、參數(shù)列表(argv)以及環(huán)境變量(envp)。exec函數(shù)族當(dāng)然不止一個(gè),但它們大致相同,在 Linux中,它們分別是:execl,execlp,execle,execv,execve和execvp,下面我只以execlp為例,其它函數(shù)究 竟與execlp有何區(qū)別,請(qǐng)通過manexec命令來了解它們的具體情況。
            一個(gè)進(jìn)程一旦調(diào)用exec類函數(shù),它本身就"死亡"了,系統(tǒng)把代碼段替換成新的程序的代碼,廢棄原有的數(shù)據(jù)段和堆棧段,并為新程序分配新的數(shù)據(jù)段與堆棧 段,唯一留下的,就是進(jìn)程號(hào),也就是說,對(duì)系統(tǒng)而言,還是同一個(gè)進(jìn)程,不過已經(jīng)是另一個(gè)程序了。(不過exec類函數(shù)中有的還允許繼承環(huán)境變量之類的信 息。)
               那么如果我的程序想啟動(dòng)另一程序的執(zhí)行但自己仍想繼續(xù)運(yùn)行的話,怎么辦呢?那就是結(jié)合fork與exec的使用。下面一段代碼顯示如何啟動(dòng)運(yùn)行其它程序:

            char command[256];
            void main()
            {
            int rtn; /*子進(jìn)程的返回?cái)?shù)值*/
            while(1) {
            /* 從終端讀取要執(zhí)行的命令 */
            printf( ">" );
            fgets( command, 256, stdin );
            command[strlen(command)-1] = 0;
            if ( fork() == 0 ) {
            /* 子進(jìn)程執(zhí)行此命令 */
            execlp( command, command );
            /* 如果exec函數(shù)返回,表明沒有正常執(zhí)行命令,打印錯(cuò)誤信息*/
            perror( command );
            exit( errorno );
            }
            else {
            /* 父進(jìn)程, 等待子進(jìn)程結(jié)束,并打印子進(jìn)程的返回值 */
            wait ( &rtn );
            printf( " child process return %d\n",. rtn );
            }
            }
            }

            此程序從終端讀入命令并執(zhí)行之,執(zhí)行完成后,父進(jìn)程繼續(xù)等待從終端讀入命令。熟悉DOS和WINDOWS系統(tǒng)調(diào)用的朋友一定知道DOS/WINDOWS也 有exec類函數(shù),其使用方法是類似的,但DOS/WINDOWS還有spawn類函數(shù),因?yàn)镈OS是單任務(wù)的系統(tǒng),它只能將"父進(jìn)程"駐留在機(jī)器內(nèi)再執(zhí) 行"子進(jìn)程",這就是spawn類的函數(shù)。WIN32已經(jīng)是多任務(wù)的系統(tǒng)了,但還保留了spawn類函數(shù),WIN32中實(shí)現(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)建一個(gè)管道,通過它來完成程序的標(biāo)準(zhǔn)輸入和標(biāo)準(zhǔn)輸出。這兩個(gè)函數(shù)是為那些不太勤快的程序員設(shè)計(jì) 的,在效率和安全方面都有相當(dāng)?shù)娜毕荩诳赡艿那闆r下,應(yīng)該盡量避免。

              2.3 Linux下的進(jìn)程間通信
            詳細(xì)的講述進(jìn)程間通信在這里絕對(duì)是不可能的事情,而且筆者很難有信心說自己對(duì)這一部分內(nèi)容的認(rèn)識(shí)達(dá)到了什么樣的地步,所以在這一節(jié)的開頭首先向大家推薦著 名作者Richard Stevens的著名作品:《Advanced Programming in the UNIX Environment》,它的中文譯本《UNIX環(huán)境高級(jí)編程》已有機(jī)械工業(yè)出版社出版,原文精彩,譯文同樣地道,如果你的確對(duì)在Linux下編程有濃 厚的興趣,那么趕緊將這本書擺到你的書桌上或計(jì)算機(jī)旁邊來。說這么多實(shí)在是難抑心中的景仰之情,言歸正傳,在這一節(jié)里,我們將介紹進(jìn)程間通信最最初步和最 最簡(jiǎn)單的一些知識(shí)和概念。
            首先,進(jìn)程間通信至少可以通過傳送打開文件來實(shí)現(xiàn),不同的進(jìn)程通過一個(gè)或多個(gè)文件來傳遞信息,事實(shí)上,在很多應(yīng)用系統(tǒng)里,都使用了這種方法。但一般說來, 進(jìn)程間通信(IPC:InterProcess Communication)不包括這種似乎比較低級(jí)的通信方法。Unix系統(tǒng)中實(shí)現(xiàn)進(jìn)程間通信的方法很多,而且不幸的是,極少方法能在所有的Unix系 統(tǒng)中進(jìn)行移植(唯一一種是半雙工的管道,這也是最原始的一種通信方式)。而Linux作為一種新興的操作系統(tǒng),幾乎支持所有的Unix下常用的進(jìn)程間通信 方法:管道、消息隊(duì)列、共享內(nèi)存、信號(hào)量、套接口等等。下面我們將逐一介紹。

               2.3.1 管道
               管道是進(jìn)程間通信中最古老的方式,它包括無名管道和有名管道兩種,前者用于父進(jìn)程和子進(jìn)程間的通信,后者用于運(yùn)行于同一臺(tái)機(jī)器上的任意兩個(gè)進(jìn)程間的通信。
               無名管道由pipe()函數(shù)創(chuàng)建:
               #include <unistd.h>
               int pipe(int filedis[2]);
               參數(shù)filedis返回兩個(gè)文件描述符:filedes[0]為讀而打開,filedes[1]為寫而打開。filedes[1]的輸出是filedes[0]的輸入。下面的例子示范了如何在父進(jìn)程和子進(jìn)程間實(shí)現(xiàn)通信。

            #define INPUT 0
            #define OUTPUT 1

            void main() {
            int file_descriptors[2];
            /*定義子進(jìn)程號(hào) */
            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)前目錄下生成了一個(gè)名為myfifo的有名管道:
                 方式一:mkfifo("myfifo","rw");
                 方式二:mknod myfifo p
               生成了有名管道后,就可以使用一般的文件I/O函數(shù)如open、close、read、write等來對(duì)它進(jìn)行操作。下面即是一個(gè)簡(jiǎn)單的例子,假設(shè)我們已經(jīng)創(chuàng)建了一個(gè)名為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 消息隊(duì)列
               消息隊(duì)列用于運(yùn)行于同一臺(tái)機(jī)器上的進(jìn)程間通信,它和管道很相似,事實(shí)上,它是一種正逐漸被淘汰的通信方式,我們可以用流管道或者套接口的方式來取代它,所以,我們對(duì)此方式也不再解釋,也建議讀者忽略這種方式。

               2.3.3 共享內(nèi)存
            共享內(nèi)存是運(yùn)行在同一臺(tái)機(jī)器上的進(jìn)程間通信最快的方式,因?yàn)閿?shù)據(jù)不需要在不同的進(jìn)程間復(fù)制。通常由一個(gè)進(jìn)程創(chuàng)建一塊共享內(nèi)存區(qū),其余進(jìn)程對(duì)這塊內(nèi)存區(qū)進(jìn)行 讀寫。得到共享內(nèi)存有兩種方式:映射/dev/mem設(shè)備和內(nèi)存映像文件。前一種方式不給系統(tǒng)帶來額外的開銷,但在現(xiàn)實(shí)中并不常用,因?yàn)樗刂拼嫒〉膶⑹? 實(shí)際的物理內(nèi)存,在Linux系統(tǒng)下,這只有通過限制Linux系統(tǒng)存取的內(nèi)存才可以做到,這當(dāng)然不太實(shí)際。常用的方式是通過shmXXX函數(shù)族來實(shí)現(xiàn)利 用共享內(nèi)存進(jìn)行存儲(chǔ)的。
               首先要用的函數(shù)是shmget,它獲得一個(gè)共享存儲(chǔ)標(biāo)識(shí)符。
                 #include <sys/types.h>
                 #include <sys/ipc.h>
                 #include <sys/shm.h>
                  int shmget(key_t key, int size, int flag);
            這個(gè)函數(shù)有點(diǎn)類似大家熟悉的malloc函數(shù),系統(tǒng)按照請(qǐng)求分配size大小的內(nèi)存用作共享內(nèi)存。Linux系統(tǒng)內(nèi)核中每個(gè)IPC結(jié)構(gòu)都有的一個(gè)非負(fù)整數(shù) 的標(biāo)識(shí)符,這樣對(duì)一個(gè)消息隊(duì)列發(fā)送消息時(shí)只要引用標(biāo)識(shí)符就可以了。這個(gè)標(biāo)識(shí)符是內(nèi)核由IPC結(jié)構(gòu)的關(guān)鍵字得到的,這個(gè)關(guān)鍵字,就是上面第一個(gè)函數(shù)的 key。數(shù)據(jù)類型key_t是在頭文件sys/types.h中定義的,它是一個(gè)長(zhǎng)整形的數(shù)據(jù)。在我們后面的章節(jié)中,還會(huì)碰到這個(gè)關(guān)鍵字。
               當(dāng)共享內(nèi)存創(chuàng)建后,其余進(jìn)程可以調(diào)用shmat()將其連接到自身的地址空間中。
               void *shmat(int shmid, void *addr, int flag);
               shmid為shmget函數(shù)返回的共享存儲(chǔ)標(biāo)識(shí)符,addr和flag參數(shù)決定了以什么方式來確定連接的地址,函數(shù)的返回值即是該進(jìn)程數(shù)據(jù)段所連接的實(shí)際地址,進(jìn)程可以對(duì)此進(jìn)程進(jìn)行讀寫操作。
            使用共享存儲(chǔ)來實(shí)現(xiàn)進(jìn)程間通信的注意點(diǎn)是對(duì)數(shù)據(jù)存取的同步,必須確保當(dāng)一個(gè)進(jìn)程去讀取數(shù)據(jù)時(shí),它所想要的數(shù)據(jù)已經(jīng)寫好了。通常,信號(hào)量被要來實(shí)現(xiàn)對(duì)共享存 儲(chǔ)數(shù)據(jù)存取的同步,另外,可以通過使用shmctl函數(shù)設(shè)置共享存儲(chǔ)內(nèi)存的某些標(biāo)志位如SHM_LOCK、SHM_UNLOCK等來實(shí)現(xiàn)。

               2.3.4 信號(hào)量
               信號(hào)量又稱為信號(hào)燈,它是用來協(xié)調(diào)不同進(jìn)程間的數(shù)據(jù)對(duì)象的,而最主要的應(yīng)用是前一節(jié)的共享內(nèi)存方式的進(jìn)程間通信。本質(zhì)上,信號(hào)量是一個(gè)計(jì)數(shù)器,它用來記錄對(duì)某個(gè)資源(如共享內(nèi)存)的存取狀況。一般說來,為了獲得共享資源,進(jìn)程需要執(zhí)行下列操作:
               (1) 測(cè)試控制該資源的信號(hào)量。
               (2) 若此信號(hào)量的值為正,則允許進(jìn)行使用該資源。進(jìn)程將進(jìn)號(hào)量減1。
               (3) 若此信號(hào)量為0,則該資源目前不可用,進(jìn)程進(jìn)入睡眠狀態(tài),直至信號(hào)量值大于0,進(jìn)程被喚醒,轉(zhuǎn)入步驟(1)。
               (4) 當(dāng)進(jìn)程不再使用一個(gè)信號(hào)量控制的資源時(shí),信號(hào)量值加1。如果此時(shí)有進(jìn)程正在睡眠等待此信號(hào)量,則喚醒此進(jìn)程。
            維護(hù)信號(hào)量狀態(tài)的是Linux內(nèi)核操作系統(tǒng)而不是用戶進(jìn)程。我們可以從頭文件/usr/src/linux/include /linux /sem.h 中看到內(nèi)核用來維護(hù)信號(hào)量狀態(tài)的各個(gè)結(jié)構(gòu)的定義。信號(hào)量是一個(gè)數(shù)據(jù)集合,用戶可以單獨(dú)使用這一集合的每個(gè)元素。要調(diào)用的第一個(gè)函數(shù)是semget,用以獲 得一個(gè)信號(hào)量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)建新的信號(hào)量集合,還是引用一個(gè)現(xiàn)有的信號(hào)量集合。nsems是該集合中的信號(hào)量數(shù)。如果是創(chuàng)建新 集合(一般在服務(wù)器中),則必須指定nsems;如果是引用一個(gè)現(xiàn)有的信號(hào)量集合(一般在客戶機(jī)中)則將nsems指定為0。
               semctl函數(shù)用來對(duì)信號(hào)量進(jìn)行操作。
               int semctl(int semid, int semnum, int cmd, union semun arg);
               不同的操作是通過cmd參數(shù)來實(shí)現(xiàn)的,在頭文件sem.h中定義了7種不同的操作,實(shí)際編程時(shí)可以參照使用。
               semop函數(shù)自動(dòng)執(zhí)行信號(hào)量集合上的操作數(shù)組。
               int semop(int semid, struct sembuf semoparray[], size_t nops);
               semoparray是一個(gè)指針,它指向一個(gè)信號(hào)量操作數(shù)組。nops規(guī)定該數(shù)組中操作的數(shù)量。
               下面,我們看一個(gè)具體的例子,它創(chuàng)建一個(gè)特定的IPC結(jié)構(gòu)的關(guān)鍵字和一個(gè)信號(hào)量,建立此信號(hào)量的索引,修改索引指向的信號(hào)量的值,最后我們清除信號(hào)量。在下面的代碼中,函數(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; /* 定義一個(gè)IPC關(guān)鍵字*/
            int id;
            struct sembuf lock_it;
            union semun options;
            int i;

            unique_key = ftok(".", 'a'); /* 生成關(guān)鍵字,字符'a'是一個(gè)隨機(jī)種子*/
            /* 創(chuàng)建一個(gè)新的信號(hào)量集合*/
            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的信號(hào)量*/

            /*打印出信號(hào)量的值*/
            i = semctl(id, 0, GETVAL, 0);
            printf("value of semaphore at index 0 is %d\n", i);

            /*下面重新設(shè)置信號(hào)量*/
            lock_it.sem_num = 0; /*設(shè)置哪個(gè)信號(hào)量*/
            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);

            /*清除信號(hào)量*/
            semctl(id, 0, IPC_RMID, 0);
            }

               2.3.5 套接口
            套接口(socket)編程是實(shí)現(xiàn)Linux系統(tǒng)和其他大多數(shù)操作系統(tǒng)中進(jìn)程間通信的主要方式之一。我們熟知的WWW服務(wù)、FTP服務(wù)、TELNET服務(wù) 等都是基于套接口編程來實(shí)現(xiàn)的。除了在異地的計(jì)算機(jī)進(jìn)程間以外,套接口同樣適用于本地同一臺(tái)計(jì)算機(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è)計(jì)自己的網(wǎng)絡(luò)螞蟻》,那里由常用的幾個(gè)套接口函數(shù)的介紹和示例程序。這一部分或許是Linux進(jìn)程 間通信編程中最須關(guān)注和最吸引人的一部分,畢竟,Internet 正在我們身邊以不可思議的速度發(fā)展著,如果一個(gè)程序員在設(shè)計(jì)編寫他下一個(gè)程序的時(shí)候,根本沒有考慮到網(wǎng)絡(luò),考慮到Internet,那么,可以說,他的設(shè) 計(jì)很難成功。

            3 Linux的進(jìn)程和Win32的進(jìn)程/線程比較
               熟悉WIN32編程的人一定知道,WIN32的進(jìn)程管理方式與Linux上有著很大區(qū)別,在UNIX里,只有進(jìn)程的概念,但在WIN32里卻還有一個(gè)"線程"的概念,那么Linux和WIN32在這里究竟有著什么區(qū)別呢?
            WIN32里的進(jìn)程/線程是繼承自O(shè)S/2的。在WIN32里,"進(jìn)程"是指一個(gè)程序,而"線程"是一個(gè)"進(jìn)程"里的一個(gè)執(zhí)行"線索"。從核心上 講,WIN32的多進(jìn)程與Linux并無多大的區(qū)別,在WIN32里的線程才相當(dāng)于Linux的進(jìn)程,是一個(gè)實(shí)際正在執(zhí)行的代碼。但是,WIN32里同一 個(gè)進(jìn)程里各個(gè)線程之間是共享數(shù)據(jù)段的。這才是與Linux的進(jìn)程最大的不同。
               下面這段程序顯示了WIN32下一個(gè)進(jìn)程如何啟動(dòng)一個(gè)線程。

            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指定一個(gè)函數(shù),線程就從那個(gè)函數(shù)處開始運(yùn)行。此程序同前面的UNIX程序一樣,由兩個(gè)線程各打印1000條信息。 threadID是子線程的線程號(hào),另外,全局變量g是子線程與父線程共享的,這就是與Linux最大的不同之處。大家可以看出,WIN32的進(jìn)程/線程 要比Linux復(fù)雜,在Linux要實(shí)現(xiàn)類似WIN32的線程并不難,只要fork以后,讓子進(jìn)程調(diào)用ThreadProc函數(shù),并且為全局變量開設(shè)共享 數(shù)據(jù)區(qū)就行了,但在WIN32下就無法實(shí)現(xiàn)類似fork的功能了。所以現(xiàn)在WIN32下的C語言編譯器所提供的庫函數(shù)雖然已經(jīng)能兼容大多數(shù) Linux/UNIX的庫函數(shù),但卻仍無法實(shí)現(xiàn)fork。
            對(duì)于多任務(wù)系統(tǒng),共享數(shù)據(jù)區(qū)是必要的,但也是一個(gè)容易引起混亂的問題,在WIN32下,一個(gè)程序員很容易忘記線程之間的數(shù)據(jù)是共享的這一情況,一個(gè)線程修 改過一個(gè)變量后,另一個(gè)線程卻又修改了它,結(jié)果引起程序出問題。但在Linux下,由于變量本來并不共享,而由程序員來顯式地指定要共享的數(shù)據(jù),使程序變 得更清晰與安全。
            至于WIN32的"進(jìn)程"概念,其含義則是"應(yīng)用程序",也就是相當(dāng)于UNIX下的exec了。

            posted on 2008-10-08 15:47 閱讀(1559) 評(píng)論(0)  編輯 收藏 引用 所屬分類: liunx編程技術(shù)

            亚洲综合日韩久久成人AV| 狠狠色综合久久久久尤物| 久久福利片| 亚洲国产成人久久综合一| 久久66热人妻偷产精品9| 久久久久亚洲av无码专区导航| 久久亚洲视频| 久久久高清免费视频| 亚洲女久久久噜噜噜熟女| 久久综合亚洲欧美成人| 国产亚洲欧美精品久久久 | 国产精品美女久久久m| 久久亚洲精品人成综合网| 99999久久久久久亚洲| 99久久免费国产精精品| 久久99国产精品一区二区| 办公室久久精品| 午夜精品久久久久久影视riav| 精品综合久久久久久98| 久久综合88熟人妻| 精品99久久aaa一级毛片| 久久综合视频网站| 午夜人妻久久久久久久久| 国产欧美久久一区二区| 国内精品久久久久国产盗摄| 欧美伊人久久大香线蕉综合69| 伊人色综合久久天天人守人婷| 无码AV中文字幕久久专区| 91精品婷婷国产综合久久 | 久久精品国产91久久麻豆自制 | av色综合久久天堂av色综合在| 婷婷综合久久中文字幕蜜桃三电影| 丰满少妇人妻久久久久久| 久久久久亚洲av成人无码电影| 国产美女亚洲精品久久久综合| 国产韩国精品一区二区三区久久 | 伊人久久精品影院| A级毛片无码久久精品免费| 久久精品中文字幕大胸| 国内精品久久久久久不卡影院| 一本一本久久A久久综合精品|