使用 UNIX System V IPC 機(jī)制共享應(yīng)用程序數(shù)據(jù)
引言
Unix 內(nèi)核管理的進(jìn)程自主地操作,從而產(chǎn)生更穩(wěn)定的系統(tǒng)。然而,每個(gè)開發(fā)人員最終都會(huì)遇到這樣的情況,即其中一組進(jìn)程需要與另一組進(jìn)程通信,也許是為了交換數(shù)據(jù)或發(fā)送命令。這種通信稱為進(jìn)程間通信(Inter-Process Communication,IPC)。System V (SysV) UNIX 規(guī)范描述了以下三種 IPC 機(jī)制,它們統(tǒng)稱為 SysV IPC:
消息隊(duì)列
信號(hào)量
共享內(nèi)存
此外,進(jìn)程還可以通過(guò)其他機(jī)制通信,例如:
讀、寫和鎖定文件
信號(hào)
套接字
管道
FIFO(先進(jìn)先出)
這后一組機(jī)制一般也稱為 IPC。由于其簡(jiǎn)單性和有效性,本文將集中于 SysV IPC 方法。
了解 SysV 模型
三種 SysV IPC 方法具有類似的語(yǔ)法,盡管它們具有不同的用途。一般情況下,它們執(zhí)行以下操作:
確定要用于 ftok(3) 的正確 IPC 密鑰。
分別使用 msgget(2)、semget(2) 或 shmget(2) 獲得用于消息隊(duì)列、信號(hào)量或共享內(nèi)存的特定于 IPC 的標(biāo)識(shí)符,這些標(biāo)識(shí)符與 IPC 密鑰相關(guān)聯(lián)。
使用 msgctl(2)、semctl(2) 或 shmctl(2) 修改 IPC 實(shí)例的屬性。
利用特定的 IPC 實(shí)例。
最后,使用 msgctl(2)、semctl(2) 或 shmctl(2) 和 IPC_RMID 標(biāo)志銷毀 IPC 實(shí)例。
每個(gè) IPC 實(shí)例都被賦予一個(gè)標(biāo)識(shí)符,以將它與系統(tǒng)上存在的其他 IPC 實(shí)例區(qū)分開來(lái)。例如,兩個(gè)不同的應(yīng)用程序可能分別決定使用共享內(nèi)存段,因此系統(tǒng)范圍的 IPC ID 將區(qū)分這兩個(gè)實(shí)例。雖然可能不是那么明顯,但是第一個(gè)挑戰(zhàn)就是弄清如何分發(fā)這樣的信息:即如何在沒(méi)有準(zhǔn)備某種 IPC 機(jī)制的情況下附加到一個(gè)公共 IPC 實(shí)例。
ftok 庫(kù)調(diào)用使用某個(gè)給定文件中的索引節(jié)點(diǎn)信息和一個(gè)唯一標(biāo)識(shí)符來(lái)得出一個(gè)密鑰,只要該文件存在并且該標(biāo)識(shí)符為常量,此密鑰就保持相同。因此,兩個(gè)進(jìn)程可以使用它們的配置文件和編譯時(shí)常量來(lái)得出相同的 IPC 密鑰。常量的存在允許同一個(gè)應(yīng)用程序通過(guò)改變常量來(lái)創(chuàng)建 IPC 機(jī)制的多個(gè)實(shí)例。
在一組進(jìn)程獨(dú)立得出各自的 IPC 密鑰之后,它們必須使用某個(gè) get 系統(tǒng)調(diào)用來(lái)獲得與該特定 IPC 實(shí)例關(guān)聯(lián)的特定標(biāo)識(shí)符。各個(gè) get 調(diào)用全都需要 IPC 密鑰和一組標(biāo)志,以及一些信號(hào)量和共享內(nèi)存大小信息。由于 Unix 是多用戶系統(tǒng),標(biāo)志將包括熟悉的八進(jìn)制形式的文件權(quán)限(因此 666 意味著任何人都可以執(zhí)行讀和寫)。如果還設(shè)置了 IPC_CREAT 標(biāo)志,則會(huì)在 IPC 實(shí)例不存在時(shí)創(chuàng)建該實(shí)例。如果沒(méi)有設(shè)置 IPC_CREAT 標(biāo)志并且還未創(chuàng)建 IPC 實(shí)例,則 get 調(diào)用將返回錯(cuò)誤。
對(duì)于能夠自己分發(fā) IPC 實(shí)例標(biāo)識(shí)符的應(yīng)用程序,存在一種用于執(zhí)行該任務(wù)的更簡(jiǎn)單方法。如果您在調(diào)用 get 以創(chuàng)建 IPC 時(shí)使用密鑰 IPC_PRIVATE,則實(shí)例標(biāo)識(shí)符將是唯一的。需要附加到該 IPC 的其他進(jìn)程不需要調(diào)用 get,因?yàn)樗鼈円呀?jīng)擁有該標(biāo)識(shí)符。
一旦擁有了標(biāo)識(shí)符,應(yīng)用程序就可以任意使用 IPC 實(shí)例。每種 IPC 方法都是不同的,并在它們各自的部分中進(jìn)行處理。
通過(guò)隊(duì)列傳遞消息
消息隊(duì)列提供了一種機(jī)制,使得一個(gè)進(jìn)程可以發(fā)送另一個(gè)進(jìn)程能夠獲得的消息。在獲得該消息之后,將從隊(duì)列中刪除該消息。消息隊(duì)列非常獨(dú)特,因?yàn)閮蓚€(gè)進(jìn)程不必同時(shí)存在——一個(gè)進(jìn)程可以發(fā)送一個(gè)消息并退出,而該消息可以在數(shù)天后才被另一個(gè)進(jìn)程獲得。
消息必須由一個(gè)長(zhǎng)整數(shù)后面跟著消息數(shù)據(jù)組成。清單 1 顯示了 C 語(yǔ)言中的這樣一個(gè)結(jié)構(gòu),其中使用了一個(gè) 100 字節(jié)的消息。
清單 1. 示例消息的 C 語(yǔ)言定義
struct mq_message {long type; /* The type or destination */char text[100]; /* Data */};
消息接收者使用消息類型。當(dāng)從隊(duì)列輪詢消息時(shí),您可以選擇第一個(gè)可用的消息,或者可以查找某種特定的消息類型。將要使用的消息類型特定于應(yīng)用程序,從而使得隊(duì)列獨(dú)特于其他形式的 IPC,因?yàn)閮?nèi)核通過(guò)讀取 type 字段,從而在一定程度上了解所傳遞的應(yīng)用程序數(shù)據(jù)。
清單 2 顯示了消息隊(duì)列的消息提交部分。
清單 2. 向消息隊(duì)列提交消息的程序
#include <sys/types.h>#include <sys/msg.h>#include <sys/ipc.h>#include <string.h>#include <stdio.h>int main (void) {key_t ipckey;int mq_id;struct { long type; char text[100]; } mymsg;/* Generate the ipc key */ipckey = ftok("/tmp/foo", 42);printf("My key is %dn", ipckey);/* Set up the message queue */mq_id = msgget(ipckey, IPC_CREAT | 0666);printf("Message identifIEr is %dn", mq_id);/* Send a message */memset(mymsg.text, 0, 100); /* Clear out the space */strcpy(mymsg.text, "Hello, world!");mymsg.type = 1;msgsnd(mq_id, &mymsg, sizeof(mymsg), 0);}
清單 2 中的代碼包括了必要的頭文件,然后定義了將在 main 函數(shù)中使用的變量。第一要?jiǎng)?wù)是使用 /tmp/foo 作為命令文件和使用數(shù)字 42 作為 ID 來(lái)確定 IPC 密鑰。出于演示的目的,這里使用 printf(3c) 將密鑰顯示在屏幕上。接下來(lái),該代碼使用 msgget 創(chuàng)建消息隊(duì)列。msgget 的第一個(gè)參數(shù)是 IPC 密鑰,第二個(gè)參數(shù)是一組標(biāo)志。在該示例中,標(biāo)志包括八進(jìn)制權(quán)限(該權(quán)限允許具有 IPC 密鑰的任何人完全使用該 IPC)和 IPC_CREAT 標(biāo)志(此標(biāo)志導(dǎo)致 msgget 創(chuàng)建隊(duì)列)。同樣,結(jié)果被打印到屏幕上。
將消息發(fā)送到隊(duì)列是非常簡(jiǎn)單的。在對(duì)消息中的內(nèi)存空間清零之后,將一個(gè)熟悉的字符串復(fù)制到緩沖區(qū)的文本部分。將消息類型設(shè)置為 1,然后調(diào)用 msgsnd。msgsnd 預(yù)期接受的參數(shù)為隊(duì)列 ID、一個(gè)指向數(shù)據(jù)的指針和數(shù)據(jù)的大小,以及一個(gè)指示是否阻塞該調(diào)用的標(biāo)志。如果該標(biāo)志為 IPC_NOWAIT,則即使隊(duì)列已滿,該調(diào)用也會(huì)返回。如果該標(biāo)志為 0,則調(diào)用將阻塞,直至隊(duì)列上的空間被釋放、隊(duì)列被刪除或應(yīng)用程序收到某個(gè)信號(hào)。
該過(guò)程的客戶端行為與此類似。清單 3 顯示了檢索服務(wù)器發(fā)送的消息的代碼。
清單 3. 用于從隊(duì)列檢索消息的代碼
#include <sys/types.h>#include <sys/msg.h>#include <sys/ipc.h>#include <string.h>#include <stdio.h>int main (void) {key_t ipckey;int mq_id;struct { long type; char text[100]; } mymsg;int received;/* Generate the ipc key */ipckey = ftok("/tmp/foo", 42);printf("My key is %dn", ipckey);/* Set up the message queue */mq_id = msgget(ipckey, 0);printf("Message identifIEr is %dn", mq_id);received = msgrcv(mq_id, &mymsg, sizeof(mymsg), 0, 0);printf("%s (%d)n", mymsg.text, received);}
獲得 IPC 密鑰和消息隊(duì)列標(biāo)識(shí)符的過(guò)程與服務(wù)器的代碼類似。對(duì) msgget 的調(diào)用不指定任何標(biāo)志,因?yàn)榉?wù)器已經(jīng)創(chuàng)建了隊(duì)列。如果應(yīng)用程序的設(shè)計(jì)允許客戶端在服務(wù)器之前啟動(dòng),則客戶端和服務(wù)器都必須指定權(quán)限和 IPC_CREAT 標(biāo)志,以便其中首先啟動(dòng)的應(yīng)用程序創(chuàng)建隊(duì)列。
然后 mq_clIEnt.c 調(diào)用 msgrcv 以從隊(duì)列提取消息。前三個(gè)參數(shù)指定消息隊(duì)列標(biāo)識(shí)符、指向?qū)⒌膬?nèi)存空間的指針和緩沖區(qū)的大小。第四個(gè)參數(shù)是類型參數(shù),它允許您選擇所要獲得的消息:
如果類型為 0,則返回隊(duì)列中的第一個(gè)消息。
如果類型為正整數(shù),則返回隊(duì)列中的第一個(gè)該類型的消息。
如果類型為負(fù)整數(shù),則返回隊(duì)列中具有最小值的第一個(gè)消息,且該最小值小于或等于指定類型的絕對(duì)值。例如,如果要將 2 然后再將 1 添加到隊(duì)列,則使用類型 -2 調(diào)用 msgrcv 將返回 1,因?yàn)樗钚。M管它是隊(duì)列中的第二個(gè)消息。
傳遞給 msgrcv 的第五個(gè)參數(shù)同樣是阻塞標(biāo)志。清單 4 顯示了實(shí)際操作中的客戶端和服務(wù)器。
清單 4. 客戶端和服務(wù)器代碼的輸出
sunbox$ ./mq_serverMy key is 704654099Message identifier is 2sunbox$ ./mq_clientMy key is 704654099Message identifier is 2Hello, world! (104)
客戶端和服務(wù)器的輸出表明,它們得出了相同的 IPC 密鑰,因?yàn)樗鼈兌家猛粋€(gè)文件和標(biāo)識(shí)符。服務(wù)器創(chuàng)建了 IPC 實(shí)例,內(nèi)核為該實(shí)例分配了值 2,并且客戶端應(yīng)用程序知道這一點(diǎn)。這樣,客戶端從消息隊(duì)列提取回“Hello, world!就沒(méi)什么奇怪的了。
此示例顯示了最簡(jiǎn)單的情況。消息隊(duì)列對(duì)于短期進(jìn)程是有用的,例如將工作提交給重負(fù)荷后端應(yīng)用程序(例如某個(gè)批處理應(yīng)用程序)的 Web 事務(wù)。客戶端也可以是服務(wù)器,并且多個(gè)應(yīng)用程序可以向隊(duì)列提交消息。消息類型字段允許應(yīng)用程序?qū)⑾l(fā)送給特定的讀取器。
使用信號(hào)量鎖定資源
進(jìn)程之間的通信不需要涉及到發(fā)送大量的數(shù)據(jù)。實(shí)際上,單個(gè)位可能就足以指示某個(gè)進(jìn)程在使用某個(gè)資源。請(qǐng)考慮兩個(gè)需要訪問(wèn)某個(gè)硬件部分的進(jìn)程,但是一次只有一個(gè)進(jìn)程能夠使用該硬件。每個(gè)進(jìn)程就包含引用計(jì)數(shù)器的點(diǎn)達(dá)成一致。如果一個(gè)進(jìn)程讀取該計(jì)數(shù)器并看到其值為 1,則它就知道另一個(gè)進(jìn)程正在使用該硬件。如果該計(jì)數(shù)器的值為 0,則該進(jìn)程就可以自由使用該硬件資源,前提是在該硬件操作期間將計(jì)數(shù)器設(shè)置為 1 并在結(jié)束操作時(shí)將其重置為 0。
此場(chǎng)景存在兩個(gè)問(wèn)題:第一個(gè)問(wèn)題不過(guò)就是設(shè)置共享計(jì)數(shù)器并就其位置達(dá)成一致,再?zèng)]有比這麻煩的了。第二個(gè)問(wèn)題是鎖定硬件資源所需要的獲取和設(shè)置操作不是原子的。如果一個(gè)進(jìn)程在讀取計(jì)數(shù)器時(shí),其值為 0,但是在它還沒(méi)有機(jī)會(huì)將計(jì)數(shù)器設(shè)置為 1 之前,另一個(gè)進(jìn)程已搶先讀取了該計(jì)數(shù)器,則第二個(gè)進(jìn)程就能夠讀取和設(shè)置計(jì)數(shù)器。兩個(gè)進(jìn)程都會(huì)認(rèn)為它們可以使用該硬件。沒(méi)有辦法知道另一個(gè)或其他進(jìn)程是否會(huì)設(shè)置該計(jì)數(shù)器。這稱為爭(zhēng)用條件。信號(hào)量通過(guò)提供一個(gè)公共應(yīng)用程序接口,以及通過(guò)實(shí)現(xiàn)原子測(cè)試和設(shè)置操作,從而同時(shí)解決了這兩個(gè)問(wèn)題。
信號(hào)量的 SysV 實(shí)現(xiàn)比上述解決方案更通用。首先,信號(hào)量的值不需要是 0 或 1;它可以是 0 或任何正數(shù)。其次,可以執(zhí)行一系列信號(hào)量操作,與用于 msgrcv 的 type 參數(shù)非常類似。這些操作作為一個(gè)指令集提供給內(nèi)核,并且這些指令要么全部運(yùn)行,要么一個(gè)也不會(huì)運(yùn)行。內(nèi)核要求將這些指令放在一個(gè)名為 sembuf 的結(jié)構(gòu)中,該結(jié)構(gòu)具有以下成員(按順序):
sem_num:描述正在操作該集合中的哪一個(gè)信號(hào)量。
sem_op:一個(gè)有符號(hào)整數(shù),其中包含要執(zhí)行的指令或測(cè)試。
sem_flg:熟悉的 IPC_NOWAIT 標(biāo)志(它指示測(cè)試應(yīng)該立即返回還是阻塞直至通過(guò))和 SEM_UNDO(它在進(jìn)程提前退出時(shí)導(dǎo)致撤銷該信號(hào)量操作)的組合。
sem_op 是放置許多配置的地方:
如果 sem_op 為 0,則測(cè)試 sem_num 以確定它是否為 0。如果 sem_num 為 0,則運(yùn)行下一個(gè)測(cè)試。如果 sem_num 不為 0,則在未設(shè)置 IPC_NOWAIT 時(shí),操作將阻塞直至信號(hào)量變?yōu)?0,而在設(shè)置了 IPC_NOWAIT 時(shí),則跳過(guò)其他測(cè)試。
如果 sem_op 是某個(gè)正數(shù),則將信號(hào)量的值加上 sem_op 的值。
如果 sem_op 是一個(gè)負(fù)整數(shù),并且信號(hào)量的值大于或等于 sem_op 的絕對(duì)值,則從信號(hào)量的值減去該絕對(duì)值。
如果 sem_op 是一個(gè)負(fù)整數(shù),并且信號(hào)量的值小于 sem_op 的絕對(duì)值,則在 IPC_NOWAIT 為 true 時(shí)立即停止測(cè)試的執(zhí)行,而在該值為 false 時(shí)則阻塞,直至信號(hào)量的值變得大于 sem_op 的絕對(duì)值。
清單 5 中的示例闡明了信號(hào)量的使用,其中研究了一個(gè)可同時(shí)運(yùn)行多次的程序,但是該程序確保一次只有一個(gè)進(jìn)程處于關(guān)鍵部分。其中使用了簡(jiǎn)單情況下的信號(hào)量;當(dāng)信號(hào)量的值為 0 時(shí)釋放資源。
清單 5. 使用信號(hào)量來(lái)保護(hù)關(guān)鍵部分
#include <sys/types.h>#include <sys/sem.h>#include <sys/ipc.h>#include <string.h> /* For strerror(3c) */#include <errno.h> /* For errno */#include <unistd.h> /* rand(3c) */#include <stdio.h>int main (int argc, char **argv) {key_t ipckey;int semid;struct sembuf sem[2]; /* sembuf defined in sys/sem.h *//* Generate the ipc key */ipckey = ftok("/tmp/foo", 42);/* Set up the semaphore set. 4 == READ, 2 == ALTER */semid = semget(ipckey, 1, 0666 | IPC_CREAT);if (semid < 0) {printf("Error - %sn", strerror(errno));_exit(1);}/* These never change so leave them outside the loop */sem[0].sem_num = 0;sem[1].sem_num = 0;sem[0].sem_flg = SEM_UNDO; /* Release semaphore on exit */sem[1].sem_flg = SEM_UNDO; /* Release semaphore on exit */while(1) { /* loop forever */printf("[%s] Waiting for the semaphore to be releasedn", argv[1]);/* Set up two semaphore operations */sem[0].sem_op = 0; /* Wait for zero */sem[1].sem_op = 1; /* Add 1 to lock it*/semop(semid, sem, 2);printf("[%s] I have the semaphoren", argv[1]);sleep(rand() % 3); /* Critical section, sleep for 0-2 seconds */sem[0].sem_op = -1; /* Decrement to unlock */semop(semid, sem, 1);printf("[%s] Released semaphoren", argv[1]);sleep(rand() % 3); /* Sleep 0-2 seconds */}}
清單 5 的開頭與消息隊(duì)列示例的開頭相同。其中 msgget 在第二個(gè)參數(shù)中指定消息隊(duì)列的大小,semget 指定信號(hào)量集(Semaphore Set) 的大小。信號(hào)量集是一組共享一個(gè)公共 IPC 實(shí)例的信號(hào)量。該集合中的信號(hào)量數(shù)量無(wú)法更改。如果已經(jīng)創(chuàng)建了信號(hào)量集,則 semget 的第二個(gè)參數(shù)實(shí)際上被忽略。如果 semget 返回一個(gè)指示失敗的負(fù)整數(shù),則打印原因,并退出程序。
在主 while 循環(huán)之前,對(duì) sem_num 和 sem_flg 進(jìn)行了初始化,因?yàn)樗鼈冊(cè)谡麄€(gè)示例中保持一致。此外還指定了 SEM_UNDO,以便在信號(hào)量擁有者未能釋放該信號(hào)量就已退出的情況下,不會(huì)鎖定所有其他應(yīng)用程序。
該循環(huán)中還打印了一個(gè)狀態(tài)消息,以指示應(yīng)用程序已開始等待信號(hào)量。此輸出附帶第一個(gè)命令行參數(shù)作為前綴,以將它與其他實(shí)例區(qū)分開來(lái)。在進(jìn)入關(guān)鍵部分之前,應(yīng)用程序鎖定了信號(hào)量。此示例中指定了兩個(gè)信號(hào)量指令。第一個(gè)為 0,意味著應(yīng)用程序?qū)⒌却敝列盘?hào)量值恢復(fù)為 0。第二個(gè)為 1,意味著在信號(hào)量恢復(fù)為零之后,將向該信號(hào)量加 1。應(yīng)用程序調(diào)用 semop 以運(yùn)行指令,并向其傳遞信號(hào)量 ID、數(shù)據(jù)結(jié)構(gòu)的地址和要使用的 sembuf 指令數(shù)量。
在 semop 返回以后,應(yīng)用程序知道它已經(jīng)鎖定了信號(hào)量,并打印一個(gè)消息以指示這一點(diǎn)。然后關(guān)鍵部分將會(huì)運(yùn)行,在此例中是隨機(jī)地暫停幾秒。最后,使用 semop 值 -1 來(lái)運(yùn)行單個(gè) sembuf 命令,從而釋放信號(hào)量,這實(shí)際上是從信號(hào)量減去 1,并將其值恢復(fù)為 0。隨后打印更多的調(diào)試輸出,應(yīng)用程序隨機(jī)暫停,然后繼續(xù)執(zhí)行。清單 6 顯示了此應(yīng)用程序的兩個(gè)實(shí)例的輸出。
清單 6. 兩個(gè)使用信號(hào)量來(lái)保護(hù)關(guān)鍵部分的程序
sunbox$ ./sem_example a & ./sem_example b &[a] Waiting for the semaphore to be released[a] I have the semaphore[b] Waiting for the semaphore to be released[a] Released semaphore[b] I have the semaphore[a] Waiting for the semaphore to be released[b] Released semaphore[a] I have the semaphore[a] Released semaphore[a] Waiting for the semaphore to be released[a] I have the semaphore
清單 6 顯示了運(yùn)行的示例的兩個(gè)實(shí)例,這兩個(gè)實(shí)例分別具有名稱 a 和 b。首先,a 獲得信號(hào)量,在 a 擁有該信號(hào)量的同時(shí),b 嘗試獲得一個(gè)鎖。一旦釋放了信號(hào)量,b 即獲得鎖。現(xiàn)在情況顛倒過(guò)來(lái),變?yōu)榈却?b 完成。最后,a 在信號(hào)量被釋放后再次獲得該信號(hào)量,因?yàn)?b 沒(méi)有等待。
關(guān)于信號(hào)量,要注意的最后一個(gè)事項(xiàng)在于,它們被稱為建議鎖(Advisory Lock)。這意味著信號(hào)量本身并不阻止兩個(gè)進(jìn)程同時(shí)使用同一個(gè)資源;相反,它們旨在建議任何進(jìn)程自愿詢問(wèn)該資源是否正在使用。
共享內(nèi)存空間
共享內(nèi)存也許是最強(qiáng)大的 SysV IPC 方法,并且此方法最容易實(shí)現(xiàn)。顧名思義,共享內(nèi)存是在兩個(gè)進(jìn)程之間共享一個(gè)內(nèi)存塊。清單 7 顯示了一個(gè)程序,該程序調(diào)用 fork(2) 來(lái)將自身劃分為一個(gè)父進(jìn)程和一個(gè)子進(jìn)程,兩個(gè)進(jìn)程之間使用一個(gè)共享內(nèi)存段進(jìn)行通信。
清單 7. 演示共享內(nèi)存用法的程序
#include <sys/types.h>#include <sys/ipc.h>#include <sys/shm.h>#include <unistd.h>#include <string.h>#include <errno.h>int main(void) {pid_t pid;int *shared; /* pointer to the shm */int shmid;shmid = shmget(IPC_PRIVATE, sizeof(int), IPC_CREAT | 0666);if (fork() == 0) { /* Child *//* Attach to shared memory and print the pointer */shared = shmat(shmid, (void *) 0, 0);printf("Child pointer %pn", shared);*shared=1;printf("Child value=%dn", *shared);sleep(2);printf("Child value=%dn", *shared);} else { /* Parent *//* Attach to shared memory and print the pointer */shared = shmat(shmid, (void *) 0, 0);printf("Parent pointer %pn", shared);printf("Parent value=%dn", *shared);sleep(1);*shared=42;printf("Parent value=%dn", *shared);sleep(5);shmctl(shmid, IPC_RMID, 0);}}
您現(xiàn)在應(yīng)該已經(jīng)熟悉傳遞給 shmget 的參數(shù)了:密鑰、大小和標(biāo)志。此示例中的共享內(nèi)存大小是單個(gè)整數(shù)。清單 7 與前一個(gè)示例的不同之處在于它對(duì) IPC 密鑰使用了 IPC_PRIVATE。當(dāng)使用了 IPC_PRIVATE 時(shí),將保證創(chuàng)建一個(gè)唯一的 IPC ID,并且預(yù)期應(yīng)用程序?qū)⒆约悍职l(fā)該 ID。在此示例中,父進(jìn)程和子進(jìn)程都知道 shmid,因?yàn)樗鼈兎謩e是對(duì)方的副本。fork 系統(tǒng)調(diào)用創(chuàng)建當(dāng)前進(jìn)程的第二個(gè)副本,稱為子進(jìn)程,此進(jìn)程幾乎與父進(jìn)程完全相同。兩個(gè)進(jìn)程的執(zhí)行都在 fork 之后恢復(fù)。返回值將用于確定當(dāng)前進(jìn)程是父進(jìn)程還是子進(jìn)程。
父進(jìn)程和子進(jìn)程看起來(lái)相似。首先,shmat 系統(tǒng)調(diào)用被用于獲得指向共享內(nèi)存段的指針。shmat 需要共享內(nèi)存 ID、一個(gè)指針和某些標(biāo)志。該指針用于請(qǐng)求特定的內(nèi)存地址。通過(guò)傳遞 0,內(nèi)核可以隨心所欲地選擇任何內(nèi)存地址。標(biāo)志大部分是特定于供應(yīng)商的,不過(guò) SHM_RDONLY 是一個(gè)公共標(biāo)志,用于指示不寫入的段。如清單 7 所示,shmat 的常見(jiàn)用法是讓內(nèi)核決定一切。
shmat 返回一個(gè)指向共享內(nèi)存段的指針,此示例出于調(diào)試目的而將其打印到了屏幕上。然后每個(gè)進(jìn)程依次修改該共享內(nèi)存段,并打印出值。最后,父進(jìn)程使用 shmctl(2) 來(lái)刪除共享內(nèi)存段。清單 8 顯示了此程序的輸出。
清單 8. 共享內(nèi)存示例的輸出
sunbox$ ./shared_memoryChild pointer ff390000Child value=1Parent pointer ff380000Parent value=1Parent value=42Child value=42
您可以從輸出中看到相同內(nèi)存空間的共享。起初,共享內(nèi)存中的值為 1,這是由子進(jìn)程設(shè)置并由父進(jìn)程讀取的。然后父進(jìn)程將該值設(shè)置為 42,并由子進(jìn)程讀取。請(qǐng)注意,父進(jìn)程和子進(jìn)程擁有指向共享內(nèi)存段的不同指針地址,盡管它們是在訪問(wèn)相同的物理內(nèi)存。在使用物理地址時(shí),這會(huì)導(dǎo)致某些數(shù)據(jù)結(jié)構(gòu)出現(xiàn)問(wèn)題,例如鏈表,因此當(dāng)您在共享內(nèi)存中構(gòu)建復(fù)雜結(jié)構(gòu)時(shí),可以使用相對(duì)尋址。
此示例依賴在一個(gè)進(jìn)程向共享內(nèi)存寫入時(shí),另一個(gè)進(jìn)程暫停。在實(shí)際應(yīng)用程序中,這是不切實(shí)際的,因此,如果您的應(yīng)用程序可能潛在地?fù)碛卸鄠€(gè)向相同內(nèi)存位置執(zhí)行寫入的進(jìn)程,可以考慮使用信號(hào)量來(lái)鎖定該區(qū)域。
結(jié)束語(yǔ)
Unix 提供了若干種用于 IPC 的方法。SysV IPC 方法是消息隊(duì)列、信號(hào)量和共享內(nèi)存。消息隊(duì)列允許一個(gè)應(yīng)用程序提交消息,其他應(yīng)用程序可以在以后獲得該消息,甚至是在發(fā)送應(yīng)用程序已結(jié)束之后。信號(hào)量確保多個(gè)應(yīng)用程序可以鎖定資源并避免爭(zhēng)用條件。共享內(nèi)存允許多個(gè)應(yīng)用程序共享一個(gè)公共內(nèi)存段,從而提供了一種傳遞和共享大量數(shù)據(jù)的快速方法。您還可以將這些方法結(jié)合起來(lái)使用。例如,您可以使用信號(hào)量來(lái)控制對(duì)共享內(nèi)存段的訪問(wèn)。
IPC 方法對(duì)應(yīng)用程序開發(fā)人員非常有用,因?yàn)樗鼈兲峁┝藨?yīng)用程序之間的標(biāo)準(zhǔn)通信方法,并且是跨不同 UNIX 風(fēng)格可移植的。當(dāng)您下次發(fā)現(xiàn)自己需要鎖定資源或在進(jìn)程之間共享數(shù)據(jù)時(shí),可以試驗(yàn)一下 SysV IPC 機(jī)制。
