採用共享內存通訊的一個顯而易見的好處是效率高,由於進程能夠直接讀寫內存,而不須要任何數據的拷貝。對於像管道和消息隊列等通訊方式,則須要在內核和用戶空間進行四次的數據拷貝,而共享內存則只拷貝兩次數據:一次從輸入文件到共享內存區,另外一次從共享內存區到輸出文件。實際上,進程之間在共享內存時,並不老是讀寫少許數據後就解除映射,有新的通訊時,再從新創建共享內存區域。而是保持共享區域,直到通訊完畢爲止,這樣,數據內容一直保存在共享內存中,並無寫回文件。共享內存中的內容每每是在解除映射時才寫回文件的。所以,採用共享內存的通訊方式效率是很是高的。node
圖1:POSIX消息隊列linux
圖2:共享內存數組
Linux的2.6.x內核支持多種共享內存方式,如mmap()系統調用,Posix共享內存,以及System V共享內存。本文對3種共享內存形式都將進行介紹。數據結構
mmap()系統調用使得進程之間經過映射同一個普通文件實現共享內存。普通文件被映射到進程地址空間後,進程能夠像訪問普通內存同樣對文件進行訪問,沒必要再調用read(),write()等操做。ide
注:實際上,mmap()系統調用並非徹底爲了用於共享內存而設計的。它自己提供了不一樣於通常對普通文件的訪問方式,進程能夠像讀寫內存同樣對普通文件的操做。而Posix或System V的共享內存IPC則純粹用於共享目的,固然mmap()實現共享內存也是其主要應用之一。函數
圖3:直接映射文件ui
圖4:開闢共享內存空間spa
linux採用的是頁式管理機制。對於用mmap()映射普通文件來講,進程會在本身的地址空間新增一塊空間,空間大小由mmap()的length參數指定,注意,進程並不必定可以對所有新增空間都能進行有效訪問。進程可以訪問的有效地址大小取決於文件被映射部分的大小。簡單的說,可以容納文件被映射部分大小的最少頁面個數決定了進程從mmap()返回的地址開始,可以有效訪問的地址空間大小。超過這個空間大小,內核會根據超過的嚴重程度返回發送不一樣的信號給進程。以下圖所示:設計
圖5:mmap映射3d
這個具體差別跟系統實現有關,這裏不作詳細討論。
文件詳細信息
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
[root@rocket ipc]# ll /tmp/mmap_text.file
-rwxr-xr-x. 1 root root 360 Oct 14 02:55 /tmp/mmap_text.file
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已經被刪除了。
系統調用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
共享內存是最快的IPC形式,在開發中,咱們必定要充分利用好共享內存的特性,取得事半功倍的效果。
類型 |
原理 |
易失性 |
mmap |
利用文件(open)映射共享內存區域 |
會保存在磁盤上,不會丟失 |
Posix shared memory |
利用/dev/shm文件系統(mmap)映射共享內存區域 |
隨內核持續,內核自舉後會丟失 |
SystemV shared memory |
利用/dev/shm文件系統(shmat)映射共享內存區域 |
隨內核持續,內核自舉後會丟失 |