共享記憶體可以說是最有用的程序間通信方式,也是最快的IPC形式, 因為程序可以直接讀寫記憶體,而不需要任何
資料的拷貝。對于像管道和消息隊列等通信方式,則需要在核心和使用者空間進行四次的資料拷貝,而共享記憶體則
隻拷貝兩次資料: 一次從輸入檔案到共享記憶體區,另一次從共享記憶體區到輸出檔案。實際上,程序之間在共享内
存時,并不總是讀寫少量資料後就解除映射,有新的通信時,再重建立立共享記憶體區域。而是保持共享區域,直
到通信完畢為止,這樣,資料内容一直儲存在共享記憶體中,并沒有寫回檔案。共享記憶體中的内容往往是在解除映
射時才寫回檔案的。是以,采用共享記憶體的通信方式效率是非常高的。
一. 傳統檔案通路
UNIX通路檔案的傳統方法是用open打開它們, 如果有多個程序通路同一個檔案, 則每一個程序在自己的位址空間都包含有該
檔案的副本,這不必要地浪費了存儲空間. 下圖說明了兩個程序同時讀一個檔案的同一頁的情形. 系統要将該頁從磁盤讀到高
速緩沖區中, 每個程序再執行一個存儲器内的複制操作将資料從高速緩沖區讀到自己的位址空間.
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiI0gTMx81dsQWZ4lmZf1GLlpXazVmcvwFciV2dsQXYtJ3bm9CX9s2RkBnVHFmb1clWvB3MaVnRtp1XlBXe0xCMy81dvRWYoNHLwEzX5xCMx8FesU2cfdGLwMzX0xiRGZkRGZ0Xy9GbvNGLpZTY1EmMZVDUSFTU4VFRR9Fd4VGdsYTMfVmepNHLrJXYtJXZ0F2dvwVZnFWbp1zczV2YvJHctM3cv1Ce-cGcq5CN1kDN2UjNmZDZxkjNxETYyYzXxIzNzUDM5AzLcZDMyIDMy8CXn9Gbi9CXzV2Zh1WavwVbvNmLvR3YxUjLyM3Lc9CX6MHc0RHaiojIsJye.jpg)
二. 共享存儲映射
現在考慮另一種處理方法: 程序A和程序B都将該頁映射到自己的位址空間, 當程序A第一次通路該頁中的資料時, 它生成一
個缺頁中斷. 核心此時讀入這一頁到記憶體并更新頁表使之指向它.以後, 當程序B通路同一頁面而出現缺頁中斷時, 該頁已經在
記憶體, 核心隻需要将程序B的頁表登記項指向次頁即可. 如下圖所示:
三、mmap()及其相關系統調用
mmap()系統調用使得程序之間通過映射同一個普通檔案實作共享記憶體。普通檔案被映射到程序位址空間後,程序可以向訪
問普通記憶體一樣對檔案進行通路,不必再調用read(),write()等操作。
mmap()系統調用形式如下:
void* mmap ( void * addr , size_t len , int prot , int flags , int fd , off_t offset )
mmap的作用是映射檔案描述符fd指定檔案的 [off,off + len]區域至調用程序的[addr, addr + len]的記憶體區域, 如下圖所示:
參數fd為即将映射到程序空間的檔案描述字,一般由open()傳回,同時,fd可以指定為-1,此時須指定flags參數中的
MAP_ANON,表明進行的是匿名映射(不涉及具體的檔案名,避免了檔案的建立及打開,很顯然隻能用于具有親緣關系的
程序間通信)。
len是映射到調用程序位址空間的位元組數,它從被映射檔案開頭offset個位元組開始算起。
prot 參數指定共享記憶體的通路權限。可取如下幾個值的或:PROT_READ(可讀) , PROT_WRITE (可寫), PROT_EXEC (可執行), PROT_NONE(不可通路)。
flags由以下幾個常值指定:MAP_SHARED , MAP_PRIVATE , MAP_FIXED,其中,MAP_SHARED , MAP_PRIVATE必
選其一,而MAP_FIXED則不推薦使用。
offset參數一般設為0,表示從檔案頭開始映射。
參數addr指定檔案應被映射到程序空間的起始位址,一般被指定一個空指針,此時選擇起始位址的任務留給核心來完成。函
數的傳回值為最後檔案映射到程序空間的位址,程序可直接操作起始位址為該值的有效位址。
四. mmap的兩個例子
範例中使用的測試檔案 data.txt:
- aaaaaaaaa
- bbbbbbbbb
- ccccccccc
- ddddddddd
1 通過共享映射的方式修改檔案
1. #include <sys/mman.h>
2. #include <sys/stat.h>
3. #include <fcntl.h>
4. #include <stdio.h>
5. #include <stdlib.h>
6. #include <unistd.h>
7. #include <error.h>
8.
9. #define BUF_SIZE 100
10.
11. int main(int argc, char
12. {
13. int
14. struct
15. char
16.
17. for
18. '#';
19. }
20.
21. /* 打開檔案 */
22. if
23. "open");
24. }
25.
26. /* 擷取檔案的屬性 */
27. if
28. "fstat");
29. }
30.
31. /* 将檔案映射至程序的位址空間 */
32. if ((mapped = (char
33. void
34. "mmap");
35. }
36.
37. /* 映射完後, 關閉檔案也可以操縱記憶體 */
38. close(fd);
39.
40. "%s", mapped);
41.
42. /* 修改一個字元,同步到磁盤檔案 */
43. '9';
44. if ((msync((void
45. "msync");
46. }
47.
48. /* 釋放存儲映射區 */
49. if ((munmap((void
50. "munmap");
51. }
52.
53. return
54. }
2 私有映射無法修改檔案
/* 将檔案映射至程序的位址空間 */
if ((mapped = (char *)mmap(NULL, sb.st_size, PROT_READ |
PROT_WRITE,
MAP_PRIVATE
, fd, 0)) == (void *)-1) {
perror("mmap");
}
五. 使用共享映射實作兩個程序之間的通信
兩個程式映射同一個檔案到自己的位址空間, 程序A先運作, 每隔兩秒讀取映射區域, 看是否發生變化.
程序B後運作, 它修改映射區域, 然後推出, 此時程序A能夠觀察到存儲映射區的變化
程序A的代碼:
1. #include <sys/mman.h>
2. #include <sys/stat.h>
3. #include <fcntl.h>
4. #include <stdio.h>
5. #include <stdlib.h>
6. #include <unistd.h>
7. #include <error.h>
8.
9. #define BUF_SIZE 100
10.
11. int main(int argc, char
12. {
13. int
14. struct
15. char
16.
17. for
18. '#';
19. }
20.
21. /* 打開檔案 */
22. if
23. "open");
24. }
25.
26. /* 擷取檔案的屬性 */
27. if
28. "fstat");
29. }
30.
31. /* 将檔案映射至程序的位址空間 */
32. if ((mapped = (char
33. void
34. "mmap");
35. }
36.
37. /* 檔案已在記憶體, 關閉檔案也可以操縱記憶體 */
38. close(fd);
39.
40. /* 每隔兩秒檢視存儲映射區是否被修改 */
41. while
42. "%s\n", mapped);
43. sleep(2);
44. }
45.
46. return
47. }
程序B的代碼:
1. #include <sys/mman.h>
2. #include <sys/stat.h>
3. #include <fcntl.h>
4. #include <stdio.h>
5. #include <stdlib.h>
6. #include <unistd.h>
7. #include <error.h>
8.
9. #define BUF_SIZE 100
10.
11. int main(int argc, char
12. {
13. int
14. struct
15. char
16.
17. for
18. '#';
19. }
20.
21. /* 打開檔案 */
22. if
23. "open");
24. }
25.
26. /* 擷取檔案的屬性 */
27. if
28. "fstat");
29. }
30.
31. /* 私有檔案映射将無法修改檔案 */
32. if ((mapped = (char
33. void
34. "mmap");
35. }
36.
37. /* 映射完後, 關閉檔案也可以操縱記憶體 */
38. close(fd);
39.
40. /* 修改一個字元 */
41. '9';
42.
43. return
44. }
六. 通過匿名映射實作父子程序通信
1. #include <sys/mman.h>
2. #include <stdio.h>
3. #include <stdlib.h>
4. #include <unistd.h>
5.
6. #define BUF_SIZE 100
7.
8. int main(int argc, char** argv)
9. {
10. char
11.
12. /* 匿名映射,建立一塊記憶體供父子程序通信 */
13. char
14. MAP_SHARED | MAP_ANONYMOUS, -1, 0);
15.
16. if(fork() == 0) {
17. sleep(1);
18. "child got a message: %s\n", p_map);
19. "%s", "hi, dad, this is son");
20. //實際上,程序終止時,會自動解除映射。
21. exit(0);
22. }
23.
24. "%s", "hi, this is father");
25. sleep(2);
26. "parent got a message: %s\n", p_map);
27.
28. return
29. }
七. 對mmap()傳回位址的通路
linux采用的是頁式管理機制。對于用mmap()映射普通檔案來說,程序會在自己的位址空間新增一塊空間,空間大
小由mmap()的len參數指定,注意,程序并不一定能夠對全部新增空間都能進行有效通路。程序能夠通路的有效位址大小取決于檔案被映射部分的大小。簡單的說,能夠容納檔案被映射部分大小的最少頁面個數決定了
程序從mmap()傳回的位址開始,能夠有效通路的位址空間大小。超過這個空間大小,核心會根據超過的嚴重程度傳回發送不同的信号給程序。可用如下圖示說明:
1. #include <sys/mman.h>
2. #include <sys/types.h>
3. #include <sys/stat.h>
4. #include <fcntl.h>
5. #include <unistd.h>
6. #include <stdio.h>
7.
8. int main(int argc, char** argv)
9. {
10. int
11. int
12. char
13. struct
14.
15. /* 取得page size */
16. pagesize = sysconf(_SC_PAGESIZE);
17. "pagesize is %d\n",pagesize);
18.
19. /* 打開檔案 */
20. fd = open(argv[1], O_RDWR, 00777);
21. fstat(fd, &sb);
22. "file size is %zd\n", (size_t)sb.st_size);
23.
24. offset = 0;
25. char
26. MAP_SHARED, fd, offset);
27. close(fd);
28.
29. '9'; /* 導緻總線錯誤 */
30. '9'; /* 導緻段錯誤 */
31.
32. munmap(p_map, pagesize * 2);
33.
34. return
35. }