程序間通訊——共享記憶體(Shared Memory)簡易原理和建立_獲得函式
共享記憶體是System V版本的最後一個程序間通訊方式。共享記憶體,顧名思義就是允許兩個不相關的程序訪問同一個邏輯記憶體,共享記憶體是兩個正在執行的程序之間共享和傳遞資料的一種非常有效的方式。不同程序之間共享的記憶體通常為同一段實體記憶體。程序可以將同一段實體記憶體連線到他們自己的地址空間中,所有的程序都可以訪問共享記憶體中的地址。如果某個程序向共享記憶體寫入資料,所做的改動將立即影響到可以訪問同一段共享記憶體的任何其他程序。
特別提醒:共享記憶體並未提供同步機制,也就是說,在第一個程序結束對共享記憶體的寫操作之前,並無自動機制可以阻止第二個程序開始對它進行讀取,所以我們通常需要用其他的機制來同步對共享記憶體的訪問,例如訊號量。
下面就 Shared Memory 的IPC作以闡述與分析。
共享記憶體的通訊原理
在Linux中,每個程序都有屬於自己的程序控制塊(PCB)和地址空間(Addr Space),並且都有一個與之對應的頁表,負責將程序的虛擬地址與實體地址進行對映,通過記憶體管理單元(MMU)進行管理。兩個不同的虛擬地址通過頁表對映到物理空間的同一區域,它們所指向的這塊區域即共享記憶體。
共享記憶體的通訊原理示意圖:
對於上圖我的理解是:當兩個程序通過頁表將虛擬地址對映到實體地址時,在實體地址中有一塊共同的記憶體區,即共享記憶體,這塊記憶體可以被兩個程序同時看到。這樣當一個程序進行寫操作,另一個程序讀操作就可以實現程序間通訊。但是,我們要確保一個程序在寫的時候不能被讀,因此我們使用訊號量來實現同步與互斥。
對於一個共享記憶體,實現採用的是引用計數的原理,當程序脫離共享儲存區後,計數器減一,掛架成功時,計數器加一,只有當計數器變為零時,才能被刪除。當程序終止時,它所附加的共享儲存區都會自動脫離。
為什麼共享記憶體速度最快?
藉助上圖說明:Proc A 程序給記憶體中寫資料, Proc B 程序從記憶體中讀取資料,在此期間一共發生了兩次複製
(1)Proc A 到共享記憶體 (2)共享記憶體到 Proc B
因為直接在記憶體上操作,所以共享記憶體的速度也就提高了。
共享記憶體的介面函式以及指令
1.檢視系統中的共享儲存段
ipcs -m
2.刪除系統中的共享儲存段
ipcrm -m [shmid]
3.shmget ( ):建立共享記憶體
int shmget(key_t key, size_t size, int shmflg);
[引數key]:由ftok生成的key標識,標識系統的唯一IPC資源。
[引數size]:需要申請共享記憶體的大小。在作業系統中,申請記憶體的最小單位為頁,一頁是4k位元組,為了避免記憶體碎片,我們一般申請的記憶體大小為頁的整數倍。
[引數shmflg]:如果要建立新的共享記憶體,需要使用IPC_CREAT,IPC_EXCL,如果是已經存在的,可以使用IPC_CREAT或直接傳0。
[返回值]:成功時返回一個新建或已經存在的的共享記憶體識別符號,取決於shmflg的引數。失敗返回-1並設定錯誤碼。
4.shmat ( ):掛接共享記憶體
void *shmat(int shmid, const void *shmaddr, int shmflg);
[引數shmid]:共享儲存段的識別符號。
[引數*shmaddr]:shmaddr = 0,則儲存段連線到由核心選擇的第一個可以地址上(推薦使用)。
[引數shmflg]:若指定了SHM_RDONLY位,則以只讀方式連線此段,否則以讀寫方式連線此段。
[返回值]:成功返回共享儲存段的指標(虛擬地址),並且核心將使其與該共享儲存段相關的shmid_ds結構中的shm_nattch計數器加1(類似於引用計數);出錯返回-1。
5.shmdt ( ):去關聯共享記憶體
當一個程序不需要共享記憶體的時候,就需要去關聯。該函式並不刪除所指定的共享記憶體區,而是將之前用shmat函式連線好的共享記憶體區脫離目前的程序。
int shmdt(const void *shmaddr);
[引數*shmaddr]:連線以後返回的地址。
[返回值]:成功返回0,並將shmid_ds結構體中的 shm_nattch計數器減1;出錯返回-1。
6.shmctl ( ):銷燬共享記憶體
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
[引數shmid]:共享儲存段識別符號。
[引數cmd]:指定的執行操作,設定為IPC_RMID時表示可以刪除共享記憶體。
[引數*buf]:設定為NULL即可。
[返回值]:成功返回0,失敗返回-1。
模擬共享記憶體
我們用server來建立共享儲存段,用client獲取共享儲存段的識別符號,二者關聯起來之後server將資料寫入共享儲存段,client從共享區讀取資料。通訊結束之後server與client斷開與共享區的關聯,並由server釋放共享儲存段。
comm.h
//comm.h
#ifndef _COMM_H__
#define _COMM_H__
#include<stdio.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#define PATHNAME "."
#define PROJ_ID 0x6666
int CreateShm(int size);
int DestroyShm(int shmid);
int GetShm(int size);
#endif
comm.c
//comm.c
#include"comm.h"
static int CommShm(int size,int flags)
{
key_t key = ftok(PATHNAME,PROJ_ID);
if(key < 0)
{
perror("ftok");
return -1;
}
int shmid = 0;
if((shmid = shmget(key,size,flags)) < 0)
{
perror("shmget");
return -2;
}
return shmid;
}
int DestroyShm(int shmid)
{
if(shmctl(shmid,IPC_RMID,NULL) < 0)
{
perror("shmctl");
return -1;
}
return 0;
}
int CreateShm(int size)
{
return CommShm(size,IPC_CREAT | IPC_EXCL | 0666);
}
int GetShm(int size)
{
return CommShm(size,IPC_CREAT);
}
client.c
//client.c
#include"comm.h"
int main()
{
int shmid = GetShm(4096);
sleep(1);
char *addr = shmat(shmid,NULL,0);
sleep(2);
int i = 0;
while(i < 26)
{
addr[i] = 'A' + i;
i++;
addr[i] = 0;
sleep(1);
}
shmdt(addr);
sleep(2);
return 0;
}
server.c
//server.c
#include"comm.h"
int main()
{
int shmid = CreateShm(4096);
char *addr = shmat(shmid,NULL,0);
sleep(2);
int i = 0;
while(i++ < 26)
{
printf("client# %s\n",addr);
sleep(1);
}
shmdt(addr);
sleep(2);
DestroyShm(shmid);
return 0;
}
Makefile
//Makefile
.PHONY:all
all:server client
client:client.c comm.c
gcc -o [email protected] $^
server:server.c comm.c
gcc -o [email protected] $^
.PHONY:clean
clean:
rm -f client server
執行結果:
總結:
(1)優點:我們可以看到使用共享記憶體進行程序之間的通訊是非常方便的,而且函式的介面也比較簡單,資料的共享還使程序間的資料不用傳送,而是直接訪問記憶體,加快了程式的效率。
(2)缺點:共享記憶體沒有提供同步機制,這使得我們在使用共享記憶體進行程序之間的通訊時,往往需要藉助其他手段來保證程序之間的同步工作。