程序間通訊之:共享記憶體
8.5.1 共享記憶體概述
可以說,共享記憶體是一種最為高效的程序間通訊方式。因為程序可以直接讀寫記憶體,不需要任何資料的複製。為了在多個程序間交換資訊,核心專門留出了一塊記憶體區。這段記憶體區可以由需要訪問的程序將其對映到自己的私有地址空間。因此,程序就可以直接讀寫這一記憶體區而不需要進行資料的複製,從而大大提高了效率。當然,由於多個程序共享一段記憶體,因此也需要依靠某種同步機制,如互斥鎖和訊號量等(請參考本章的共享記憶體實驗)。其原理示意圖如圖8.8所示。
圖8.8 共享記憶體原理示意圖
8.5.2 共享記憶體的應用
1.函式說明
共享記憶體的實現分為兩個步驟,第一步是建立共享記憶體,這裡用到的函式是shmget(),也就是從記憶體中獲得一段共享記憶體區域,第二步對映共享記憶體,也就是把這段建立的共享記憶體對映到具體的程序空間中,這裡使用的函式是shmat()。到這裡,就可以使用這段共享記憶體了,也就是可以使用不帶緩衝的I/O讀寫命令對其進行操作。除此之外,當然還有撤銷對映的操作,其函式為shmdt()。這裡就主要介紹這3個函式。
2.函式格式
表8.20列舉了shmget()函式的語法要點。
表8.20 shmget()函式語法要點
所需標頭檔案 |
#include <sys/types.h> |
函式原型 |
int shmget(key_t key, int size, int shmflg) |
函式傳入值 |
key:共享記憶體的鍵值,多個程序可以通過它訪問同一個共享記憶體,其中有個特殊值IPC_PRIVATE。它用於建立當前程序的私有共享記憶體 |
size:共享記憶體區大小 |
|
shmflg:同open()函式的許可權位,也可以用八進位制表示法 |
|
函式返回值 |
成功:共享記憶體段識別符號 |
出錯:-1 |
表8.21列舉了shmat()函式的語法要點。
表8.21 shmat()函式語法要點
所需標頭檔案 |
#include <sys/types.h> |
|
函式原型 |
char *shmat(int shmid, const void *shmaddr, int shmflg) |
|
函式傳入值 |
shmid:要對映的共享記憶體區識別符號 |
|
shmaddr:將共享記憶體對映到指定地址(若為0則表示系統自動分配地址並把該段共享記憶體對映到呼叫程序的地址空間) |
||
shmflg |
SHM_RDONLY:共享記憶體只讀 |
|
預設0:共享記憶體可讀寫 |
||
函式返回值 |
成功:被對映的段地址 |
|
出錯:-1 |
表8.22列舉了shmdt()函式的語法要點。
表8.22 shmdt()函式語法要點
所需標頭檔案 |
#include <sys/types.h> |
函式原型 |
int shmdt(const void *shmaddr) |
函式傳入值 |
shmaddr:被對映的共享記憶體段地址 |
函式返回值 |
成功:0 |
出錯:-1 |
3.使用例項
該例項說明如何使用基本的共享記憶體函式。首先是建立一個共享記憶體區(採用的共享記憶體的鍵值為IPC_PRIVATE,是因為本例項中建立的共享記憶體是父子程序之間的共用部分),之後建立子程序,在父子兩個程序中將共享記憶體分別對映到各自的程序地址空間之中。
父程序先等待使用者輸入,然後將使用者輸入的字串寫入到共享記憶體,之後往共享記憶體的頭部寫入“WROTE”字串表示父程序已成功寫入資料。子程序一直等到共享記憶體的頭部字串為“WROTE”,然後將共享記憶體的有效資料(在父程序中使用者輸入的字串)在螢幕上列印。父子兩個程序在完成以上工作之後,分別解除與共享記憶體的對映關係。
最後在子程序中刪除共享記憶體。因為共享記憶體自身並不提供同步機制,所以應該額外實現不同程序之間的同步(例如:訊號量)。為了簡單起見,在本例項中用標誌字串來實現非常簡單的父子程序之間的同步。
這裡要介紹的一個命令是ipcs,這是用於報告程序間通訊機制狀態的命令。它可以檢視共享記憶體、訊息佇列等各種程序間通訊機制的情況,這裡使用了system()函式用於呼叫shell命令“ipcs”。程式原始碼如下所示:
/* shmem.c */
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define BUFFER_SIZE 2048
int main()
{
pid_t pid;
int shmid;
char *shm_addr;
char flag[] = "WROTE";
char *buff;
/* 建立共享記憶體 */
if ((shmid = shmget(IPC_PRIVATE, BUFFER_SIZE, 0666)) < 0)
{
perror("shmget");
exit(1);
}
else
{
printf("Create shared-memory: %d\n",shmid);
}
/* 顯示共享記憶體情況 */
system("ipcs -m");
pid = fork();
if (pid == -1)
{
perror("fork");
exit(1);
}
else if (pid == 0) /* 子程序處理 */
{
/*對映共享記憶體*/
if ((shm_addr = shmat(shmid, 0, 0)) == (void*)-1)
{
perror("Child: shmat");
exit(1);
}
else
{
printf("Child: Attach shared-memory: %p\n", shm_addr);
}
system("ipcs -m");
/* 通過檢查在共享記憶體的頭部是否標誌字串"WROTE"來確認
父程序已經向共享記憶體寫入有效資料 */
while (strncmp(shm_addr, flag, strlen(flag)))
{
printf("Child: Wait for enable data...\n");
sleep(5);
}
/* 獲取共享記憶體的有效資料並顯示 */
strcpy(buff, shm_addr + strlen(flag));
printf("Child: Shared-memory :%s\n", buff);
/* 解除共享記憶體對映 */
if ((shmdt(shm_addr)) < 0)
{
perror("shmdt");
exit(1);
}
else
{
printf("Child: Deattach shared-memory\n");
}
system("ipcs -m");
/* 刪除共享記憶體 */
if (shmctl(shmid, IPC_RMID, NULL) == -1)
{
perror("Child: shmctl(IPC_RMID)\n");
exit(1);
}
else
{
printf("Delete shared-memory\n");
}
system("ipcs -m");
}
else /* 父程序處理 */
{
/*對映共享記憶體*/
if ((shm_addr = shmat(shmid, 0, 0)) == (void*)-1)
{
perror("Parent: shmat");
exit(1);
}
else
{
printf("Parent: Attach shared-memory: %p\n", shm_addr);
}
sleep(1);
printf("\nInput some string:\n");
fgets(buff, BUFFER_SIZE, stdin);
strncpy(shm_addr + strlen(flag), buff, strlen(buff));
strncpy(shm_addr, flag, strlen(flag));
/* 解除共享記憶體對映 */
if ((shmdt(shm_addr)) < 0)
{
perror("Parent: shmdt");
exit(1);
}
else
{
printf("Parent: Deattach shared-memory\n");
}
system("ipcs -m");
waitpid(pid, NULL, 0);
printf("Finished\n");
}
exit(0);
}
下面是執行結果。從該結果可以看出,nattch的值隨著共享記憶體狀態的變化而變化,共享記憶體的值根據不同的系統會有所不同。
$ ./shmem
Create shared-memory: 753665
/* 在剛建立共享記憶體時(尚未有任何地址對映)共享記憶體的情況 */
------ Shared Memory Segments --------
key shmid owner perms bytes nattch status
0x00000000 753665 david 666 2048 0
Child: Attach shared-memory: 0xb7f59000 /* 共享記憶體的對映地址 */
Parent: Attach shared-memory: 0xb7f59000
/* 在父子程序中進行共享記憶體的地址對映之後共享記憶體的情況*/
------ Shared Memory Segments --------
key shmid owner perms bytes nattch status
0x00000000 753665 david 666 2048 2
Child: Wait for enable data...
Input some string:
Hello /* 使用者輸入字串“Hello” */
Parent: Deattach shared-memory
/* 在父程序中解除共享記憶體的對映關係之後共享記憶體的情況 */
------ Shared Memory Segments --------
key shmid owner perms bytes nattch status
0x00000000 753665 david 666 2048 1
/*在子程序中讀取共享記憶體的有效資料並列印*/
Child: Shared-memory :hello
Child: Deattach shared-memory
/* 在子程序中解除共享記憶體的對映關係之後共享記憶體的情況 */
------ Shared Memory Segments --------
key shmid owner perms bytes nattch status
0x00000000 753665 david 666 2048 0
Delete shared-memory
/* 在刪除共享記憶體之後共享記憶體的情況 */
------ Shared Memory Segments --------
key shmid owner perms bytes nattch status
Finished