Linux进程间通信(四) - 共享内存
共享內存的優勢
采用共享內存通信的一個顯而易見的好處是效率高,因為進程可以直接讀寫內存,而不需要任何數據的拷貝。對于像管道和消息隊列等通信方式,則需要在內核和用戶空間進行四次的數據拷貝,而共享內存則只拷貝兩次數據:一次從輸入文件到共享內存區,另一次從共享內存區到輸出文件。實際上,進程之間在共享內存時,并不總是讀寫少量數據后就解除映射,有新的通信時,再重新建立共享內存區域。而是保持共享區域,直到通信完畢為止,這樣,數據內容一直保存在共享內存中,并沒有寫回文件。共享內存中的內容往往是在解除映射時才寫回文件的。因此,采用共享內存的通信方式效率是非常高的。
圖1:POSIX消息隊列
圖2:共享內存
Linux的2.6.x內核支持多種共享內存方式,如mmap()系統調用,Posix共享內存,以及System V共享內存。本文對3種共享內存形式都將進行介紹。
mmap調用
mmap()系統調用使得進程之間通過映射同一個普通文件實現共享內存。普通文件被映射到進程地址空間后,進程可以像訪問普通內存一樣對文件進行訪問,不必再調用read(),write()等操作。
注:實際上,mmap()系統調用并不是完全為了用于共享內存而設計的。它本身提供了不同于一般對普通文件的訪問方式,進程可以像讀寫內存一樣對普通文件的操作。而Posix或System V的共享內存IPC則純粹用于共享目的,當然mmap()實現共享內存也是其主要應用之一。
圖3:直接映射文件
圖4:開辟共享內存空間
linux采用的是頁式管理機制。對于用mmap()映射普通文件來說,進程會在自己的地址空間新增一塊空間,空間大小由mmap()的length參數指定,注意,進程并不一定能夠對全部新增空間都能進行有效訪問。進程能夠訪問的有效地址大小取決于文件被映射部分的大小。簡單的說,能夠容納文件被映射部分大小的最少頁面個數決定了進程從mmap()返回的地址開始,能夠有效訪問的地址空間大小。超過這個空間大小,內核會根據超過的嚴重程度返回發送不同的信號給進程。如下圖所示:
圖5:mmap映射
這個具體差異跟系統實現有關,這里不做詳細討論。
數據結構
文件詳細信息
struct stat {
?? dev_t???? st_dev;???? /* ID of device containing file */
?? ino_t???? st_ino;???? /* inode number */
?? mode_t??? st_mode;??? /* protection */
?? nlink_t?? st_nlink;?? /* number of hard links */
?? uid_t???? st_uid;???? /* user ID of owner */
?? gid_t???? st_gid;???? /* group ID of owner */
?? dev_t???? st_rdev;??? /* device ID (if special file) */
?? off_t???? st_size;??? /* total size, in bytes */
?? blksize_t st_blksize; /* blocksize for file system I/O */
?? blkcnt_t? st_blocks;? /* number of 512B blocks allocated */
?? time_t??? st_atime;?? /* time of last access */
?? time_t??? st_mtime;?? /* time of last modification */
?? time_t??? st_ctime;?? /* time of last status change */
};
函數說明
#include <sys/mman.h>
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
void *mmap64(void *addr, size_t length, int prot, int flags, int fd, off64_t offset);
?? fd:為即將映射到進程空間的文件描述字,一般由open()返回,同時,fd可以指定為-1,此時須指定flags參數中的MAP_ANON,表明進行的是匿名映射(不涉及具體的文件名,避免了文件的創建及打開,很顯然只能用于具有親緣關系的進程間通信)。
?? Length:映射到調用進程地址空間的字節數,它從被映射文件開頭offset個字節開始算起。prot:指定共享內存的訪問權限。可取如下幾個值的或:PROT_READ(可讀),PROT_WRITE(可寫),PROT_EXEC(可執行),PROT_NONE(不可訪問)。
?? flags:由以下幾個常值指定MAP_SHARED, MAP_PRIVATE, MAP_FIXED等,其中,MAP_SHARED, MAP_PRIVATE必選其一,而MAP_FIXED則不推薦使用。
ü? MAP_SHARED 對映射區域的寫入數據會復制回文件內,而且允許其他映射該文件的進程共享。
ü? MAP_PRIVATE 對映射區域的寫入操作會產生一個映射文件的復制,即私人的“寫入時復制”(copy on write)對此區域作的任何修改都不會寫回原來的文件內容。
ü? MAP_FIXED 如果參數start所指的地址無法成功建立映射時,則放棄映射,不對地址做修正。通常不鼓勵用此旗標。
ü? MAP_ANONYMOUS 建立匿名映射。此時會忽略參數fd,不涉及文件,而且映射區域無法和其他進程共享。
ü? MAP_LOCKED 將映射區域鎖定住,這表示該區域不會被置換(swap)。
?? offset:一般設為0,表示從文件頭開始映射。
?? addr:指定文件應被映射到進程空間的起始地址,一般被指定一個空指針,此時選擇起始地址的任務留給內核來完成。函數的返回值為最后文件映射到進程空間的地址,進程可直接操作起始地址為該值的有效地址。
?
int munmap(void *addr, size_t length);
該調用在進程地址空間中解除一個映射關系,addr是調用mmap()時返回的地址,length是映射區的大小。當映射關系解除后,對原來映射地址的訪問將導致段錯誤發生。
?
int msync(void *addr, size_t length, int flags);
一般說來,進程在映射空間的對共享內容的改變并不直接寫回到磁盤文件中,往往在調用munmap()后才執行該操作。可以通過調用msync()實現磁盤上文件內容與共享內存區的內容一致。
?
// 調整fd所指的文件的大小到length
int ftruncate(int fd, off_t length);
?
// 獲取fd所指的文件的詳細信息
int fstat(int fd, struct stat *buf);
代碼說明
寫共享內存
#include <sys/mman.h> #include <fcntl.h> #include <sys/mman.h> #include <sys/types.h> #include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <errno.h> #include <string.h>typedef struct {char name[32];int age; } people;main(int argc, char** argv) {people* p_map;char temp = 'a';int fd = open(argv[1], O_CREAT|O_RDWR|O_TRUNC, 00777);if (-1 == fd){printf("open file error = %s\n", strerror(errno));return -1;}ftruncate(fd, sizeof(people)*10);p_map = (people*)mmap(NULL, sizeof(people)*10, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);if (MAP_FAILED == p_map){printf("mmap file error = %s\n", strerror(errno));return -1;}for(int i = 0; i < 10; i++){memcpy( ( *(p_map+i) ).name, &temp, 1);( *(p_map+i) ).name[1] = 0;( *(p_map+i) ).age = 20+i;temp += 1;}printf("initialize over\n");close(fd);munmap(p_map, sizeof(people)*10);printf("umap ok \n");return 0; }讀共享內存
#include <fcntl.h> #include <sys/mman.h> #include <sys/types.h> #include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <errno.h> #include <string.h> #include <sys/stat.h>typedef struct {char name[32];int age; } people;main(int argc, char** argv) {people* p_map;struct stat filestat;int fd = open(argv[1], O_CREAT|O_RDWR, 00777);if (-1 == fd){printf("open file error = %s\n", strerror(errno));return -1;}fstat(fd, &filestat);p_map = (people*)mmap(NULL, filestat.st_size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);if (MAP_FAILED == p_map){printf("mmap file error = %s\n", strerror(errno));return -1;}for(int i = 0; i < 10; i++){printf("name = %s, age = %d\n",(*(p_map+i)).name, (*(p_map+i)).age);}close(fd);munmap(p_map, sizeof(people)*10);printf("umap ok \n");return 0; }結果說明
[root@rocket ipc]# g++ -g -o ipc_mmap_writer ipc_mmap_writer.cpp
[root@rocket ipc]# ./ipc_mmap_writer /tmp/mmap_text.file
initialize over
umap ok
[root@rocket ipc]# g++ -g -o ipc_mmap_reader ipc_mmap_reader.cpp
[root@rocket ipc]# ./ipc_mmap_reader /tmp/mmap_text.file
name = a, age = 20
name = b, age = 21
name = c, age = 22
name = d, age = 23
name = e, age = 24
name = f, age = 25
name = g, age = 26
name = h, age = 27
name = i, age = 28
name = j, age = 29
umap ok
查看mmap文件
[root@rocket ipc]# ll /tmp/mmap_text.file
-rwxr-xr-x. 1 root root 360 Oct 14 02:55 /tmp/mmap_text.file
POSIX共享內存
POSIX共享內存使用方法有以下兩個步驟:
?? 通過shm_open創建或打開一個POSIX共享內存對象
?? 調用mmap將它映射到當前進程的地址空間
和通過內存映射文件進行通信的使用上差別在于mmap描述符參數獲取方式不一樣:通過open或shm_open。如下圖所示:
圖6:Posix內存映射文件
POSIX共享內存和POSIX消息隊列,有名信號量一樣都是具有隨內核持續性的特點。
在Linux 2.6.x中,對于POSIX信號量和共享內存的名字會在/dev/shm下建立對應的路徑名
[root@rocket shm]# ll /dev/shm/|grep mem
-rwxr-xr-x. 1 root root????? 360 Oct 14 05:23 shm_from_mem.txt
函數說明
#include <sys/mman.h>
#include <sys/stat.h>??????? /* For mode constants */
#include <fcntl.h>? ???????? /* For O_* constants */
// 打開一個共享內存的文件句柄
int shm_open(const char *name, int oflag, mode_t mode);
注意這里的名字具有形式 /somename,即必須以 / 為開頭,因為POSIX共享內存對應的文件是位于/dev/shm這個特殊的文件系統內。
?
// 刪除一個共享內存的名字,但只有所有程序都關閉,才會真的刪除
int shm_unlink(const char *name);
代碼說明
寫共享內存
#include <sys/mman.h> #include <fcntl.h> #include <sys/mman.h> #include <sys/types.h> #include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <errno.h> #include <string.h>typedef struct {char name[32];int age; } people;main(int argc, char** argv) {people* p_map;char temp = 'a';int fd = shm_open(argv[1], O_CREAT|O_RDWR, 00777);if (-1 == fd){printf("open file error = %s\n", strerror(errno));return -1;}ftruncate(fd, sizeof(people)*10);p_map = (people*)mmap(NULL, sizeof(people)*10, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);if (MAP_FAILED == p_map){printf("mmap file error = %s\n", strerror(errno));return -1;}for(int i = 0; i < 10; i++){memcpy( ( *(p_map+i) ).name, &temp, 1);( *(p_map+i) ).name[1] = 0;( *(p_map+i) ).age = 20+i;temp += 1;}printf("initialize over\n");close(fd);munmap(p_map, sizeof(people)*10);printf("umap ok \n");return 0; }讀共享內存
?
#include <fcntl.h> #include <sys/mman.h> #include <sys/types.h> #include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <errno.h> #include <string.h> #include <sys/stat.h>typedef struct {char name[32];int age; } people;main(int argc, char** argv) {people* p_map;struct stat filestat;int fd = shm_open(argv[1], O_CREAT|O_RDWR, 00777);if (-1 == fd){printf("open file error = %s\n", strerror(errno));return -1;}fstat(fd, &filestat);p_map = (people*)mmap(NULL, filestat.st_size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);if (MAP_FAILED == p_map){printf("mmap file error = %s\n", strerror(errno));return -1;}for(int i = 0; i < 10; i++){printf("name = %s, age = %d\n",(*(p_map+i)).name, (*(p_map+i)).age);}close(fd);munmap(p_map, sizeof(people)*10);printf("umap ok \n");return 0; }刪除共享內存
#include <fcntl.h> #include <sys/mman.h> #include <sys/types.h> #include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <errno.h> #include <string.h> #include <sys/stat.h>main(int argc, char** argv) {int ret = shm_unlink(argv[1]);if (-1 == ret){printf("unlink shm error = %s\n", strerror(errno));return -1;}printf("unlink ok \n");return 0; }結果說明
[root@rocket ipc]# g++ -g -o ipc_posix_mmap_writer ipc_posix_mmap_writer.cpp -lrt
[root@rocket ipc]# ./ipc_posix_mmap_writer /shm_from_mem.txt
initialize over
umap ok
[root@rocket ipc]# g++ -g -o ipc_posix_mmap_reader ipc_posix_mmap_reader.cpp -lrt
[root@rocket ipc]# ./ipc_posix_mmap_reader /shm_from_mem.txt
name = a, age = 20
name = b, age = 21
name = c, age = 22
name = d, age = 23
name = e, age = 24
name = f, age = 25
name = g, age = 26
name = h, age = 27
name = i, age = 28
name = j, age = 29
umap ok
?
[root@rocket ipc]# ./ipc_posix_mmap_unlink /shm_from_mem.txt
unlink ok
[root@rocket ipc]# ./ipc_posix_mmap_unlink /shm_from_mem.txt
unlink shm error = No such file or directory
[root@rocket ipc]# ll /dev/shm/|grep mem
[root@rocket ipc]#
可以看到/dev/shm下面的shm_from_mem.txt已經被刪除了。
System V共享內存
系統調用mmap()通過映射一個普通文件實現共享內存。System V則是通過映射特殊文件系統shm中的文件實現進程間的共享內存通信。也就是說,每個共享內存區域對應特殊文件系統shm中的一個文件(這是通過shmid_kernel結構聯系起來的)。進程間需要共享的數據被放在一個叫做IPC共享內存區域的地方,所有需要訪問該共享區域的進程都要把該共享區域映射到本進程的地址空間中去。System V共享內存通過shmget獲得或創建一個IPC共享內存區域,并返回相應的標識符。內核在保證shmget獲得或創建一個共享內存區,初始化該共享內存區相應的shmid_kernel結構注同時,還將在特殊文件系統shm中,創建并打開一個同名文件,并在內存中建立起該文件的相應dentry及inode結構,新打開的文件不屬于任何一個進程(任何進程都可以訪問該共享內存區)。所有這一切都是系統調用shmget完成的。
每一個共享內存區都有一個控制結構struct shmid_kernel,shmid_kernel是共享內存區域中非常重要的一個數據結構,它是存儲管理和文件系統結合起來的橋梁,定義如下:
struct shmid_kernel /* private to the kernel */
{???????
???????? struct kern_ipc_perm????? shm_perm;
???????? struct file *??????????????? shm_file;
???????? int?????????????????????? id;
???????? unsigned long?????????? shm_nattch;
???????? unsigned long?????????? shm_segsz;
???????? time_t???????????????????????? shm_atim;
???????? time_t???????????????????????? shm_dtim;
???????? time_t???????????????????????? shm_ctim;
???????? pid_t??????????????????????????? shm_cprid;
???????? pid_t??????????????????????????? shm_lprid;
};
該結構中最重要的一個域應該是shm_file,它存儲了將被映射文件的地址。每個共享內存區對象都對應特殊文件系統shm中的一個文件,一般情況下,特殊文件系統shm中的文件是不能用read()、write()等方法訪問的,當采取共享內存的方式把其中的文件映射到進程地址空間后,可直接采用訪問內存的方式對其訪問。
圖7:System V共享內存內核結構
內核通過數據結構struct ipc_ids shm_ids維護系統中的所有共享內存區域。上圖中的shm_ids.entries變量指向一個ipc_id結構數組,而每個ipc_id結構數組中有個指向kern_ipc_perm結構的指針。到這里讀者應該很熟悉了,對于系統V共享內存區來說,kern_ipc_perm的宿主是shmid_kernel結構,shmid_kernel是用來描述一個共享內存區域的,這樣內核就能夠控制系統中所有的共享區域。同時,在shmid_kernel結構的file類型指針shm_file指向文件系統shm中相應的文件,這樣,共享內存區域就與shm文件系統中的文件對應起來。
在創建了一個共享內存區域后,還要將它映射到進程地址空間,系統調用shmat()完成此項功能。由于在調用shmget()時,已經創建了文件系統shm中的一個同名文件與共享內存區域相對應,因此,調用shmat()的過程相當于映射文件系統shm中的同名文件過程,原理與mmap()大同小異。
函數說明
#include <sys/ipc.h>
#include <sys/shm.h>
// 獲取共享內存區域
int shmget(key_t key, size_t size, int shmflg);
// 連接共享內存區域
void *shmat(int shmid, const void *shmaddr, int shmflg);
// 斷開共享內存區域
int shmdt(const void *shmaddr);
// 對共享內存區域進行控制
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
// 將path和proj_id轉換成System V IPC key
key_t ftok(const char *pathname, int proj_id);
代碼說明
寫共享內存
#include <sys/mman.h> #include <fcntl.h> #include <sys/mman.h> #include <sys/types.h> #include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <errno.h> #include <string.h> #include <sys/ipc.h> #include <sys/shm.h>typedef struct {char name[32];int age; } people;int main(int argc, char** argv) {int shm_id,i;key_t key;people* p_map;char temp = 'a';const char* name = "/dev/shm/my_systemv_shm1";key = ftok(name,0);if (key == -1){perror("ftok error");return -1;}shm_id=shmget(key, 4096, IPC_CREAT);if(shm_id == -1){perror("shmget error");return -1;}p_map=(people*)shmat(shm_id,NULL,0);for(int i = 0; i < 10; i++){memcpy( ( *(p_map+i) ).name, &temp, 1);( *(p_map+i) ).name[1] = 0;( *(p_map+i) ).age = 20+i;temp += 1;}printf("initialize over\n");if(shmdt(p_map) == -1){perror(" detach error ");return -1;}return 0; }讀共享內存
#include <sys/mman.h> #include <fcntl.h> #include <sys/mman.h> #include <sys/types.h> #include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <errno.h> #include <string.h> #include <sys/ipc.h> #include <sys/shm.h>typedef struct {char name[32];int age; } people;int main(int argc, char** argv) {int shm_id,i;key_t key;people* p_map;const char* name = "/dev/shm/my_systemv_shm1";key = ftok(name,0);if (key == -1){perror("ftok error");return -1;}shm_id=shmget(key, 4096, IPC_CREAT);if(shm_id == -1){perror("shmget error");return -1;}p_map=(people*)shmat(shm_id,NULL,0);for(int i = 0; i < 10; i++){printf( "name:%s, ",(*(p_map+i)).name );printf( "age %d\n",(*(p_map+i)).age );}if(shmdt(p_map) == -1){perror(" detach error ");return -1;}return 0; }結果說明
[root@rocket ipc]# g++ -g -o ipc_systemv_mmap_writer ipc_systemv_mmap_writer.cpp
[root@rocket ipc]# touch /dev/shm/my_systemv_shm1
[root@rocket ipc]# ./ipc_systemv_mmap_writer
initialize over
[root@rocket ipc]# g++ -g -o ipc_systemv_mmap_reader ipc_systemv_mmap_reader.cpp
[root@rocket ipc]# ./ipc_systemv_mmap_reader
name:a, age 20
name:b, age 21
name:c, age 22
name:d, age 23
name:e, age 24
name:f, age 25
name:g, age 26
name:h, age 27
name:i, age 28
name:j, age 29
?
觀察一下共享內存:
[root@rocket ipc]# ./get_ipc_key /dev/shm/my_systemv_shm1
key = 1084739
[root@rocket ipc]# ipcs
?
------ Shared Memory Segments --------
key??????? shmid????? owner????? perms????? bytes????? nattch???? status?????
0x00000000 0????????? gdm??????? 600??????? 393216???? 2????????? dest????????
0x00000000 32769????? gdm??????? 600??????? 393216???? 2????????? dest? ??????
0x00000000 65538????? gdm??????? 600??????? 393216???? 2????????? dest????????
0x00000000 98307????? gdm??????? 600??????? 393216???? 2????????? dest????????
0x00108d43 131076???? root?????? 0????????? 4096?????? 0 ??
?
看到我們新建的共享內存了吧?刪除也很簡單:
[root@rocket ipc]# ipcrm -m 131076
[root@rocket ipc]# ipcs
?
------ Shared Memory Segments --------
key??????? shmid????? owner????? perms????? bytes????? nattch???? status?????
0x00000000 0????????? gdm??????? 600??????? 393216???? 2????????? dest????????
0x00000000 32769????? gdm??????? 600??????? 393216???? 2????????? dest????????
0x00000000 65538????? gdm??????? 600??????? 393216???? 2????????? dest????????
0x00000000 98307????? gdm??????? 600??????? 393216???? 2????????? dest
?
總結及3種共享內存比較
共享內存是最快的IPC形式,在開發中,我們一定要充分利用好共享內存的特性,取得事半功倍的效果。
| 類型 | 原理 | 易失性 |
| mmap | 利用文件(open)映射共享內存區域 | 會保存在磁盤上,不會丟失 |
| Posix shared memory | 利用/dev/shm文件系統(mmap)映射共享內存區域 | 隨內核持續,內核自舉后會丟失 |
| SystemV shared memory | 利用/dev/shm文件系統(shmat)映射共享內存區域 | 隨內核持續,內核自舉后會丟失 |
?
?
轉載于:https://www.cnblogs.com/linuxbug/p/4882776.html
總結
以上是生活随笔為你收集整理的Linux进程间通信(四) - 共享内存的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Digital System Desig
- 下一篇: verilog定时打铃上下课程序设计