5.共享映射區(無血緣關系用的)
文章目錄
- 5.共享映射區(無血緣關系用的)
- 1.概述
- 2.mmap&&munmap函數
- 3.mmap注意事項
- 4.mmap實現進程通信
- 父子進程
- 練習
- 無血緣關系
- 5.mmap匿名映射區
1.概述
-
原理:共享映射區是將文件內容映射到進程的地址空間中,使得多個進程可以通過訪問這個共享的內存區域來實現通信。進程對映射區域的操作就如同對文件進行操作一樣,這些操作會直接反映在文件和其他共享該映射區域的進程中。
-
示例場景:多個進程需要共同操作一個配置文件,通過將該配置文件映射到共享映射區,進程可以直接在內存中讀取和修改配置信息,而不需要頻繁地進行文件 I/O 操作。
-
優點:結合了內存操作的高效性和文件存儲的持久性;可以方便地在不相關的進程之間實現通信,只要它們能訪問到同一個文件。
-
缺點:對文件的操作需要注意同步問題,否則可能導致數據不一致;文件大小可能會限制共享映射區的大小。
**存儲映射I/O(Memory-mapped l/O)使一個磁盤文件與內存存儲空間中的一個緩沖區相映射。**于是當從緩沖區中取數據,就相當于讀文件中的相應字節。于此類似,將數據存入緩沖區,則相應的字節就自動寫入文件。這樣,就可在不適用read和write函數的情況下,使用地址(指針)完成I/O操作。
使用這種方法,首先應通知內核,將一個指定文件映射到存儲區域中。這個映射工作可以通過mmap函數來實
現。
2.mmap&&munmap函數
創建共享內存映射
include<sys/mman.h>
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
參數:
-
addr: 指定映射區的首地址。通常傳NULL,表示讓系統自動分配
-
length:共享內存映射區的大小。(<= 文件的實際大小)
-
prot: 共享內存映射區的讀寫屬性。PROT_READ、PROT_WRITE、PROT_READ|PROT_WRITE
-
flags: 標注共享內存的共享屬性,共享就是對文件的修改會寫回磁盤,私有就不會寫回磁盤。MAP_SHARED、MAP_PRIVATE
-
fd: 用于創建共享內存映射區的那個文件的 文件描述符,就是要映射到內存的文件
-
offset:默認0,表示映射文件全部。偏移位置,從哪里開始映射。需是 4k 的整數倍
返回值:
成功:映射區的首地址
失敗:MAP_FAILED (void*(-1)), errno----就是把-1強轉為void *了
釋放共享內存映射
int munmap(void *addr, size_t length);
參數
addr:mmap 的返回值,共享內存映射首地址
length:大小
返回值
成功0,失敗-1
函數使用
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h>void sys_err(const char *str)
{perror(str);exit(1);
}int main(int argc, char *argv[])
{char *p = NULL;int fd;fd = open("testmap", O_RDWR|O_CREAT|O_TRUNC, 0644); // 創建文件用于創建映射區if (fd == -1)sys_err("open error");
/*lseek(fd, 10, SEEK_END); // 兩個函數等價于 ftruncate()函數write(fd, "\0", 1);
*/ftruncate(fd, 20); // 需要借助寫權限,才能夠對文件進行拓展int len = lseek(fd, 0, SEEK_END);p = mmap(NULL, len, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);if (p == MAP_FAILED) {sys_err("mmap error");}// 使用 p 對文件進行讀寫操作.strcpy(p, "hello mmap"); // 寫操作printf("----%s\n", p); // 讀操作int ret = munmap(p, len); // 釋放映射區if (ret == -1) {sys_err("munmap error");}return 0;
}
3.mmap注意事項
思考 :
-
可以open的時候O_CREAT一個新文件來創建映射區嗎 ?
-
如果open時O_RDONLY,mmap時PROT參數指定PROT_READ|PROT_WRITE會怎樣 ?
-
文件描述符先關閉,對mmap映射有沒有影響 ?
-
如果文件偏移量為1000會怎樣 ?
-
對mem越界操作會怎樣?
-
如果mem++,munmap可否成功 ?
-
mmap什么情況下會調用失敗 ?
很多參數都會導致失敗
-
如果不檢測mmap的返回值,會怎樣?
會死得很慘
使用注意事項:
- 用于創建映射區的文件大小為 0,卻指定非0大小創建映射區,出 “總線錯誤”。
- 用于創建映射區的文件大小為 0,也指定0大小創建映射區, 出 “無效參數”。
- 用于創建映射區的文件讀寫屬性為,只讀,映射區屬性為 讀、寫。 出 “無效參數”; 文件和映射區都是只讀的是可以的;文件只有寫權限,映射區只有寫權限也會報錯。(2答案)
- 創建映射區,需要read權限。當訪問權限指定為 “共享”MAP_SHARED是, mmap的讀寫權限應該 <=文件的open權限。 映射區只寫不行。
- 文件描述符fd,在mmap創建映射區完成即可關閉。后續訪問文件,用 地址訪問。(3答案)
- offset 必須是 4096的整數倍。(MMU 映射的最小單位 4k )(4答案)
- 對申請的映射區內存,不能越界訪問。 (5答案)
- 讀寫都沒問題,但是munmap會失敗,munmap用于釋放的 地址,必須是mmap申請返回的地址。(6答案)
- 映射區訪問權限為 “私有”MAP_PRIVATE, 對內存所做的所有修改,只在內存有效,不會反應到物理磁盤上。
- 映射區訪問權限為 “私有”MAP_PRIVATE, 只需要open文件時,文件有讀權限,用于創建映射區即可。
mmap函數的保險調用方式:
1. fd = open("文件名", O_RDWR);
2. mmap(NULL, 有效文件大小, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
4.mmap實現進程通信
父子進程
父子等有血緣關系的進程之間也可以通過mmap建立的映射區來完成數據通信。
但相應的要在創建映射區的時候指定對應的標志位參數flags:
MAP_PRIVATE:(私有映射)父子進程各自獨占映射區;
MAP_SHARED:(共享映射) 父子進程共享映射區;
結論:
父子進程共享:1. 打開的文件 2.mmap建立的映射區(但必須要使用MAP_SHARED)
流程:
父子進程使用 mmap 進程間通信:
1.父進程 先 創建映射區。 open( O_RDWR) mmap( MAP_SHARED );
2.指定 MAP_SHARED 權限
3.fork() 創建子進程。
4.一個進程讀, 另外一個進程寫。
練習
練習:父進程創建映射區,然后fork子進程,子進程修改映射區內容,而后,父進程讀取映射區內容,查驗是
否共享
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/wait.h>int var = 100;int main(void)
{int *p;pid_t pid;int fd = open("temp", O_RDWR);//p = (int *)mmap(NULL, 40, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0);p = (int *)mmap(NULL, 490, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);//p = (int *)mmap(NULL, 4, PROT_READ|PROT_WRITE, MAP_PRIVATE, fd, 0); 私有的不行if(p == MAP_FAILED){ //注意:不是p == NULLperror("mmap error");exit(1);}close(fd);pid = fork(); //創建子進程if(pid == 0){*p = 7000; // 寫共享內存var = 1000;printf("child, *p = %d, var = %d\n", *p, var);} else {sleep(1);printf("parent, *p = %d, var = %d\n", *p, var); // 讀共享內存wait(NULL);int ret = munmap(p, 4); //釋放映射區if (ret == -1) {perror("munmap error");exit(1);}}return 0;
}
無血緣關系
流程: 【要求會寫】
1.兩個進程 打開同一個文件,創建映射區。
2.指定flags 為 MAP_SHARED。
3.一個進程寫入,另外一個進程讀出。
【注意】:無血緣關系進程間通信。
? mmap:數據可以重復讀取。
? fifo:數據只能一次讀取。
讀端
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#include <errno.h>struct student {int id;char name[256];int age;
};void sys_err(const char *str)
{perror(str);exit(1);
}int main(int argc, char *argv[])
{struct student stu;struct student *p;int fd; fd = open("test_map", O_RDONLY);if (fd == -1)sys_err("open error");p = mmap(NULL, sizeof(stu), PROT_READ, MAP_SHARED, fd, 0);if (p == MAP_FAILED)sys_err("mmap error");close(fd);while (1) {printf("id= %d, name=%s, age=%d\n", p->id, p->name, p->age);usleep(10000);}munmap(p, sizeof(stu));return 0;
}
寫端
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#include <errno.h>struct student {int id;char name[256];int age;
};void sys_err(const char *str)
{perror(str);exit(1);
}int main(int argc, char *argv[])
{struct student stu = {1, "xiaoming", 18};struct student *p;int fd; // fd = open("test_map", O_RDWR|O_CREAT|O_TRUNC, 0664);fd = open("test_map", O_RDWR);if (fd == -1)sys_err("open error");ftruncate(fd, sizeof(stu));p = mmap(NULL, sizeof(stu), PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);if (p == MAP_FAILED)sys_err("mmap error");close(fd);while (1) {memcpy(p, &stu, sizeof(stu));stu.id++;sleep(2);}munmap(p, sizeof(stu));return 0;
}
5.mmap匿名映射區
匿名映射:只能用于 血緣關系(父子)進程間通信。
p = (int *)mmap(NULL, 40, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0);
映射區大小想要多少寫多少
權限想要啥寫啥
文件描述符的地方傳-1
flags要 | 下面提到的兩個宏
/dev/zero 從這個文件里面拿數據可以隨便拿,想要多大拿多大的數據,只不過讀出來都是文件空洞