日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
Linux下的進(jìn)程間通信:共享存儲(chǔ)

學(xué)習(xí)在 Linux 中進(jìn)程是如何與其他進(jìn)程進(jìn)行同步的。

創(chuàng)新互聯(lián)建站專業(yè)為企業(yè)提供吳中網(wǎng)站建設(shè)、吳中做網(wǎng)站、吳中網(wǎng)站設(shè)計(jì)、吳中網(wǎng)站制作等企業(yè)網(wǎng)站建設(shè)、網(wǎng)頁設(shè)計(jì)與制作、吳中企業(yè)網(wǎng)站模板建站服務(wù),10余年吳中做網(wǎng)站經(jīng)驗(yàn),不只是建網(wǎng)站,更提供有價(jià)值的思路和整體網(wǎng)絡(luò)服務(wù)。

本篇是 Linux 下進(jìn)程間通信(IPC)系列的***篇文章。這個(gè)系列將使用 C 語言代碼示例來闡明以下 IPC 機(jī)制:

  • 共享文件
  • 共享內(nèi)存(使用信號(hào)量)
  • 管道(命名的或非命名的管道)
  • 消息隊(duì)列
  • 套接字
  • 信號(hào)

在聚焦上面提到的共享文件和共享內(nèi)存這兩個(gè)機(jī)制之前,這篇文章將帶你回顧一些核心的概念。

核心概念

進(jìn)程是運(yùn)行著的程序,每個(gè)進(jìn)程都有著它自己的地址空間,這些空間由進(jìn)程被允許訪問的內(nèi)存地址組成。進(jìn)程有一個(gè)或多個(gè)執(zhí)行線程,而線程是一系列執(zhí)行指令的集合:單線程進(jìn)程就只有一個(gè)線程,而多線程的進(jìn)程則有多個(gè)線程。一個(gè)進(jìn)程中的線程共享各種資源,特別是地址空間。另外,一個(gè)進(jìn)程中的線程可以直接通過共享內(nèi)存來進(jìn)行通信,盡管某些現(xiàn)代語言(例如 Go)鼓勵(lì)一種更有序的方式,例如使用線程安全的通道。當(dāng)然對(duì)于不同的進(jìn)程,默認(rèn)情況下,它們能共享內(nèi)存。

有多種方法啟動(dòng)之后要進(jìn)行通信的進(jìn)程,下面所舉的例子中主要使用了下面的兩種方法:

  • 一個(gè)終端被用來啟動(dòng)一個(gè)進(jìn)程,另外一個(gè)不同的終端被用來啟動(dòng)另一個(gè)。
  • 在一個(gè)進(jìn)程(父進(jìn)程)中調(diào)用系統(tǒng)函數(shù) fork,以此生發(fā)另一個(gè)進(jìn)程(子進(jìn)程)。

***個(gè)例子采用了上面使用終端的方法。這些代碼示例的 ZIP 壓縮包可以從我的網(wǎng)站下載到。

共享文件

程序員對(duì)文件訪問應(yīng)該都已經(jīng)很熟識(shí)了,包括許多坑(不存在的文件、文件權(quán)限損壞等等),這些問題困擾著程序?qū)ξ募氖褂?。盡管如此,共享文件可能是最為基礎(chǔ)的 IPC 機(jī)制了。考慮一下下面這樣一個(gè)相對(duì)簡(jiǎn)單的例子,其中一個(gè)進(jìn)程(生產(chǎn)者 producer)創(chuàng)建和寫入一個(gè)文件,然后另一個(gè)進(jìn)程(消費(fèi)者 consumer)從這個(gè)相同的文件中進(jìn)行讀取:

 
 
 
  1. writes +-----------+ reads
  2. producer-------->| disk file |<-------consumer
  3. +-----------+

在使用這個(gè) IPC 機(jī)制時(shí)最明顯的挑戰(zhàn)是競(jìng)爭(zhēng)條件可能會(huì)發(fā)生:生產(chǎn)者和消費(fèi)者可能恰好在同一時(shí)間訪問該文件,從而使得輸出結(jié)果不確定。為了避免競(jìng)爭(zhēng)條件的發(fā)生,該文件在處于狀態(tài)時(shí)必須以某種方式處于被鎖狀態(tài),從而阻止在操作執(zhí)行時(shí)和其他操作的沖突。在標(biāo)準(zhǔn)系統(tǒng)庫中與鎖相關(guān)的 API 可以被總結(jié)如下:

  • 生產(chǎn)者應(yīng)該在寫入文件時(shí)獲得一個(gè)文件的排斥鎖。一個(gè)排斥鎖最多被一個(gè)進(jìn)程所擁有。這樣就可以排除掉競(jìng)爭(zhēng)條件的發(fā)生,因?yàn)樵阪i被釋放之前沒有其他的進(jìn)程可以訪問這個(gè)文件。
  • 消費(fèi)者應(yīng)該在從文件中讀取內(nèi)容時(shí)得到至少一個(gè)共享鎖。多個(gè)讀取者可以同時(shí)保有一個(gè)共享鎖,但是沒有寫入者可以獲取到文件內(nèi)容,甚至在當(dāng)只有一個(gè)讀取者保有一個(gè)共享鎖時(shí)。

共享鎖可以提升效率。假如一個(gè)進(jìn)程只是讀入一個(gè)文件的內(nèi)容,而不去改變它的內(nèi)容,就沒有什么原因阻止其他進(jìn)程來做同樣的事。但如果需要寫入內(nèi)容,則很顯然需要文件有排斥鎖。

標(biāo)準(zhǔn)的 I/O 庫中包含一個(gè)名為 fcntl 的實(shí)用函數(shù),它可以被用來檢查或者操作一個(gè)文件上的排斥鎖和共享鎖。該函數(shù)通過一個(gè)文件描述符(一個(gè)在進(jìn)程中的非負(fù)整數(shù)值)來標(biāo)記一個(gè)文件(在不同的進(jìn)程中不同的文件描述符可能標(biāo)記同一個(gè)物理文件)。對(duì)于文件的鎖定, Linux 提供了名為 flock 的庫函數(shù),它是 fcntl 的一個(gè)精簡(jiǎn)包裝。***個(gè)例子中使用 fcntl 函數(shù)來暴露這些 API 細(xì)節(jié)。

示例 1. 生產(chǎn)者程序

 
 
 
  1. #include
  2. #include
  3. #include
  4. #include
  5.  
  6. #define FileName "data.dat"
  7.  
  8. void report_and_exit(const char* msg) {
  9. [perror][4](msg);
  10. [exit][5](-1); /* EXIT_FAILURE */
  11. }
  12.  
  13. int main() {
  14. struct flock lock;
  15. lock.l_type = F_WRLCK; /* read/write (exclusive) lock */
  16. lock.l_whence = SEEK_SET; /* base for seek offsets */
  17. lock.l_start = 0; /* 1st byte in file */
  18. lock.l_len = 0; /* 0 here means 'until EOF' */
  19. lock.l_pid = getpid(); /* process id */
  20.  
  21. int fd; /* file descriptor to identify a file within a process */
  22. if ((fd = open(FileName, O_RDONLY)) < 0) /* -1 signals an error */
  23. report_and_exit("open to read failed...");
  24.  
  25. /* If the file is write-locked, we can't continue. */
  26. fcntl(fd, F_GETLK, &lock); /* sets lock.l_type to F_UNLCK if no write lock */
  27. if (lock.l_type != F_UNLCK)
  28. report_and_exit("file is still write locked...");
  29.  
  30. lock.l_type = F_RDLCK; /* prevents any writing during the reading */
  31. if (fcntl(fd, F_SETLK, &lock) < 0)
  32. report_and_exit("can't get a read-only lock...");
  33.  
  34. /* Read the bytes (they happen to be ASCII codes) one at a time. */
  35. int c; /* buffer for read bytes */
  36. while (read(fd, &c, 1) > 0) /* 0 signals EOF */
  37. write(STDOUT_FILENO, &c, 1); /* write one byte to the standard output */
  38.  
  39. /* Release the lock explicitly. */
  40. lock.l_type = F_UNLCK;
  41. if (fcntl(fd, F_SETLK, &lock) < 0)
  42. report_and_exit("explicit unlocking failed...");
  43.  
  44. close(fd);
  45. return 0;
  46. }

上面生產(chǎn)者程序的主要步驟可以總結(jié)如下:

  • 這個(gè)程序首先聲明了一個(gè)類型為 struct flock 的變量,它代表一個(gè)鎖,并對(duì)它的 5 個(gè)域做了初始化。***個(gè)初始化

       
       
       
    1. lock.l_type = F_WRLCK; /* exclusive lock */

    使得這個(gè)鎖為排斥鎖(read-write)而不是一個(gè)共享鎖(read-only)。假如生產(chǎn)者獲得了這個(gè)鎖,則其他的進(jìn)程將不能夠?qū)ξ募鲎x或者寫操作,直到生產(chǎn)者釋放了這個(gè)鎖,或者顯式地調(diào)用 fcntl,又或者隱式地關(guān)閉這個(gè)文件。(當(dāng)進(jìn)程終止時(shí),所有被它打開的文件都會(huì)被自動(dòng)關(guān)閉,從而釋放了鎖)

  • 上面的程序接著初始化其他的域。主要的效果是整個(gè)文件都將被鎖上。但是,有關(guān)鎖的 API 允許特別指定的字節(jié)被上鎖。例如,假如文件包含多個(gè)文本記錄,則單個(gè)記錄(或者甚至一個(gè)記錄的一部分)可以被鎖,而其余部分不被鎖。

  • ***次調(diào)用 fcntl

       
       
       
    1. if (fcntl(fd, F_SETLK, &lock) < 0)

    嘗試排斥性地將文件鎖住,并檢查調(diào)用是否成功。一般來說, fcntl 函數(shù)返回 -1 (因此小于 0)意味著失敗。第二個(gè)參數(shù) F_SETLK 意味著 fcntl 的調(diào)用不是堵塞的;函數(shù)立即做返回,要么獲得鎖,要么顯示失敗了。假如替換地使用 F_SETLKW(末尾的 W 代指等待),那么對(duì) fcntl 的調(diào)用將是阻塞的,直到有可能獲得鎖的時(shí)候。在調(diào)用 fcntl 函數(shù)時(shí),它的***個(gè)參數(shù) fd 指的是文件描述符,第二個(gè)參數(shù)指定了將要采取的動(dòng)作(在這個(gè)例子中,F_SETLK 指代設(shè)置鎖),第三個(gè)參數(shù)為鎖結(jié)構(gòu)的地址(在本例中,指的是 &lock)。

  • 假如生產(chǎn)者獲得了鎖,這個(gè)程序?qū)⑾蛭募懭雰蓚€(gè)文本記錄。

  • 在向文件寫入內(nèi)容后,生產(chǎn)者改變鎖結(jié)構(gòu)中的 l_type 域?yàn)?unlock 值:

       
       
       
    1. lock.l_type = F_UNLCK;

    并調(diào)用 fcntl 來執(zhí)行解鎖操作。***程序關(guān)閉了文件并退出。

示例 2. 消費(fèi)者程序

 
 
 
  1. #include
  2. #include
  3. #include
  4. #include
  5.  
  6. #define FileName "data.dat"
  7.  
  8. void report_and_exit(const char* msg) {
  9. [perror][4](msg);
  10. [exit][5](-1); /* EXIT_FAILURE */
  11. }
  12.  
  13. int main() {
  14. struct flock lock;
  15. lock.l_type = F_WRLCK; /* read/write (exclusive) lock */
  16. lock.l_whence = SEEK_SET; /* base for seek offsets */
  17. lock.l_start = 0; /* 1st byte in file */
  18. lock.l_len = 0; /* 0 here means 'until EOF' */
  19. lock.l_pid = getpid(); /* process id */
  20.  
  21. int fd; /* file descriptor to identify a file within a process */
  22. if ((fd = open(FileName, O_RDONLY)) < 0) /* -1 signals an error */
  23. report_and_exit("open to read failed...");
  24.  
  25. /* If the file is write-locked, we can't continue. */
  26. fcntl(fd, F_GETLK, &lock); /* sets lock.l_type to F_UNLCK if no write lock */
  27. if (lock.l_type != F_UNLCK)
  28. report_and_exit("file is still write locked...");
  29.  
  30. lock.l_type = F_RDLCK; /* prevents any writing during the reading */
  31. if (fcntl(fd, F_SETLK, &lock) < 0)
  32. report_and_exit("can't get a read-only lock...");
  33.  
  34. /* Read the bytes (they happen to be ASCII codes) one at a time. */
  35. int c; /* buffer for read bytes */
  36. while (read(fd, &c, 1) > 0) /* 0 signals EOF */
  37. write(STDOUT_FILENO, &c, 1); /* write one byte to the standard output */
  38.  
  39. /* Release the lock explicitly. */
  40. lock.l_type = F_UNLCK;
  41. if (fcntl(fd, F_SETLK, &lock) < 0)
  42. report_and_exit("explicit unlocking failed...");
  43.  
  44. close(fd);
  45. return 0;
  46. }

相比于鎖的 API,消費(fèi)者程序會(huì)相對(duì)復(fù)雜一點(diǎn)兒。特別的,消費(fèi)者程序首先檢查文件是否被排斥性的被鎖,然后才嘗試去獲得一個(gè)共享鎖。相關(guān)的代碼為:

 
 
 
  1. lock.l_type = F_WRLCK;
  2. ...
  3. fcntl(fd, F_GETLK, &lock); /* sets lock.l_type to F_UNLCK if no write lock */
  4. if (lock.l_type != F_UNLCK)
  5. report_and_exit("file is still write locked...");

fcntl 調(diào)用中的 F_GETLK 操作指定檢查一個(gè)鎖,在本例中,上面代碼的聲明中給了一個(gè) F_WRLCK 的排斥鎖。假如特指的鎖不存在,那么 fcntl 調(diào)用將會(huì)自動(dòng)地改變鎖類型域?yàn)?F_UNLCK 以此來顯示當(dāng)前的狀態(tài)。假如文件是排斥性地被鎖,那么消費(fèi)者將會(huì)終止。(一個(gè)更健壯的程序版本或許應(yīng)該讓消費(fèi)者會(huì)兒,然后再嘗試幾次。)

假如當(dāng)前文件沒有被鎖,那么消費(fèi)者將嘗試獲取一個(gè)共享(read-only)鎖(F_RDLCK)。為了縮短程序,fcntl 中的 F_GETLK 調(diào)用可以丟棄,因?yàn)榧偃缙渌M(jìn)程已經(jīng)保有一個(gè)讀寫鎖,F_RDLCK 的調(diào)用就可能會(huì)失敗。重新調(diào)用一個(gè)只讀鎖能夠阻止其他進(jìn)程向文件進(jìn)行寫的操作,但可以允許其他進(jìn)程對(duì)文件進(jìn)行讀取。簡(jiǎn)而言之,共享鎖可以被多個(gè)進(jìn)程所保有。在獲取了一個(gè)共享鎖后,消費(fèi)者程序?qū)⒘⒓磸奈募凶x取字節(jié)數(shù)據(jù),然后在標(biāo)準(zhǔn)輸出中打印這些字節(jié)的內(nèi)容,接著釋放鎖,關(guān)閉文件并終止。

下面的 % 為命令行提示符,下面展示的是從相同終端開啟這兩個(gè)程序的輸出:

 
 
 
  1. % ./producer
  2. Process 29255 has written to data file...
  3.  
  4. % ./consumer
  5. Now is the winter of our discontent
  6. Made glorious summer by this sun of York

在本次的代碼示例中,通過 IPC 傳輸?shù)臄?shù)據(jù)是文本:它們來自莎士比亞的戲劇《理查三世》中的兩行臺(tái)詞。然而,共享文件的內(nèi)容還可以是紛繁復(fù)雜的,任意的字節(jié)數(shù)據(jù)(例如一個(gè)電影)都可以,這使得文件共享變成了一個(gè)非常靈活的 IPC 機(jī)制。但它的缺點(diǎn)是文件獲取速度較慢,因?yàn)槲募墨@取涉及到讀或者寫。同往常一樣,編程總是伴隨著折中。下面的例子將通過共享內(nèi)存來做 IPC,而不是通過共享文件,在性能上相應(yīng)的有極大的提升。

共享內(nèi)存

對(duì)于共享內(nèi)存,Linux 系統(tǒng)提供了兩類不同的 API:傳統(tǒng)的 System V API 和更新一點(diǎn)的 POSIX API。在單個(gè)應(yīng)用中,這些 API 不能混用。但是,POSIX 方式的一個(gè)壞處是它的特性仍在發(fā)展中,并且依賴于安裝的內(nèi)核版本,這非常影響代碼的可移植性。例如,默認(rèn)情況下,POSIX API 用內(nèi)存映射文件來實(shí)現(xiàn)共享內(nèi)存:對(duì)于一個(gè)共享的內(nèi)存段,系統(tǒng)為相應(yīng)的內(nèi)容維護(hù)一個(gè)備份文件。在 POSIX 規(guī)范下共享內(nèi)存可以被配置為不需要備份文件,但這可能會(huì)影響可移植性。我的例子中使用的是帶有備份文件的 POSIX API,這既結(jié)合了內(nèi)存獲取的速度優(yōu)勢(shì),又獲得了文件存儲(chǔ)的持久性。

下面的共享內(nèi)存例子中包含兩個(gè)程序,分別名為 memwritermemreader,并使用信號(hào)量來調(diào)整它們對(duì)共享內(nèi)存的獲取。在任何時(shí)候當(dāng)共享內(nèi)存進(jìn)入一個(gè)寫入者場(chǎng)景時(shí),無論是多進(jìn)程還是多線程,都有遇到基于內(nèi)存的競(jìng)爭(zhēng)條件的風(fēng)險(xiǎn),所以,需要引入信號(hào)量來協(xié)調(diào)(同步)對(duì)共享內(nèi)存的獲取。

memwriter 程序應(yīng)當(dāng)在它自己所處的終端首先啟動(dòng),然后 memreader 程序才可以在它自己所處的終端啟動(dòng)(在接著的十幾秒內(nèi))。memreader 的輸出如下:

 
 
 
  1. This is the way the world ends...

在每個(gè)源程序的最上方注釋部分都解釋了在編譯它們時(shí)需要添加的鏈接參數(shù)。

首先讓我們復(fù)習(xí)一下信號(hào)量是如何作為一個(gè)同步機(jī)制工作的。一般的信號(hào)量也被叫做一個(gè)計(jì)數(shù)信號(hào)量,因?yàn)閹в幸粋€(gè)可以增加的值(通常初始化為 0)??紤]一家租用自行車的商店,在它的庫存中有 100 輛自行車,還有一個(gè)供職員用于租賃的程序。每當(dāng)一輛自行車被租出去,信號(hào)量就增加 1;當(dāng)一輛自行車被還回來,信號(hào)量就減 1。在信號(hào)量的值為 100 之前都還可以進(jìn)行租賃業(yè)務(wù),但如果等于 100 時(shí),就必須停止業(yè)務(wù),直到至少有一輛自行車被還回來,從而信號(hào)量減為 99。

二元信號(hào)量是一個(gè)特例,它只有兩個(gè)值:0 和 1。在這種情況下,信號(hào)量的表現(xiàn)為互斥量(一個(gè)互斥的構(gòu)造)。下面的共享內(nèi)存示例將把信號(hào)量用作互斥量。當(dāng)信號(hào)量的值為 0 時(shí),只有 memwriter 可以獲取共享內(nèi)存,在寫操作完成后,這個(gè)進(jìn)程將增加信號(hào)量的值,從而允許 memreader 來讀取共享內(nèi)存。

示例 3. memwriter 進(jìn)程的源程序

 
 
 
  1. /** Compilation: gcc -o memwriter memwriter.c -lrt -lpthread **/
  2. #include
  3. #include
  4. #include
  5. #include
  6. #include
  7. #include
  8. #include
  9. #include
  10. #include "shmem.h"
  11.  
  12. void report_and_exit(const char* msg) {
  13. [perror][4](msg);
  14. [exit][5](-1);
  15. }
  16.  
  17. int main() {
  18. int fd = shm_open(BackingFile, /* name from smem.h */
  19. O_RDWR | O_CREAT, /* read/write, create if needed */
  20. AccessPerms); /* access permissions (0644) */
  21. if (fd < 0) report_and_exit("Can't open shared mem segment...");
  22.  
  23. ftruncate(fd, ByteSize); /* get the bytes */
  24.  
  25. caddr_t memptr = mmap(NULL, /* let system pick where to put segment */
  26. ByteSize, /* how many bytes */
  27. PROT_READ | PROT_WRITE, /* access protections */
  28. MAP_SHARED, /* mapping visible to other processes */
  29. fd, /* file descriptor */
  30. 0); /* offset: start at 1st byte */
  31. if ((caddr_t) -1 == memptr) report_and_exit("Can't get segment...");
  32.  
  33. [fprintf][7](stderr, "shared mem address: %p [0..%d]\n", memptr, ByteSize - 1);
  34. [fprintf][7](stderr, "backing file: /dev/shm%s\n", BackingFile );
  35.  
  36. /* semahore code to lock the shared mem */
  37. sem_t* semptr = sem_open(SemaphoreName, /* name */
  38. O_CREAT, /* create the semaphore */
  39. AccessPerms, /* protection perms */
  40. 0); /* initial value */
  41. if (semptr == (void*) -1) report_and_exit("sem_open");
  42.  
  43. [strcpy][8](memptr, MemContents); /* copy some ASCII bytes to the segment */
  44.  
  45. /* increment the semaphore so that memreader can read */
  46. if (sem_post(semptr) < 0) report_and_exit("sem_post");
  47.  
  48. sleep(12); /* give reader a chance */
  49.  
  50. /* clean up */
  51. munmap(memptr, ByteSize); /* unmap the storage */
  52. close(fd);
  53. sem_close(semptr);
  54. shm_unlink(BackingFile); /* unlink from the backing file */
  55. return 0;
  56. }

下面是 memwritermemreader 程序如何通過共享內(nèi)存來通信的一個(gè)總結(jié):

  • 上面展示的 memwriter 程序調(diào)用 shm_open 函數(shù)來得到作為系統(tǒng)協(xié)調(diào)共享內(nèi)存的備份文件的文件描述符。此時(shí),并沒有內(nèi)存被分配。接下來調(diào)用的是令人誤解的名為 ftruncate 的函數(shù)

       
       
       
    1. ftruncate(fd, ByteSize); /* get the bytes */

    它將分配 ByteSize 字節(jié)的內(nèi)存,在該情況下,一般為大小適中的 512 字節(jié)。memwritermemreader 程序都只從共享內(nèi)存中獲取數(shù)據(jù),而不是從備份文件。系統(tǒng)將負(fù)責(zé)共享內(nèi)存和備份文件之間數(shù)據(jù)的同步。

  • 接著 memwriter 調(diào)用 mmap 函數(shù):

       
       
       
    1. caddr_t memptr = mmap(NULL, /* let system pick where to put segment */
    2. ByteSize, /* how many bytes */
    3. PROT_READ | PROT_WRITE, /* access protections */
    4. MAP_SHARED, /* mapping visible to other processes */
    5. fd, /* file descriptor */
    6. 0); /* offset: start at 1st byte */

    來獲得共享內(nèi)存的指針。(memreader 也做一次類似的調(diào)用。) 指針類型 caddr_tc 開頭,它代表 calloc,而這是動(dòng)態(tài)初始化分配的內(nèi)存為 0 的一個(gè)系統(tǒng)函數(shù)。memwriter 通過庫函數(shù) strcpy(字符串復(fù)制)來獲取后續(xù)操作的 memptr。

  • 到現(xiàn)在為止,memwriter 已經(jīng)準(zhǔn)備好進(jìn)行寫操作了,但首先它要?jiǎng)?chuàng)建一個(gè)信號(hào)量來確保共享內(nèi)存的排斥性。假如 memwriter 正在執(zhí)行寫操作而同時(shí) memreader 在執(zhí)行讀操作,則有可能出現(xiàn)競(jìng)爭(zhēng)條件。假如調(diào)用 sem_open 成功了:

       
       
       
    1. sem_t* semptr = sem_open(SemaphoreName, /* name */
    2. O_CREAT, /* create the semaphore */
    3. AccessPerms, /* protection perms */
    4. 0); /* initial value */

    那么,接著寫操作便可以執(zhí)行。上面的 SemaphoreName(任意一個(gè)唯一的非空名稱)用來在 memwritermemreader 識(shí)別信號(hào)量。初始值 0 將會(huì)傳遞給信號(hào)量的創(chuàng)建者,在這個(gè)例子中指的是 memwriter 賦予它執(zhí)行操作的權(quán)利。

  • 在寫操作完成后,memwriter* 通過調(diào)用sem_post` 函數(shù)將信號(hào)量的值增加到 1:

       
       
       
    1. if (sem_post(semptr) < 0) ..

    增加信號(hào)了將釋放互斥鎖,使得 memreader 可以執(zhí)行它的操作。為了更好地測(cè)量,memwriter 也將從它自己的地址空間中取消映射,

       
       
       
    1. munmap(memptr, ByteSize); /* unmap the storage *

    這將使得 memwriter 不能進(jìn)一步地訪問共享內(nèi)存。

示例 4. memreader 進(jìn)程的源代碼

 
 
 
  1. /** Compilation: gcc -o memreader memreader.c -lrt -lpthread **/
  2. #include
  3. #include
  4. #include
  5. #include
  6. #include
  7. #include
  8. #include
  9. #include
  10. #include "shmem.h"
  11.  
  12. void report_and_exit(const char* msg) {
  13. [perror][4](msg);
  14. [exit][5](-1);
  15. }
  16.  
  17. int main() {
  18. int fd = shm_open(BackingFile, O_RDWR, AccessPerms); /* empty to begin */
  19. if (fd < 0) report_and_exit("Can't get file descriptor...");
  20.  
  21. /* get a pointer to memory */
  22. caddr_t memptr = mmap(NULL, /* let system pick where to put segment */
  23. ByteSize, /* how many bytes */
  24. PROT_READ | PROT_WRITE, /* access protections */
  25. MAP_SHARED, /* mapping visible to other processes */
  26. fd, /* file descriptor */
  27. 0); /* offset: start at 1st byte */
  28. if ((caddr_t) -1 == memptr) report_and_exit("Can't access segment...");
  29.  
  30. /* create a semaphore for mutual exclusion */
  31. sem_t* semptr = sem_open(SemaphoreName, /* name */
  32. O_CREAT, /* create the semaphore */
  33. AccessPerms, /* protection perms */
  34. 0); /* initial value */
  35. if (semptr == (void*) -1) report_and_exit("sem_open");
  36.  
  37. /* use semaphore as a mutex (lock) by waiting for writer to increment it */
  38. if (!sem_wait(semptr)) { /* wait until semaphore != 0 */
  39. int i;
  40. for (i = 0; i < [strlen][6](MemContents); i++)
  41. write(STDOUT_FILENO, memptr + i, 1); /* one byte at a time */
  42. sem_post(semptr);
  43. }
  44.  
  45. /* cleanup */
  46. munmap(memptr, ByteSize);
  47. close(fd);
  48. sem_close(semptr);
  49. unlink(BackingFile);
  50. return 0;
  51. }

memwritermemreader 程序中,共享內(nèi)存的主要著重點(diǎn)都在 shm_openmmap 函數(shù)上:在成功時(shí),***個(gè)調(diào)用返回一個(gè)備份文件的文件描述符,而第二個(gè)調(diào)用則使用這個(gè)文件描述符從共享內(nèi)存段中獲取一個(gè)指針。它們對(duì) shm_open 的調(diào)用都很相似,除了 memwriter 程序創(chuàng)建共享內(nèi)存,而 `memreader 只獲取這個(gè)已經(jīng)創(chuàng)建的內(nèi)存:

 
 
 
  1. int fd = shm_open(BackingFile, O_RDWR | O_CREAT, AccessPerms); /* memwriter */
  2. int fd = shm_open(BackingFile, O_RDWR, AccessPerms); /* memreader */

有了文件描述符,接著對(duì) mmap 的調(diào)用就是類似的了:

 
 
 
  1. caddr_t memptr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);

mmap 的***個(gè)參數(shù)為 NULL,這意味著讓系統(tǒng)自己決定在虛擬內(nèi)存地址的哪個(gè)地方分配內(nèi)存,當(dāng)然也可以指定一個(gè)地址(但很有技巧性)。MAP_SHARED 標(biāo)志著被分配的內(nèi)存在進(jìn)程中是共享的,***一個(gè)參數(shù)(在這個(gè)例子中為 0 ) 意味著共享內(nèi)存的偏移量應(yīng)該為***個(gè)字節(jié)。size 參數(shù)特別指定了將要分配的字節(jié)數(shù)目(在這個(gè)例子中是 512);另外的保護(hù)參數(shù)(AccessPerms)暗示著共享內(nèi)存是可讀可寫的。

當(dāng) memwriter 程序執(zhí)行成功后,系統(tǒng)將創(chuàng)建并維護(hù)備份文件,在我的系統(tǒng)中,該文件為 /dev/shm/shMemEx,其中的 shMemEx 是我為共享存儲(chǔ)命名的(在頭文件 shmem.h 中給定)。在當(dāng)前版本的 memwritermemreader 程序中,下面的語句

 
 
 
  1. shm_unlink(BackingFile); /* removes backing file */

將會(huì)移除備份文件。假如沒有 unlink 這個(gè)語句,則備份文件在程序終止后仍然持久地保存著。

memreadermemwriter 一樣,在調(diào)用 sem_open 函數(shù)時(shí),通過信號(hào)量的名字來獲取信號(hào)量。但 memreader 隨后將進(jìn)入等待狀態(tài),直到 memwriter 將初始值為 0 的信號(hào)量的值增加。

 
 
 
  1. if (!sem_wait(semptr)) { /* wait until semaphore != 0 */

一旦等待結(jié)束,memreader 將從共享內(nèi)存中讀取 ASCII 數(shù)據(jù),然后做些清理工作并終止。

共享內(nèi)存 API 包括顯式地同步共享內(nèi)存段和備份文件。在這次的示例中,這些操作都被省略了,以免文章顯得雜亂,好讓我們專注于內(nèi)存共享和信號(hào)量的代碼。

即便在信號(hào)量代碼被移除的情況下,memwritermemreader 程序很大幾率也能夠正常執(zhí)行而不會(huì)引入競(jìng)爭(zhēng)條件:memwriter 創(chuàng)建了共享內(nèi)存段,然后立即向它寫入;memreader 不能訪問共享內(nèi)存,直到共享內(nèi)存段被創(chuàng)建好。然而,當(dāng)一個(gè)寫操作處于混合狀態(tài)時(shí),***實(shí)踐需要共享內(nèi)存被同步。信號(hào)量 API 足夠重要,值得在代碼示例中著重強(qiáng)調(diào)。

總結(jié)

上面共享文件和共享內(nèi)存的例子展示了進(jìn)程是怎樣通過共享存儲(chǔ)來進(jìn)行通信的,前者通過文件而后者通過內(nèi)存塊。這兩種方法的 API 相對(duì)來說都很直接。這兩種方法有什么共同的缺點(diǎn)嗎?現(xiàn)代的應(yīng)用經(jīng)常需要處理流數(shù)據(jù),而且是非常大規(guī)模的數(shù)據(jù)流。共享文件或者共享內(nèi)存的方法都不能很好地處理大規(guī)模的流數(shù)據(jù)。按照類型使用管道會(huì)更加合適一些。所以這個(gè)系列的第二部分將會(huì)介紹管道和消息隊(duì)列,同樣的,我們將使用 C 語言寫的代碼示例來輔助講解。



分享文章:Linux下的進(jìn)程間通信:共享存儲(chǔ)
文章源于:http://www.5511xx.com/article/dhsjogg.html