新聞中心
學(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)頁(yè)設(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 語(yǔ)言代碼示例來闡明以下 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)代語(yǔ)言(例如 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)行讀?。?/p>
writes +-----------+ readsproducer-------->| disk file |<-------consumer+-----------+
在使用這個(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)庫(kù)中與鎖相關(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 庫(kù)中包含一個(gè)名為 fcntl 的實(shí)用函數(shù),它可以被用來檢查或者操作一個(gè)文件上的排斥鎖和共享鎖。該函數(shù)通過一個(gè)文件描述符(一個(gè)在進(jìn)程中的非負(fù)整數(shù)值)來標(biāo)記一個(gè)文件(在不同的進(jìn)程中不同的文件描述符可能標(biāo)記同一個(gè)物理文件)。對(duì)于文件的鎖定, Linux 提供了名為 flock 的庫(kù)函數(shù),它是 fcntl 的一個(gè)精簡(jiǎn)包裝。***個(gè)例子中使用 fcntl 函數(shù)來暴露這些 API 細(xì)節(jié)。
示例 1. 生產(chǎn)者程序
#include#include#include#include#define FileName "data.dat"void report_and_exit(const char* msg) {[perror][4](msg);[exit][5](-1); /* EXIT_FAILURE */}int main() {struct flock lock;lock.l_type = F_WRLCK; /* read/write (exclusive) lock */lock.l_whence = SEEK_SET; /* base for seek offsets */lock.l_start = 0; /* 1st byte in file */lock.l_len = 0; /* 0 here means 'until EOF' */lock.l_pid = getpid(); /* process id */int fd; /* file descriptor to identify a file within a process */if ((fd = open(FileName, O_RDONLY)) < 0) /* -1 signals an error */report_and_exit("open to read failed...");/* If the file is write-locked, we can't continue. */fcntl(fd, F_GETLK, &lock); /* sets lock.l_type to F_UNLCK if no write lock */if (lock.l_type != F_UNLCK)report_and_exit("file is still write locked...");lock.l_type = F_RDLCK; /* prevents any writing during the reading */if (fcntl(fd, F_SETLK, &lock) < 0)report_and_exit("can't get a read-only lock...");/* Read the bytes (they happen to be ASCII codes) one at a time. */int c; /* buffer for read bytes */while (read(fd, &c, 1) > 0) /* 0 signals EOF */write(STDOUT_FILENO, &c, 1); /* write one byte to the standard output *//* Release the lock explicitly. */lock.l_type = F_UNLCK;if (fcntl(fd, F_SETLK, &lock) < 0)report_and_exit("explicit unlocking failed...");close(fd);return 0;}
上面生產(chǎn)者程序的主要步驟可以總結(jié)如下:
-
這個(gè)程序首先聲明了一個(gè)類型為
struct flock的變量,它代表一個(gè)鎖,并對(duì)它的 5 個(gè)域做了初始化。***個(gè)初始化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)用
fcntlif (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值:lock.l_type = F_UNLCK;
并調(diào)用
fcntl來執(zhí)行解鎖操作。***程序關(guān)閉了文件并退出。
示例 2. 消費(fèi)者程序
#include#include#include#include#define FileName "data.dat"void report_and_exit(const char* msg) {[perror][4](msg);[exit][5](-1); /* EXIT_FAILURE */}int main() {struct flock lock;lock.l_type = F_WRLCK; /* read/write (exclusive) lock */lock.l_whence = SEEK_SET; /* base for seek offsets */lock.l_start = 0; /* 1st byte in file */lock.l_len = 0; /* 0 here means 'until EOF' */lock.l_pid = getpid(); /* process id */int fd; /* file descriptor to identify a file within a process */if ((fd = open(FileName, O_RDONLY)) < 0) /* -1 signals an error */report_and_exit("open to read failed...");/* If the file is write-locked, we can't continue. */fcntl(fd, F_GETLK, &lock); /* sets lock.l_type to F_UNLCK if no write lock */if (lock.l_type != F_UNLCK)report_and_exit("file is still write locked...");lock.l_type = F_RDLCK; /* prevents any writing during the reading */if (fcntl(fd, F_SETLK, &lock) < 0)report_and_exit("can't get a read-only lock...");/* Read the bytes (they happen to be ASCII codes) one at a time. */int c; /* buffer for read bytes */while (read(fd, &c, 1) > 0) /* 0 signals EOF */write(STDOUT_FILENO, &c, 1); /* write one byte to the standard output *//* Release the lock explicitly. */lock.l_type = F_UNLCK;if (fcntl(fd, F_SETLK, &lock) < 0)report_and_exit("explicit unlocking failed...");close(fd);return 0;}
相比于鎖的 API,消費(fèi)者程序會(huì)相對(duì)復(fù)雜一點(diǎn)兒。特別的,消費(fèi)者程序首先檢查文件是否被排斥性的被鎖,然后才嘗試去獲得一個(gè)共享鎖。相關(guān)的代碼為:
lock.l_type = F_WRLCK;...fcntl(fd, F_GETLK, &lock); /* sets lock.l_type to F_UNLCK if no write lock */if (lock.l_type != F_UNLCK)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è)程序的輸出:
% ./producerProcess 29255 has written to data file...% ./consumerNow is the winter of our discontentMade 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è)程序,分別名為 memwriter 和 memreader,并使用信號(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 的輸出如下:
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)??紤]一家租用自行車的商店,在它的庫(kù)存中有 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)程的源程序
/** Compilation: gcc -o memwriter memwriter.c -lrt -lpthread **/#include#include#include#include#include#include#include#include#include "shmem.h"void report_and_exit(const char* msg) {[perror][4](msg);[exit][5](-1);}int main() {int fd = shm_open(BackingFile, /* name from smem.h */O_RDWR | O_CREAT, /* read/write, create if needed */AccessPerms); /* access permissions (0644) */if (fd < 0) report_and_exit("Can't open shared mem segment...");ftruncate(fd, ByteSize); /* get the bytes */caddr_t memptr = mmap(NULL, /* let system pick where to put segment */ByteSize, /* how many bytes */PROT_READ | PROT_WRITE, /* access protections */MAP_SHARED, /* mapping visible to other processes */fd, /* file descriptor */0); /* offset: start at 1st byte */if ((caddr_t) -1 == memptr) report_and_exit("Can't get segment...");[fprintf][7](stderr, "shared mem address: %p [0..%d]\n", memptr, ByteSize - 1);[fprintf][7](stderr, "backing file: /dev/shm%s\n", BackingFile );/* semahore code to lock the shared mem */sem_t* semptr = sem_open(SemaphoreName, /* name */O_CREAT, /* create the semaphore */AccessPerms, /* protection perms */0); /* initial value */if (semptr == (void*) -1) report_and_exit("sem_open");[strcpy][8](memptr, MemContents); /* copy some ASCII bytes to the segment *//* increment the semaphore so that memreader can read */if (sem_post(semptr) < 0) report_and_exit("sem_post");sleep(12); /* give reader a chance *//* clean up */munmap(memptr, ByteSize); /* unmap the storage */close(fd);sem_close(semptr);shm_unlink(BackingFile); /* unlink from the backing file */return 0;}
下面是 memwriter 和 memreader 程序如何通過共享內(nèi)存來通信的一個(gè)總結(jié):
-
上面展示的
memwriter程序調(diào)用shm_open函數(shù)來得到作為系統(tǒng)協(xié)調(diào)共享內(nèi)存的備份文件的文件描述符。此時(shí),并沒有內(nèi)存被分配。接下來調(diào)用的是令人誤解的名為ftruncate的函數(shù)ftruncate(fd, ByteSize); /* get the bytes */
它將分配
ByteSize字節(jié)的內(nèi)存,在該情況下,一般為大小適中的 512 字節(jié)。memwriter和memreader程序都只從共享內(nèi)存中獲取數(shù)據(jù),而不是從備份文件。系統(tǒng)將負(fù)責(zé)共享內(nèi)存和備份文件之間數(shù)據(jù)的同步。 -
接著
memwriter調(diào)用mmap函數(shù):caddr_t memptr = mmap(NULL, /* let system pick where to put segment */ByteSize, /* how many bytes */PROT_READ | PROT_WRITE, /* access protections */MAP_SHARED, /* mapping visible to other processes */fd, /* file descriptor */0); /* offset: start at 1st byte */
來獲得共享內(nèi)存的指針。(
memreader也做一次類似的調(diào)用。) 指針類型caddr_t以c開頭,它代表calloc,而這是動(dòng)態(tài)初始化分配的內(nèi)存為 0 的一個(gè)系統(tǒng)函數(shù)。memwriter通過庫(kù)函數(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成功了:sem_t* semptr = sem_open(SemaphoreName, /* name */O_CREAT, /* create the semaphore */AccessPerms, /* protection perms */0); /* initial value */
那么,接著寫操作便可以執(zhí)行。上面的
SemaphoreName(任意一個(gè)唯一的非空名稱)用來在memwriter和memreader識(shí)別信號(hào)量。初始值 0 將會(huì)傳遞給信號(hào)量的創(chuàng)建者,在這個(gè)例子中指的是memwriter賦予它執(zhí)行寫操作的權(quán)利。 -
在寫操作完成后,
memwriter* 通過調(diào)用sem_post` 函數(shù)將信號(hào)量的值增加到 1:if (sem_post(semptr) < 0) ..
增加信號(hào)了將釋放互斥鎖,使得
memreader可以執(zhí)行它的讀操作。為了更好地測(cè)量,memwriter也將從它自己的地址空間中取消映射,munmap(memptr, ByteSize); /* unmap the storage *
這將使得
memwriter不能進(jìn)一步地訪問共享內(nèi)存。
示例 4. memreader 進(jìn)程的源代碼
/** Compilation: gcc -o memreader memreader.c -lrt -lpthread **/#include#include#include#include#include#include#include#include#include "shmem.h"void report_and_exit(const char* msg) {[perror][4](msg);[exit][5](-1);}int main() {int fd = shm_open(BackingFile, O_RDWR, AccessPerms); /* empty to begin */if (fd < 0) report_and_exit("Can't get file descriptor...");/* get a pointer to memory */caddr_t memptr = mmap(NULL, /* let system pick where to put segment */ByteSize, /* how many bytes */PROT_READ | PROT_WRITE, /* access protections */MAP_SHARED, /* mapping visible to other processes */fd, /* file descriptor */0); /* offset: start at 1st byte */if ((caddr_t) -1 == memptr) report_and_exit("Can't access segment...");/* create a semaphore for mutual exclusion */sem_t* semptr = sem_open(SemaphoreName, /* name */O_CREAT, /* create the semaphore */AccessPerms, /* protection perms */0); /* initial value */if (semptr == (void*) -1) report_and_exit("sem_open");/* use semaphore as a mutex (lock) by waiting for writer to increment it */if (!sem_wait(semptr)) { /* wait until semaphore != 0 */int i;for (i = 0; i < [strlen][6](MemContents); i++)write(STDOUT_FILENO, memptr + i, 1); /* one byte at a time */sem_post(semptr);}/* cleanup */munmap(memptr, ByteSize);close(fd);sem_close(semptr);unlink(BackingFile);return 0;}
memwriter 和 memreader 程序中,共享內(nèi)存的主要著重點(diǎn)都在 shm_open 和 mmap 函數(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)存:
int fd = shm_open(BackingFile, O_RDWR | O_CREAT, AccessPerms); /* memwriter */int fd = shm_open(BackingFile, O_RDWR, AccessPerms); /* memreader */
有了文件描述符,接著對(duì) mmap 的調(diào)用就是類似的了:
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)前版本的 memwriter 和 memreader 程序中,下面的語(yǔ)句
shm_unlink(BackingFile); /* removes backing file */
將會(huì)移除備份文件。假如沒有 unlink 這個(gè)語(yǔ)句,則備份文件在程序終止后仍然持久地保存著。
memreader 和 memwriter 一樣,在調(diào)用 sem_open 函數(shù)時(shí),通過信號(hào)量的名字來獲取信號(hào)量。但 memreader 隨后將進(jìn)入等待狀態(tài),直到 memwriter 將初始值為 0 的信號(hào)量的值增加。
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)量代碼被移除的情況下,memwriter 和 memreader 程序很大幾率也能夠正常執(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 語(yǔ)言寫的代碼示例來輔助講解。
網(wǎng)站名稱:Linux下的進(jìn)程間通信:共享存儲(chǔ)
本文鏈接:http://m.fisionsoft.com.cn/article/dhsjogg.html


咨詢
建站咨詢
