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