Linux之進(jìn)程間通信(共享內(nèi)存【mmap實(shí)現(xiàn)+系統(tǒng)V】)
共享內(nèi)存
- 共享內(nèi)存可以說(shuō)是最有用的進(jìn)程間通信方式,也是最快的IPC形式,兩個(gè)不同的進(jìn)程A、B共享內(nèi)存的意思就是:同一塊物理內(nèi)存被映射到進(jìn)程A、B各自的進(jìn)程地址空間,進(jìn)程A可以同時(shí)看到進(jìn)程B對(duì)共享內(nèi)存中數(shù)據(jù)的更新,反之亦然。
- 由于個(gè)多個(gè)進(jìn)程共享同一塊內(nèi)存區(qū)域,必然需要某種同步機(jī)制、互斥鎖和信號(hào)量都可以。
好處: 效率高,進(jìn)程可以直接讀寫(xiě)內(nèi)存,而不需要復(fù)制任何數(shù)據(jù),而管道、消息隊(duì)列等通信方式,則需要在內(nèi)核和用戶空間進(jìn)行四次數(shù)據(jù)復(fù)制。
并且只有在解除映射時(shí),共享內(nèi)存的內(nèi)容才會(huì)寫(xiě)會(huì)文紀(jì)念
共享內(nèi)存通過(guò)內(nèi)核對(duì)象,使得不同的進(jìn)程在自己的虛擬地址空間上分配一塊空間映射到相同的物理內(nèi)存空間上,這塊物理內(nèi)存空間對(duì)于映射到上面的每個(gè)進(jìn)程而言都是可以訪問(wèn)的。(臨界資源)
共享內(nèi)存就是允許兩個(gè)不相關(guān)的進(jìn)程訪問(wèn)同一個(gè)邏輯內(nèi)存。
共享內(nèi)存是在兩個(gè)正在運(yùn)行的進(jìn)程之間共享和傳遞數(shù)據(jù)的一種非常有效的方式。
不同進(jìn)程之間共享的內(nèi)存通常安排為同一段物理內(nèi)存。
進(jìn)程可以將同一段共享內(nèi)存連接到它們自己的地址空間中,所有進(jìn)程都可以訪問(wèn)共享內(nèi)存中的地址,就好像它們是由用C語(yǔ)言函數(shù)malloc()分配的內(nèi)存一樣。
而如果某個(gè)進(jìn)程向共享內(nèi)存寫(xiě)入數(shù)據(jù),所做的改動(dòng)將立即影響到可以 訪問(wèn)同一段共享內(nèi)存的任何其他進(jìn)程。
mmap()及其相關(guān)的系統(tǒng)調(diào)用
mmap是linux操作系統(tǒng)提供給用戶空間調(diào)用的內(nèi)存映射函數(shù),很多人僅僅只是知道可以通過(guò)mmap完成進(jìn)程間的內(nèi)存共享和減少用戶態(tài)到內(nèi)核態(tài)的數(shù)據(jù)拷貝次數(shù),但是并沒(méi)有深入理解mmap在操作系統(tǒng)內(nèi)部是如何實(shí)現(xiàn)的,原理是什么
mmap()系統(tǒng)調(diào)用使得進(jìn)程之間可以通過(guò)映射同一個(gè)普通文件實(shí)現(xiàn)內(nèi)存共享。普通文件被映射到進(jìn)程地址空間后,進(jìn)程可以訪問(wèn)普通內(nèi)存一樣對(duì)文件進(jìn)行訪問(wèn),不必再調(diào)用read和write操作。
注意: mmap并不是完全為了IPC而設(shè)計(jì)的,只是IPC的一種應(yīng)用方式,它本身提供了一種像訪問(wèn)普通內(nèi)存一樣的訪問(wèn)對(duì)普通文件進(jìn)行操作的方式。
通過(guò)使用帶有特殊權(quán)限集的虛擬內(nèi)存段來(lái)實(shí)現(xiàn)。對(duì)這類(lèi)虛擬內(nèi)存段的讀寫(xiě)會(huì)使操作系統(tǒng)去讀寫(xiě)磁盤(pán)文件中與之對(duì)應(yīng)的部分。
mmap 函數(shù)創(chuàng)建一個(gè)指向一段內(nèi)存區(qū)域的指針,該內(nèi)存區(qū)域與可以通過(guò)一個(gè)打開(kāi)的文件描述符訪問(wèn)的文件的內(nèi)容相關(guān)聯(lián)
解釋如下:
mmap()
#include <sys/mman.h> void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
可以通過(guò)傳遞 offset 參數(shù)來(lái)改變經(jīng)共享內(nèi)存段訪問(wèn)的文件中數(shù)據(jù)的起始偏移值。
打開(kāi)的文件描述符由 fd 參數(shù)給出。
可以訪問(wèn)的數(shù)據(jù)量(即內(nèi)存段的長(zhǎng)度)由 length 參數(shù)設(shè)置。
可以通過(guò) addr 參數(shù)來(lái)請(qǐng)求使用某個(gè)特定的內(nèi)存地址。如果它的取值是零,結(jié)果指針就將自動(dòng)分配。這是推薦的做法,否則會(huì)降低程序的可移植性,因?yàn)椴煌到y(tǒng)上的可用地址范圍是不一樣的。
prot 參數(shù)用于設(shè)置內(nèi)存段的訪問(wèn)權(quán)限。它是下列常數(shù)值的按位或的結(jié)果
- PROT_READ 內(nèi)存段可讀。
- PROT_WRITE 內(nèi)存段可寫(xiě)。
- PROT_EXEC 內(nèi)存段可執(zhí)行。
- PROT_NONE 內(nèi)存段不能被訪問(wèn)。
flags 參數(shù)控制程序?qū)υ搩?nèi)存段的改變所造成的影響:
mmap()用于共享內(nèi)存的量和兩種方式如下:
使用普通文件提供的內(nèi)存映射,適用于任何進(jìn)程間,使用該方式需要先打開(kāi)或者創(chuàng)建一個(gè)文件,再調(diào)用ngmmap,典型調(diào)用代碼如下:
fd = open(name.falg.mode); if(fd < 0) ptr = mmap(NULL,len.PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
使用特殊文件提供的內(nèi)存映射,適用于具有親緣關(guān)系的進(jìn)程之間,由于父子進(jìn)程特殊的親緣關(guān)系,在父進(jìn)程中先調(diào)用mmap,調(diào)用fork,那么在代用fork之后,子進(jìn)程可以繼承父進(jìn)程匿名映射后的地址空間,同樣也繼承mmap返回的地址,這樣父子進(jìn)程就可以通過(guò)映射區(qū)域進(jìn)行通信了。(注意:一般來(lái)說(shuō),子進(jìn)程單獨(dú)維護(hù)從父進(jìn)程繼承而來(lái)的一些變量,而mmap()返回的地址由父子進(jìn)程共同維護(hù))【具體使用實(shí)現(xiàn)敬請(qǐng)期待博主整理】
munmap()
用于解除內(nèi)存映射,取消參數(shù)start所指的映射內(nèi)存的起始地址,參數(shù)length則是欲取消的內(nèi)存大小,當(dāng)進(jìn)程結(jié)束或者利用exec相關(guān)函數(shù)來(lái)執(zhí)行其他程序時(shí),映射內(nèi)存會(huì)自動(dòng)解除,但關(guān)閉對(duì)應(yīng)的文件描述符時(shí)不會(huì)解除映射。
#include <sys/mman.h> int munmap(void *addr, size_t length);
共享內(nèi)存的使用
與信號(hào)量一樣,在Linux中也提供了一組函數(shù)接口用于使用共享內(nèi)存,而且使用共享共存的接口還與信號(hào)量的非常相似,而且比使用信號(hào)量的接口來(lái)得簡(jiǎn)單。它們聲明在頭文件 sys/shm.h
中。
1.獲取或創(chuàng)建內(nèi)核對(duì)象,并且制定共享內(nèi)存的大?。ㄏ到y(tǒng)分配物理空間是,按照頁(yè)進(jìn)行分配)
int shmget(key_t key, int size, int flag);
只是創(chuàng)建內(nèi)核對(duì)象,并申請(qǐng)物理空間
key_t key
:與信號(hào)量的semget函數(shù)一樣,程序需要提供一個(gè)參數(shù)key(非0整數(shù)),它有效地為共享內(nèi)存段命名,不同的進(jìn)程通過(guò)相同的key值來(lái)訪問(wèn)同一塊共享內(nèi)存int size
:size以字節(jié)為單位指定需要共享的內(nèi)存容量int flag
:falg是權(quán)限標(biāo)志,它的作用與open函數(shù)的mode參數(shù)一樣,如果要想在key標(biāo)識(shí)的共享內(nèi)存不存在時(shí),創(chuàng)建它的話,可以與IPC_CREAT做或操作。共享內(nèi)存的權(quán)限標(biāo)志與文件的讀寫(xiě)權(quán)限一樣,舉例來(lái)說(shuō),0644,它表示允許一個(gè)進(jìn)程創(chuàng)建的共享內(nèi)存被內(nèi)存創(chuàng)建者所擁有的進(jìn)程向共享內(nèi)存讀取和寫(xiě)入數(shù)據(jù),同時(shí)其他用戶創(chuàng)建的進(jìn)程只能讀取共享內(nèi)存。
返回值
- shmget()函數(shù)成功時(shí)返回一個(gè)與key相關(guān)的共享內(nèi)存標(biāo)識(shí)符(非負(fù)整數(shù)),用于后續(xù)的共享內(nèi)存函數(shù)。
- 調(diào)用失敗返回-1.
2.分配自己虛擬地址空間映射到共享內(nèi)存的物理空間上
void *shmat(int shmid,const void *addr, int flag);
shmid
:shmid是由shmget()函數(shù)返回的共享內(nèi)存標(biāo)識(shí)。void *addr
:addr指定共享內(nèi)存連接到當(dāng)前進(jìn)程中的地址位置,通常為NULL,表示讓系統(tǒng)來(lái)選擇共享內(nèi)存的地址。int flag
:flag是一組標(biāo)志位,通常為0。
調(diào)用成功時(shí)返回一個(gè)指向共享內(nèi)存第一個(gè)字節(jié)的指針,如果調(diào)用失敗返回-1.
3.斷開(kāi)當(dāng)前進(jìn)程與共享內(nèi)存的映射
不使用刪除而使用斷開(kāi)的原因是因?yàn)椋阂苍S還存在其他的進(jìn)程會(huì)繼續(xù)使用這塊共享內(nèi)存
int shmdt(const void *addr);
4.操作共享內(nèi)存的方法
int shmctl(int shmid, int cmd, struct shmid_t *buf);
int shmid
:shmid是shmget()函數(shù)返回的共享內(nèi)存標(biāo)識(shí)符。int cmd
:command是要采取的操作,它可以取下面的三個(gè)值 :
IPC_STAT:把shmid_ds結(jié)構(gòu)中的數(shù)據(jù)設(shè)置為共享內(nèi)存的當(dāng)前關(guān)聯(lián)值,即用共享內(nèi)存的當(dāng)前關(guān)聯(lián)值覆蓋shmid_ds的值。
IPC_SET:如果進(jìn)程有足夠的權(quán)限,就把共享內(nèi)存的當(dāng)前關(guān)聯(lián)值設(shè)置為shmid_ds結(jié)構(gòu)中給出的值
IPC_RMID:刪除共享內(nèi)存段
struct shmid_t *buf
:buf是一個(gè)結(jié)構(gòu)指針,它指向共享內(nèi)存模式和訪問(wèn)權(quán)限的結(jié)構(gòu)
因?yàn)橛羞B接計(jì)數(shù)器,除非最后一個(gè)進(jìn)程與該共享段斷開(kāi)連接,則刪除該共享段。否則,并不會(huì)真正刪除該共享段,但是共享內(nèi)存的內(nèi)核對(duì)象會(huì)被立即刪除,不能使用shmat方法與該段連接。
一個(gè)進(jìn)程調(diào)用該方法刪除后,不會(huì)影響之前已經(jīng)和該共享存儲(chǔ)段連接的進(jìn)程
下面我們利用共享內(nèi)存來(lái)進(jìn)行一個(gè)簡(jiǎn)單的測(cè)試:
完成下面的過(guò)程,
成功在共享內(nèi)存中讀到了數(shù)據(jù)
#include<stdio.h> #include<stdlib.h> #include<assert.h> #include<string.h> #include<unistd.h> #include<sys/types.h> #include<sys/ipc.h> #include<sys/shm.h> #include"sem.h" #define READSEM 1 #define WRITESEM 0 int main() { int shmid = shmget((key_t)1234,128,0664 | IPC_CREAT); assert(shmid != -1); char *ptr = (char*)shmat(shmid,NULL,0); assert(ptr != (char*)-1); int initVal[] = {1,0}; int semid = SemGet(1234,intVal,2); assert(semid != -1); //A進(jìn)程寫(xiě) while(1) { SemP(semid,WRITESEM); printf("Input:"); fgets(ptr,127,stdin); SemV(semid,READSEM); if(strncmp(ptr,"end",3) == 0) { break; } } shmdt(ptr); exit(0); }
#include<stdio.h> #include<stdlib.h> #include<assert.h> #include<string.h> #include<unistd.h> #include<sys/types.h> #include<sys/ipc.h> #include<sys/shm.h> #include"sem.h" #define READSEM 1 #define WRITESEM 0 int main() { int shmid = shmget((key_t)1234,128,0664 | IPC_CREAT); assert(shmid != -1); char *ptr = (char*)shmat(shmid,NULL,0); assert(ptr != (char*)-1); int initVal[] = {1,0}; int semid = SemGet(1234,intVal,2); assert(semid != -1); //B進(jìn)程讀 while(1) { SemP(semid,READSEM); if(strncmp(ptr,"end",3) == 0) { break; } int i = 0; for(;i < strlen(ptr) - 1;i++) { printf("%c",toupper(ptr[i])); fflush(stdout); sleep(1); } printf("\n"); SemV(semid,WRITESEM); } shmdt(ptr); exit(0); }
從上面的代碼中我們可以看出:
共享內(nèi)存是最快的IPC,在通信過(guò)程中少了兩次數(shù)據(jù)的拷貝。(相較于管道)
命令管理共享內(nèi)存
- 查看 ipcs -m
- 刪除 ipcrm -m shmid
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
阿里云云服務(wù)器Linux系統(tǒng)掛載數(shù)據(jù)盤(pán)圖文教程
這篇文章主要介紹了阿里云云服務(wù)器Linux系統(tǒng)掛載數(shù)據(jù)盤(pán)圖文教程,阿里云服務(wù)器一般需要購(gòu)買(mǎi)額外的數(shù)據(jù)盤(pán),本文就講解如何掛載使用額外的數(shù)據(jù)盤(pán),需要的朋友可以參考下2014-09-09Ubuntu16.04安裝clion全過(guò)程及步驟詳解
這篇文章主要介紹了Ubuntu16.04安裝clion全過(guò)程及步驟詳解,clion是一款JetBrains 推出的全新的 C/C++ 跨平臺(tái)集成開(kāi)發(fā)環(huán)境,在Ubuntu系統(tǒng)下使用方便,下面給大家分享操作步驟,需要的朋友可以參考下2020-08-08Linux之進(jìn)程的虛擬地址空間,邏輯地址和物理地址,進(jìn)程管理命令
這篇文章主要介紹了Linux之進(jìn)程的虛擬地址空間,邏輯地址和物理地址,進(jìn)程管理命令,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-03-03