存储映射区
存储映射区可在不使用read和write函数的情况下,使用地址(指针)完成I/O操作。
它的本质是将一个磁盘文件与存储空间中的一个缓冲区相映射。从缓冲区中取数据,就相当于读文件中的相应字节;将数据写入缓冲区,则会将数据写入文件。
使用存储映射这种方法,首先应通知内核,将一个指定文件映射到存储区域中。这个映射工作可以通过mmap函数来实现。
mmap函数
函数作用
建立存储映射区
函数原型
cpp
#include <sys/mman.h>
void* mmap(void* addr, size_t length, int prot, int flags, int fd, off_t offset);
函数返回值
- 成功:返回创建的映射区首地址;
- 失败:
MAP_FAILED
宏
函数参数
addr
:指定映射的起始地址,通常设为NULL
,由系统指定length
:映射到内存的文件长度prot
:映射区的保护方式,最常用的:- 读:
PROT_READ
- 写:
PROT_WRITE
- 读写:
PROT_READ | PROT_WRITE
- 读:
flags
:映射区的特性,可以是MAP_SHARED
:写入映射区的数据会写回文件,且允许其他映射该文件的进程共享。MAP_PRIVATE
:对映射区的写入操作会产生一个映射区的复制(copy-on-write),对此区域所做的修改不会写回原文件。
fd
:由open返回的文件描述符,代表要映射的文件。offset
:以文件开始处的偏移量,必须是4k的整数倍,通常为0,表示从文件头开始映射。
使用mmap函数建立匿名映射:
cpp
mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
mmap使用总结
- 第一个参数 写成
NULL
- 第二个参数 要映射的文件大小 > 0
- 第三个参数:
PROT_READ
、PROT_WRITE
- 第四个参数:
MAP_SHARED
或者MAP_PRIVATE
- 第五个参数:打开的文件对应的文件描述符
- 第六个参数:4k的整数倍
munmap函数
函数作用
释放由mmap函数建立的存储映射区
函数原型
cpp
#include <sys/mman.h>
int munmap(void* addr, size_t length);
函数返回值
- 成功:返回
0
- 失败:返回
-1
,设置errno值
函数参数
addr
:调用mmap函数成功返回的映射区首地址length
:映射区大小(mmap函数的第二个参数)
注意事项
- 创建映射区的过程中,隐含着一次对映射文件的读操作,将文件内容读取到映射区
- 当
MAP_SHARED
时,要求:映射区的权限应 <=文件打开的权限(出于对映射区的保护)。而MAP_PRIVATE
则无所谓,因为mmap中的权限是对内存的限制。 - 映射区的释放与文件关闭无关,只要映射建立成功,文件可以立即关闭。
- 特别注意,当映射文件大小为0时,不能创建映射区。所以,用于映射的文件必须要有实际大小;mmap使用时常常会出现总线错误,通常是由于共享文件存储空间大小引起的。
- munmap传入的地址一定是mmap的返回地址。坚决杜绝
指针++
操作。 - 文件偏移量必须为0或者4K的整数倍
- mmap创建映射区出错概率非常高,一定要检查返回值,确保映射区建立成功再进行后续操作。
相关思考题
- 可以open的时候
O_CREAT
一个新文件来创建映射区吗? - 如果open时
O_RDONLY
,mmap时PROT参数指定PROT_READ|PROT_WRITE
会怎样? - mmap映射完成之后,文件描述符关闭,对mmap映射有没有影响?
- 如果文件偏移量为1000会怎样?
- 对mem越界操作会怎样?
- 如果
mem++
,munmap可否成功? - mmap什么情况下会调用失败?
- 如果不检测mmap的返回值,会怎样?
练习
对文件的读写操作
先试用 shell 命令创建file1
,然后向file1
中写入hello
五个字符;然后编写程序对 file1
文件建立 5 个字节的内存映射区,并读取映射区的内容,之后将其改为world
五个字符。程序运行结束使用 cat 命令验证。
cpp
#include <fcntl.h>
#include <stdio.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <unistd.h>
void argsCheck(int argc, const char *filename);
int main(int argc, char *argv[]) {
argsCheck(argc, argv[1]);
int fd = open(argv[1], O_RDWR);
char *p = (char *) mmap(NULL, 5, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
puts("内存映射区的内容为:");
for (int i = 0; i < 5; ++i) {
printf("%c", *(p + i));
}
puts("");
for (int i = 0; i < 5; ++i) {
sprintf(p, "world");
}
munmap(p, 5);
close(fd);
return 0;
}
void argsCheck(int argc, const char *filename) {
if (argc != 2) {
printf("args error.\n");
_exit(-1);
}
struct stat fileStat;
int ret = stat(filename, &fileStat);
if (ret == -1) {
perror("stat");
_exit(-1);
}
if (fileStat.st_size == 0) {
printf("If the mapping file size is 0, the mapping area cannot be created.\n");
_exit(-1);
}
}
父子进程间通信
图解说明
思路
- 调用mmap函数创建存储映射区,返回映射区首地址ptr
- 调用fork函数创建子进程,子进程也拥有了映射区首地址
- 父子进程可以通过映射区首地址指针ptr完成通信
- 调用munmap函数释放存储映射区
cpp
#include <fcntl.h>
#include <stdio.h>
#include <sys/mman.h>
#include <sys/wait.h>
#include <unistd.h>
int main(int argc, char *argv[]) {
int fd = open("file1", O_RDWR);
if (fd == -1) {
perror("open");
return -1;
}
void *p = mmap(NULL, 4096, PROT_WRITE | PROT_READ, MAP_SHARED, fd, 0);
if (fork() == 0) {
printf("%s\n", (char *) p);
} else {
sprintf(p, "Son, can you see my message?");
wait(NULL);
munmap(p, 4096);
close(fd);
}
return 0;
}
程序输出:
shell
$ ./mmap1
Son, can you see my message?
没有血缘关系的进程间通信
思路
- 两个进程都打开相同的文件,然后调用mmap函数建立存储映射区,这样两个进程共享同一个存储映射区。
mmap2_.c
cpp
#include <fcntl.h>
#include <stdio.h>
#include <sys/mman.h>
#include <unistd.h>
int main(int argc, char *argv[]) {
int fd = open("file2", O_RDWR);
if (fd == -1) {
perror("open");
return -1;
}
char *p = mmap(NULL, 4096, PROT_WRITE | PROT_READ, MAP_SHARED, fd, 0);
printf("%s", p);
munmap(p, 4096);
close(fd);
return 0;
}
mmap2.c
cpp
#include <fcntl.h>
#include <stdio.h>
#include <sys/mman.h>
#include <unistd.h>
int main(int argc, char *argv[]) {
int fd = open("file2", O_RDWR);
if (fd == -1) {
perror("open");
return -1;
}
char *p = mmap(NULL, 4096, PROT_WRITE | PROT_READ, MAP_SHARED, fd, 0);
sprintf(p, "Hello process, can you see my message?\n");
munmap(p, 4096);
close(fd);
execl("mmap2_", "./mmap2_", NULL);
return 0;
}
程序输出:
shell
$ gcc mmap2_.c -o mmap2_
$ gcc mmap2.c -o mmap2
$ ./mmap2
Hello process, can you see my message?