進程間的通信方式(一):共享內存
來自專欄 RDMA43 人贊了文章
共享內存指 (shared memory)在多處理器的計算機系統中,可以被不同中央處理器(CPU)訪問的大容量內存。由於多個CPU需要快速訪問存儲器,這樣就要對存儲器進行緩存(Cache)。任何一個緩存的數據被更新後,由於其他處理器也可能要存取,共享內存就需要立即更新,否則不同的處理器可能用到不同的數據。共享內存是 Unix下的多進程之間的通信方法 ,這種方法通常用於一個程序的多進程間通信,實際上多個程序間也可以通過共享內存來傳遞信息。
1.共享內存概述 共享內存是進程間通信中最簡單的方式之一。共享內存允許兩個或更多進程訪問同一塊內存,就如同 malloc() 函數向不同進程返回了指向同一個物理內存區域的指針。當一個進程改變了這塊地址中的內容的時候,其它進程都會察覺到這個更改。上圖描述的內容一樣,共享內存實際上就是進程通過調用shmget(Shared Memory GET 獲取共享內存)來分配一個共享內存塊,然後每個進程通過shmat(Shared Memory Attach 綁定到共享內存塊),將進程的邏輯虛擬地址空間指向共享內存塊中。 隨後需要訪問這個共享內存塊的進程都必須將這個共享內存綁定到自己的地址空間中去。當一個進程往一個共享內存快中寫入了數據,共享這個內存區域的所有進程就可用都看到其中的內容。
共享內存的特點:- 共享內存是進程間共享數據的一種最快的方法。一個進程向共享的內存區域寫入了數據,共享這個內存區域的所有進程就可以立刻看到其中的內容。
- 使用共享內存要注意的是多個進程之間對一個給定存儲區訪問的互斥。若一個進程正在向共享內存區寫數據,則在它做完這一步操作前,別的進程不應當去讀、寫這些數據。
2.共享內存的通信
因為所有進程共享同一塊內存,共享內存在各種進程間通信方式中具有最高的效率。訪問共享內存區域和訪問進程獨有的內存區域一樣快,並不需要通過系統調用或者其它需要切入內核的過程來完成。同時它也避免了對數據的各種不必要的複製。 因為系統內核沒有對訪問共享內存進行同步,您必須提供自己的同步措施。例如,在數據被寫入之前不允許進程從共享內存中讀取信息、不允許兩個進程同時向同一個共享內存地址寫入數據等。解決這些問題的常用方法是通過使用信號量進行同步。3.共享內存的內存模型 要使用一塊共享內存,進程必須首先分配它。隨後需要訪問這個共享內存塊的每一個進程都必須將這個共享內存綁定到自己的地址空間中。當完成通信之後,所有進程都將脫離共享內存,並且由一個進程釋放該共享內存塊。理解 Linux 系統內存模型可以有助於解釋這個綁定的過程。在 Linux 系統中,每個進程的虛擬內存是被分為許多頁面的。這些內存頁面中包含了實際的數據。每個進程都會維護一個從內存地址到虛擬內存頁面之間的映射關係。儘管每個進程都有自己的內存地址,不同的進程可以同時將同一個內存頁面映射到自己的地址空間中,從而達到共享內存的目的。 分配一個新的共享內存塊會創建新的內存頁面。因為所有進程都希望共享對同一塊內存的訪問,只應由一個進程創建一塊新的共享內存。再次分配一塊已經存在的內存塊不會創建新的頁面,而只是會返回一個標識該內存塊的標識符。一個進程如需使用這個共享內存塊,則首先需要將它綁定到自己的地址空間中。這樣會創建一個從進程本身虛擬地址到共享頁面的映射關係。當對共享內存的使用結束之後,這個映射關係將被刪除。當再也沒有進程需要使用這個共享內存塊的時候,必須有一個(且只能是一個)進程負責釋放這個被共享的內存頁面。4.共享內存的操作4.1共享內存的分配
進程通過調用shmget(Shared Memory GET,獲取共享內存)來分配一個共享內存塊。 該函數的第一個參數是一個用來標識共享內存塊的鍵值。彼此無關的進程可以通過指定同一個鍵以獲取對同一個共享內存塊的訪問。不幸的是,其它程序也可能挑選了同樣的特定值作為自己分配共享內存的鍵值,從而產生衝突。用特殊常量IPC_PRIVATE作為鍵值可以保證系統建立一個全新的共享內存塊。該函數的第二個參數指定了所申請的內存塊的大小。因為這些內存塊是以頁面為單位進行分配的,實際分配的內存塊大小將被擴大到頁面大小的整數倍。第三個參數是一組標誌,通過特定常量的按位或操作來shmget。所需頭文件:
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size,int shmflg);
功能:
創建或打開一塊共享內存區。
參數:
key:進程間通信鍵值,ftok() 的返回值。
size:該共享存儲段的長度(位元組)。
shmflg:標識函數的行為及共享內存的許可權,其取值如下:
IPC_CREAT:如果不存在就創建
IPC_EXCL: 如果已經存在則返回失敗
位或許可權位:共享內存位或許可權位後可以設置共享內存的訪問許可權,格式和 open() 函數的 mode_t 一樣(open() 的使用請點此鏈接),但可執行許可權未使用。
返回值:
成功:共享內存標識符。
失敗:-1。
4.2.共享內存的映射
要讓一個進程獲取對一塊共享內存的訪問,這個進程必須先調用 shmat(SHared Memory Attach,綁定到共享內存)。將 shmget 返回的共享內存標識符 SHMID 傳遞給這個函數作為第一個參數。該函數的第二個參數是一個指針,指向您希望用於映射該共享內存塊的進程虛擬內存地址;如果您指定NULL則Linux會自動選擇一個合適的地址用於映射。第三個參數是一個標誌位,
所需頭文件:
#include <sys/types.h>
#include <sys/shm.h>
void *shmat(int shmid, const void *shmaddr, int shmflg);
功能:
將一個共享內存段映射到調用進程的數據段中。簡單來理解,讓進程和共享內存建立一種聯繫,讓進程某個指針指向此共享內存。
參數:
shmid:共享內存標識符,shmget() 的返回值。
shmaddr:共享內存映射地址(若為 NULL 則由系統自動指定),推薦使用 NULL。
shmflg:共享內存段的訪問許可權和映射條件( 通常為 0 ),具體取值如下:
0:共享內存具有可讀可寫許可權。
SHM_RDONLY:只讀。
SHM_RND:(shmaddr 非空時才有效)
返回值:
成功:共享內存段映射地址( 相當於這個指針就指向此共享內存 )
失敗:-1
4.3.解除共享內存的映射
當一個進程不再使用一個共享內存塊的時候應通過調用 shmdt(Shared Memory Detach,脫離共享內存塊)函數與該共享內存塊脫離。如果當釋放這個內存塊的進程是最後一個使用該內存塊的進程,則這個內存塊將被刪除。對 exit 或任何exec族函數的調用都會自動使進程脫離共享內存塊。
所需頭文件:
#include <sys/types.h>
#include <sys/shm.h>
int shmdt(const void *shmaddr);
功能:
將共享內存和當前進程分離( 僅僅是斷開聯繫並不刪除共享內存,相當於讓之前的指向此共享內存的指針,不再指向)。
參數:
shmaddr:共享內存映射地址。
返回值:
成功:0
失敗:-1
4.4.共享內存的控制
所需的頭文件:
#include <sys/ipc.h>
#include <sys/shm.h>
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
功能:
共享內存屬性的控制。
參數:
shmid:共享內存標識符。
cmd:函數功能的控制,其取值如下:
IPC_RMID:刪除。(常用 )
IPC_SET:設置 shmid_ds 參數,相當於把共享內存原來的屬性值替換為 buf 里的屬性值。
IPC_STAT:保存 shmid_ds 參數,把共享內存原來的屬性值備份到 buf 里。
SHM_LOCK:鎖定共享內存段( 超級用戶 )。
SHM_UNLOCK:解鎖共享內存段。
SHM_LOCK 用於鎖定內存,禁止內存交換。並不代表共享內存被鎖定後禁止其它進程訪問。其真正的意義是:被鎖定的內存不允許被交換到虛擬內存中。這樣做的優勢在於讓共享內存一直處於內存中,從而提高程序性能。
buf:shmid_ds 數據類型的地址(具體類型請點此鏈接 ),用來存放或修改共享內存的屬性。
返回值:
成功:0
失敗:-1
4.實戰實例
做這麼一個例子:創建兩個進程,在 A 進程中創建一個共享內存,並向其寫入數據,通過 B 進程從共享內存中讀取數據。
寫端代碼如下:
#include <stdio.h>#include <stdlib.h>#include <string.h>#include <unistd.h>#include <sys/types.h>#include <sys/ipc.h>#include <sys/shm.h>#define BUFSZ 512int main(int argc, char *argv[]){ int shmid; int ret; key_t key; char *shmadd; //創建key值 key = ftok("../", 2015); if(key == -1) { perror("ftok"); } //創建共享內存 shmid = shmget(key, BUFSZ, IPC_CREAT|0666); if(shmid < 0) { perror("shmget"); exit(-1); } //映射 shmadd = shmat(shmid, NULL, 0); if(shmadd < 0) { perror("shmat"); _exit(-1); } //拷貝數據至共享內存區 printf("copy data to shared-memory
"); bzero(shmadd, BUFSZ); // 共享內存清空 strcpy(shmadd, "how are you, lh
"); return 0;}
讀端代碼如下:
#include <stdio.h>#include <stdlib.h>#include <string.h>#include <unistd.h>#include <sys/types.h>#include <sys/ipc.h>#include <sys/shm.h>#define BUFSZ 512int main(int argc, char *argv[]){ int shmid; int ret; key_t key; char *shmadd; //創建key值 key = ftok("../", 2015); if(key == -1) { perror("ftok"); } system("ipcs -m"); //查看共享內存 //打開共享內存 shmid = shmget(key, BUFSZ, IPC_CREAT|0666); if(shmid < 0) { perror("shmget"); exit(-1); } //映射 shmadd = shmat(shmid, NULL, 0); if(shmadd < 0) { perror("shmat"); exit(-1); } //讀共享內存區數據 printf("data = [%s]
", shmadd); //分離共享內存和當前進程 ret = shmdt(shmadd); if(ret < 0) { perror("shmdt"); exit(1); } else { printf("deleted shared-memory
"); } //刪除共享內存 shmctl(shmid, IPC_RMID, NULL); system("ipcs -m"); //查看共享內存 return 0;}
推薦閱讀:
※微服務優化之非同步調用
※量子計算入門讀物
※03 聽說你們計科編譯原理要寫一個詞法分析器
※The combinator of combinator
※用Python書寫520,用代碼丈量你的TA