进程间的通信方式(一):共享内存
? ? ?????共享內(nèi)存指 (shared memory)在多處理器的計算機系統(tǒng)中,可以被不同中央處理器(CPU)訪問的大容量內(nèi)存。由于多個CPU需要快速訪問存儲器,這樣就要對存儲器進行緩存(Cache)。任何一個緩存的數(shù)據(jù)被更新后,由于其他處理器也可能要存取,共享內(nèi)存就需要立即更新,否則不同的處理器可能用到不同的數(shù)據(jù)。共享內(nèi)存是 Unix下的多進程之間的通信方法 ,這種方法通常用于一個程序的多進程間通信,實際上多個程序間也可以通過共享內(nèi)存來傳遞信息。
1.共享內(nèi)存概述
? ? ????共享內(nèi)存是進程間通信中最簡單的方式之一。共享內(nèi)存允許兩個或更多進程訪問同一塊內(nèi)存,就如同 malloc() 函數(shù)向不同進程返回了指向同一個物理內(nèi)存區(qū)域的指針。當(dāng)一個進程改變了這塊地址中的內(nèi)容的時候,其它進程都會察覺到這個更改。
?
????????上圖描述的內(nèi)容一樣,共享內(nèi)存實際上就是進程通過調(diào)用shmget(Shared?Memory?GET 獲取共享內(nèi)存)來分配一個共享內(nèi)存塊,然后每個進程通過shmat(Shared?Memory?Attach?綁定到共享內(nèi)存塊),將進程的邏輯虛擬地址空間指向共享內(nèi)存塊中。?隨后需要訪問這個共享內(nèi)存塊的進程都必須將這個共享內(nèi)存綁定到自己的地址空間中去。當(dāng)一個進程往一個共享內(nèi)存快中寫入了數(shù)據(jù),共享這個內(nèi)存區(qū)域的所有進程就可用都看到其中的內(nèi)容。
共享內(nèi)存的特點:
2.共享內(nèi)存的通信
?? ?????因為所有進程共享同一塊內(nèi)存,共享內(nèi)存在各種進程間通信方式中具有最高的效率。訪問共享內(nèi)存區(qū)域和訪問進程獨有的內(nèi)存區(qū)域一樣快,并不需要通過系統(tǒng)調(diào)用或者其它需要切入內(nèi)核的過程來完成。同時它也避免了對數(shù)據(jù)的各種不必要的復(fù)制。
?? ?????因為系統(tǒng)內(nèi)核沒有對訪問共享內(nèi)存進行同步,您必須提供自己的同步措施。例如,在數(shù)據(jù)被寫入之前不允許進程從共享內(nèi)存中讀取信息、不允許兩個進程同時向同一個共享內(nèi)存地址寫入數(shù)據(jù)等。解決這些問題的常用方法是通過使用信號量進行同步。
3.共享內(nèi)存的內(nèi)存模型
?? ?????要使用一塊共享內(nèi)存,進程必須首先分配它。隨后需要訪問這個共享內(nèi)存塊的每一個進程都必須將這個共享內(nèi)存綁定到自己的地址空間中。當(dāng)完成通信之后,所有進程都將脫離共享內(nèi)存,并且由一個進程釋放該共享內(nèi)存塊。理解 Linux 系統(tǒng)內(nèi)存模型可以有助于解釋這個綁定的過程。在 Linux 系統(tǒng)中,每個進程的虛擬內(nèi)存是被分為許多頁面的。這些內(nèi)存頁面中包含了實際的數(shù)據(jù)。每個進程都會維護一個從內(nèi)存地址到虛擬內(nèi)存頁面之間的映射關(guān)系。盡管每個進程都有自己的內(nèi)存地址,不同的進程可以同時將同一個內(nèi)存頁面映射到自己的地址空間中,從而達到共享內(nèi)存的目的。
?? ?????分配一個新的共享內(nèi)存塊會創(chuàng)建新的內(nèi)存頁面。因為所有進程都希望共享對同一塊內(nèi)存的訪問,只應(yīng)由一個進程創(chuàng)建一塊新的共享內(nèi)存。再次分配一塊已經(jīng)存在的內(nèi)存塊不會創(chuàng)建新的頁面,而只是會返回一個標(biāo)識該內(nèi)存塊的標(biāo)識符。一個進程如需使用這個共享內(nèi)存塊,則首先需要將它綁定到自己的地址空間中。這樣會創(chuàng)建一個從進程本身虛擬地址到共享頁面的映射關(guān)系。當(dāng)對共享內(nèi)存的使用結(jié)束之后,這個映射關(guān)系將被刪除。當(dāng)再也沒有進程需要使用這個共享內(nèi)存塊的時候,必須有一個(且只能是一個)進程負責(zé)釋放這個被共享的內(nèi)存頁面。
?
4.共享內(nèi)存的操作
4.1共享內(nèi)存的分配
?? ?進程通過調(diào)用shmget(Shared Memory GET,獲取共享內(nèi)存)來分配一個共享內(nèi)存塊。
?? ?該函數(shù)的第一個參數(shù)是一個用來標(biāo)識共享內(nèi)存塊的鍵值。彼此無關(guān)的進程可以通過指定同一個鍵以獲取對同一個共享內(nèi)存塊的訪問。不幸的是,其它程序也可能挑選了同樣的特定值作為自己分配共享內(nèi)存的鍵值,從而產(chǎn)生沖突。用特殊常量IPC_PRIVATE作為鍵值可以保證系統(tǒng)建立一個全新的共享內(nèi)存塊。該函數(shù)的第二個參數(shù)指定了所申請的內(nèi)存塊的大小。因為這些內(nèi)存塊是以頁面為單位進行分配的,實際分配的內(nèi)存塊大小將被擴大到頁面大小的整數(shù)倍。第三個參數(shù)是一組標(biāo)志,通過特定常量的按位或操作來shmget。
?
所需頭文件:
#include <sys/ipc.h>
#include <sys/shm.h>
?
int shmget(key_t key, size_t size,int shmflg);
功能:
????????創(chuàng)建或打開一塊共享內(nèi)存區(qū)。
參數(shù):
????????key:進程間通信鍵值,ftok() 的返回值。
????????size:該共享存儲段的長度(字節(jié))。
????????shmflg:標(biāo)識函數(shù)的行為及共享內(nèi)存的權(quán)限,其取值如下:
????????????IPC_CREAT:如果不存在就創(chuàng)建
????????????IPC_EXCL:??如果已經(jīng)存在則返回失敗
????????????位或權(quán)限位:共享內(nèi)存位或權(quán)限位后可以設(shè)置共享內(nèi)存的訪問權(quán)限,格式和 open() 函數(shù)的 mode_t 一樣(open() 的使用請點此鏈接),但可執(zhí)行權(quán)限未使用。
返回值:
????????成功:共享內(nèi)存標(biāo)識符。
????????失敗:-1。
4.2.共享內(nèi)存的映射
?? ?要讓一個進程獲取對一塊共享內(nèi)存的訪問,這個進程必須先調(diào)用 shmat(SHared Memory Attach,綁定到共享內(nèi)存)。將 shmget 返回的共享內(nèi)存標(biāo)識符 SHMID 傳遞給這個函數(shù)作為第一個參數(shù)。該函數(shù)的第二個參數(shù)是一個指針,指向您希望用于映射該共享內(nèi)存塊的進程虛擬內(nèi)存地址;如果您指定NULL則Linux會自動選擇一個合適的地址用于映射。第三個參數(shù)是一個標(biāo)志位,
?
所需頭文件:
#include <sys/types.h>
#include <sys/shm.h>
?
void *shmat(int shmid, const void *shmaddr, int shmflg);
功能:
????????將一個共享內(nèi)存段映射到調(diào)用進程的數(shù)據(jù)段中。簡單來理解,讓進程和共享內(nèi)存建立一種聯(lián)系,讓進程某個指針指向此共享內(nèi)存。
參數(shù):
????????shmid:共享內(nèi)存標(biāo)識符,shmget() 的返回值。
????????shmaddr:共享內(nèi)存映射地址(若為 NULL 則由系統(tǒng)自動指定),推薦使用 NULL。
????????shmflg:共享內(nèi)存段的訪問權(quán)限和映射條件( 通常為 0 ),具體取值如下:
????????????????0:共享內(nèi)存具有可讀可寫權(quán)限。
????????????????SHM_RDONLY:只讀。
????????????????SHM_RND:(shmaddr 非空時才有效)
返回值:
????????成功:共享內(nèi)存段映射地址( 相當(dāng)于這個指針就指向此共享內(nèi)存 )
????????失敗:-1
?
4.3.解除共享內(nèi)存的映射
?
當(dāng)一個進程不再使用一個共享內(nèi)存塊的時候應(yīng)通過調(diào)用 shmdt(Shared Memory Detach,脫離共享內(nèi)存塊)函數(shù)與該共享內(nèi)存塊脫離。如果當(dāng)釋放這個內(nèi)存塊的進程是最后一個使用該內(nèi)存塊的進程,則這個內(nèi)存塊將被刪除。對 exit 或任何exec族函數(shù)的調(diào)用都會自動使進程脫離共享內(nèi)存塊。
?
所需頭文件:
#include <sys/types.h>
#include <sys/shm.h>
?
int shmdt(const void *shmaddr);
功能:
????????將共享內(nèi)存和當(dāng)前進程分離( 僅僅是斷開聯(lián)系并不刪除共享內(nèi)存,相當(dāng)于讓之前的指向此共享內(nèi)存的指針,不再指向)。
參數(shù):
????????shmaddr:共享內(nèi)存映射地址。
返回值:
????????成功:0
????????失敗:-1
?
4.4.共享內(nèi)存的控制
?
所需的頭文件:
#include <sys/ipc.h>
#include <sys/shm.h>
?
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
功能:
????????共享內(nèi)存屬性的控制。
參數(shù):
????????shmid:共享內(nèi)存標(biāo)識符。
????????cmd:函數(shù)功能的控制,其取值如下:
????????????????IPC_RMID:刪除。(常用 )
????????????????IPC_SET:設(shè)置 shmid_ds 參數(shù),相當(dāng)于把共享內(nèi)存原來的屬性值替換為 buf 里的屬性值。
????????????????IPC_STAT:保存 shmid_ds 參數(shù),把共享內(nèi)存原來的屬性值備份到 buf 里。
????????????????SHM_LOCK:鎖定共享內(nèi)存段( 超級用戶 )。
????????????????SHM_UNLOCK:解鎖共享內(nèi)存段。
?
SHM_LOCK 用于鎖定內(nèi)存,禁止內(nèi)存交換。并不代表共享內(nèi)存被鎖定后禁止其它進程訪問。其真正的意義是:被鎖定的內(nèi)存不允許被交換到虛擬內(nèi)存中。這樣做的優(yōu)勢在于讓共享內(nèi)存一直處于內(nèi)存中,從而提高程序性能。
?
????????buf:shmid_ds 數(shù)據(jù)類型的地址(具體類型請點此鏈接 ),用來存放或修改共享內(nèi)存的屬性。
返回值:
????????成功:0
????????失敗:-1
?
4.實戰(zhàn)實例
做這么一個例子:創(chuàng)建兩個進程,在 A 進程中創(chuàng)建一個共享內(nèi)存,并向其寫入數(shù)據(jù),通過 B 進程從共享內(nèi)存中讀取數(shù)據(jù)。
寫端代碼如下:
#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;//創(chuàng)建key值key = ftok("../", 2015);if(key == -1){perror("ftok");}//創(chuàng)建共享內(nèi)存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);}//拷貝數(shù)據(jù)至共享內(nèi)存區(qū)printf("copy data to shared-memory\n");bzero(shmadd, BUFSZ); // 共享內(nèi)存清空strcpy(shmadd, "how are you, lh\n");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;//創(chuàng)建key值key = ftok("../", 2015);if(key == -1){perror("ftok");}system("ipcs -m"); //查看共享內(nèi)存//打開共享內(nèi)存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);}//讀共享內(nèi)存區(qū)數(shù)據(jù)printf("data = [%s]\n", shmadd);//分離共享內(nèi)存和當(dāng)前進程ret = shmdt(shmadd);if(ret < 0){perror("shmdt");exit(1);}else{printf("deleted shared-memory\n");}//刪除共享內(nèi)存shmctl(shmid, IPC_RMID, NULL);system("ipcs -m"); //查看共享內(nèi)存return 0; }?
?
?
?
《新程序員》:云原生和全面數(shù)字化實踐50位技術(shù)專家共同創(chuàng)作,文字、視頻、音頻交互閱讀總結(jié)
以上是生活随笔為你收集整理的进程间的通信方式(一):共享内存的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 大数据技术:Zookeeper分布式协调
- 下一篇: 进程间的通信方式:简介