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 == NULL
perror("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 从这个文件里面拿数据可以随便拿,想要多大拿多大的数据,只不过读出来都是文件空洞