一、共用記憶體簡介
共用記憶體區是最快的IPC形式,這些進程間資料傳遞不再涉及到核心,換句話說是進程不再通過執行進入核心的系統調用來傳遞彼此的資料。
即每個進程地址空間都有一個共用儲存空間的映射區,當這塊地區都映射到相同的真正的物理地址空間時,可以通過這塊地區進行資料交換,例如共用庫就是這麼實現的,很多進程都會使用同一個函數如printf,也許在真正的物理地址空間中只存在一份printf.o ,然後所有進程都映射到這一份printf.o 就實現了共用。
用管道或者訊息佇列傳遞資料:
用共用記憶體傳遞資料:
即使有共用記憶體傳遞資料比用訊息佇列和管道來說,減少了進入核心的次數,提高了效率。
二、mmap 函數
#include <sys/mman.h>
功能:將檔案或者裝置空間映射到共用記憶體區。
原型 void *mmap(void *addr, size_t len, int prot, int flags, int fd, off_t offset);
參數
addr: 要映射的起始地址,通常指定為NULL,讓核心自動選擇
len:映射到進程地址空間的位元組數
prot:映射區保護方式
flags:標誌
fd:檔案描述符
offset:從檔案頭開始的位移量,必須是頁大小的整數倍(在32位體系統結構上通常是4K)
傳回值:成功返回映射到的記憶體區的起始地址;失敗返回-1
prot 參數取值:
PROT_EXEC 表示映射的這一段可執行,例如映射共用庫
PROT_READ 表示映射的這一段可讀
PROT_WRITE 表示映射的這一段可寫
PROT_NONE 表示映射的這一段不可訪問
flag參數有很多種取值,這裡只講兩種,其它取值可查看mmap(2)
MAP_SHARED 多個進程對同一個檔案的映射是共用的,一個進程對映射的記憶體做了修改,另一個進程也會看到這種變化。
MAP_PRIVATE 多個進程對同一個檔案的映射不是共用的,一個進程對映射的記憶體做了修改,另一個進程並不會看到這種變化,也不會真的寫到檔案中去。
記憶體對應檔:
如果mmap成功則返回映射首地址,如果出錯則返回常數MAP_FAILED。當進程終止時,該進程的映射記憶體會自動解除,也可以調用munmap解除映射:
功能:取消mmap函數建立的映射
原型 int munmap(void *addr, size_t len);
參數
addr: 映射的記憶體起始地址
len:映射到進程地址空間的位元組數
傳回值:成功返回0;失敗返回-1
下面寫兩個程式測試一下:
mmap_write.c
C++ Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
|
|
#include<string.h> #include<stdio.h> #include<stdlib.h> #include<sys/ipc.h> #include<sys/msg.h> #include<sys/types.h> #include<unistd.h> #include<errno.h> #include<fcntl.h> #include<sys/stat.h> #include<sys/mman.h>#define ERR_EXIT(m) \ do { \ perror(m); \ exit(EXIT_FAILURE); \ } while(0) typedef struct stu { char name[4]; int age; } STU; int main(int argc, char *argv[]) { if (argc != 2) { fprintf(stderr, "Usage: %s <file>\n", argv[0]); exit(EXIT_FAILURE); } int fd; fd = open(argv[1], O_CREAT | O_RDWR | O_TRUNC, 0666); if (fd == -1) ERR_EXIT("open"); lseek(fd, sizeof(STU) * 5 - 1, SEEK_SET); write(fd, "", 1); STU *p; p = (STU *)mmap(NULL, sizeof(STU) * 5, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (p == -1) ERR_EXIT("mmap"); char ch = 'a'; int i; for (i = 0; i < 5; i++) { memcpy((p + i)->name, &ch, 1); (p + i)->age = 20 + i; ch++; } printf("initialize over\n"); munmap(p, sizeof(STU) * 5); printf("exit...\n"); return 0; } |
mmap_read.c
C++ Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
|
|
#include<string.h> #include<stdio.h> #include<stdlib.h> #include<sys/ipc.h> #include<sys/msg.h> #include<sys/types.h> #include<unistd.h> #include<errno.h> #include<fcntl.h> #include<sys/stat.h> #include<sys/mman.h>#define ERR_EXIT(m) \ do { \ perror(m); \ exit(EXIT_FAILURE); \ } while(0) typedef struct stu { char name[4]; int age; } STU; int main(int argc, char *argv[]) { if (argc != 2) { fprintf(stderr, "Usage: %s <file>\n", argv[0]); exit(EXIT_FAILURE); } int fd; fd = open(argv[1], O_RDWR); if (fd == -1) ERR_EXIT("open"); STU *p; p = (STU *)mmap(NULL, sizeof(STU) * 5, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (p == -1) ERR_EXIT("mmap"); int i; for (i = 0; i < 5; i++) { printf("name = %s age = %d\n", (p + i)->name, (p + i)->age); } munmap(p, sizeof(STU) * 5); printf("exit...\n"); return 0; } |
先運行mmap_write ,然後用od -c 查看檔案內容:
simba@ubuntu:~/Documents/code/linux_programming/UNP/system_v$ ./mmap_write test
initialize over
exit...
simba@ubuntu:~/Documents/code/linux_programming/UNP/system_v$ od -c test
0000000 a \0 \0 \0 024 \0 \0 \0 b \0 \0 \0 025 \0 \0 \0
0000020 c \0 \0 \0 026 \0 \0 \0 d \0 \0 \0 027 \0 \0 \0
0000040 e \0 \0 \0 030 \0 \0 \0
0000050注意od -c 輸出的是八進位,024即20,即對記憶體的操作寫入了檔案。再嘗試運行mmap_read,輸出如下:simba@ubuntu:~/Documents/code/linux_programming/UNP/system_v$ ./mmap_read test
name = a age = 20
name = b age = 21
name = c age = 22
name = d age = 23
name = e age = 24
exit...
simba@ubuntu:~/Documents/code/linux_programming/UNP/system_v$
再次將檔案test 映射到記憶體,然後從記憶體讀取到了檔案的內容。
mmap 編程注意點:
1、映射不能改變檔案的大小;
2、可用於處理序間通訊的有效地址空間不完全受限於被對應檔的大小;
3、檔案一旦被映射後,所有對映射地區的訪問實際上是對記憶體地區的訪問。映射地區內容寫迴文件時,所寫內容不能超過檔案的大小;
對於1,3點,將mmap_write.c 中40行以後的代碼中的5改成10,即映射的記憶體大於檔案的大小,這樣寫入是不會出錯的,因為是向記憶體寫入,但用od 查看時發現檔案還是40 個位元組,即只有前5個STU才被真正寫入到了檔案。
對於第2點,將mmap_write.c 和 mmap_read.c 都按上面說的更改成10,然後在mmap_write.c 中munmap 函數之前sleep(10); 先運行mmap_write,再在另一終端運行mmap_read,觀察結果:
simba@ubuntu:~/Documents/code/linux_programming/UNP/system_v$ ./mmap_read test
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
exit...
即在mmap_write 對映射記憶體地區寫入之後尚未取消映射時,mmap_read 也映射了test 檔案,兩個虛擬進程地址空間的映射地區都指向了同一塊實體記憶體,所以也能讀到write 進程對記憶體的修改,但進程結束後查看test 檔案,還是40個位元組而已。記憶體的映射是以頁面為單位的,一般為4k,所以才有第2條的說法,其實這才是真正體現共用記憶體可以處理序間通訊的所在。
最後一點,與write 類似,將檔案對應到記憶體後對記憶體進行寫入,不一定會馬上寫迴文件,有可能核心也會產生一個緩衝區,找個適當的時間核心再寫回裝置檔案,write 之後可以調用fsync 進行同步,同樣地,mmap 可以調用msync 進行同步。
參考:
《linux c 編程一站式學習》
《UNP》