簡介
共用記憶體(shared memory)是最簡單的Linux處理序間通訊方式之一。使用共用記憶體,不同進程可以對同一塊記憶體進行讀寫。由於所有進程對共用記憶體的訪問就和訪問自己的記憶體空間一樣,而不需要進行額外系統調用或核心操作,同時還避免了多餘的記憶體拷貝,所以,這種方式是效率最高、速度最快的處理序間通訊方式。
這種最大限度的自由也給共用記憶體帶來了缺點:核心並不提供任何對共用記憶體訪問的同步機制,比如同時對共用記憶體的相同地址進行寫操作,則後寫的資料會覆蓋之前的資料。所以,使用共用記憶體一般還需要使用其他IPC機制(如訊號量)進行讀寫同步與互斥。
基本原理
瞭解Linux記憶體管理機制,就很容易知道共用記憶體的原理了。大家知道,核心對記憶體的管理是以頁(page)為單位的,Linux下一般一個page大小是4k。而程式本身的虛擬位址空間是線性,所以核心管理了進程從虛擬位址空間到起對應的頁的映射。建立共用記憶體空間後,核心將不同進程虛擬位址的映射到同一個頁面:所以在不同進程中,對共用記憶體所在的記憶體位址的訪問最終都被映射到同一頁面。示範了共用記憶體的工作機制:
使用方法
共用記憶體的使用過程可分為 建立->串連->使用->分離->銷毀 這幾步。
共用記憶體的建立使用shmget函數(SHared Memory GET)函數,使用方法如下:
int segment_id = shmget (shm_key, getpagesize (),IPC_CREAT | S_IRUSR | S_IWUSER);
shmget根據shm_key建立一個大小為page_size的共用記憶體空間,參數3是一系列的建立參數。如果shm_key已經建立,使用該shm_key會返回可以串連到該以建立共用記憶體的id。
建立後,為了使共用記憶體可以被當前進程使用,必須緊接著進行串連操作。使用函數shmat(SHared Memory ATtach),參數傳入通過shmget返回的共用記憶體id即可:
shared_memory = (char*) shmat (segment_id, 0, 0);
shmat返回映射到進程虛擬位址空間的地址指標,這樣進程就能像訪問一塊普通的記憶體緩衝一樣訪問共用記憶體。例子中後兩個參數我們全部傳入0,採用預設的串連方式。實際上,第二個參數是希望串連的進程虛擬位址空間的地址,如果使用0,Linux會自己選用一個合適的地址;第三個參數是串連選項,可以是:
SHM_RND:將第二個參數指向的記憶體空間自動提升到page size的整數倍,如果不知明該參數,你需要自己控制第二個參數所指記憶體空間的大小。
SHM_RDONLY:該共用記憶體是唯讀,不可寫。
當共用記憶體使用量完畢後,使用函數shmdt (SHared Memory DeTach)進行解串連。該函數以shmat返回的記憶體位址作為參數。每最後一個使用該共用記憶體的進程分離該共用記憶體後,核心將會對該共用記憶體自動銷毀。當然,我們最好能顯式的進行銷毀,以避免不必要的共用記憶體資源浪費。 函數shmctl (SHared Memory ConTroL)可以返回共用記憶體的資訊並對其進行控制,如
shmctl (segment_id, IPC_STAT, &shmbuffer);
可以返回該共用記憶體的資訊,並將資訊儲存在第三個參數指向的shmid_ds結構體中。
當向第二個參數傳入IPC_RMID時,共用記憶體將會在最後一個使用該共用記憶體的進程分離共用記憶體是銷毀共用記憶體。
shmctl還有很多其他使用方法, 不再贅述。
樣本程式
下面的樣本程式,a進程每一秒的向共用記憶體寫入一個隨機數,b進程每隔一秒從該共用記憶體讀出該數。
/* * a.c * write a random number between 0 and 999 to the shm every 1 second*/#include<stdio.h>#include<unistd.h>#include<sys/shm.h>#include<stdlib.h>#include<error.h>int main(){int shm_id;int *share;int num;srand(time(NULL));shm_id = shmget (1234, getpagesize(), IPC_CREAT);if(shm_id == -1){perror("shmget()");}share = (int *)shmat(shm_id, 0, 0);while(1){num = random() % 1000;*share = num;printf("write a random number %d\n", num);sleep(1);}return 0;}
/* * b.c * read from the shm every 1 second*/#include<stdio.h>#include<unistd.h>#include<sys/shm.h>#include<stdlib.h>#include<error.h>int main(){int shm_id;int *share;shm_id = shmget (1234, getpagesize(), IPC_CREAT);if(shm_id == -1){perror("shmget()");}share = (int *)shmat(shm_id, 0, 0);while(1){sleep(1);printf("%d\n", *share);}return 0;}
分別運行程式a和程式b,結果如下
PS
前面已經提到過,共用記憶體簡單而高效,但是缺乏同步機制,所以,一般情況下需要配合其他IPC機制共同使用。否則肯定會帶來同步問題。