1. 什么是存储映射IO
存储映射I/O (Memory-mapped I/O) 使一个磁盘文件与存储空间(内存)中的一个缓冲区相映射。这样的话,当从缓冲区中取数据,就相当于读文件中的相应的字节,而将数据存入缓冲区,则相应的字节就自动写入文件。这样,就可在不使用read和write函数的情况下,使用地址(指针)完成I/O操作,当然也可以使用内存操作函数strcpy,memcpy等。
使用这种方法,首先应通知内核,将一个指定文件映射到存储区域中。这个映射工作可以通过mmap函数来实现。
2. mmap函数介绍
2.1 mmap函数创建映射区
- 包含头文件
#include <sys/mman.h>
- 函数原型
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset); void *mmap64(void *addr, size_t length, int prot, int flags, int fd, off64_t offset); int munmap(void *addr, size_t length);
- 函数功能
mmap() creates a new mapping in the virtual address space of the calling process. The starting address for the new mapping is specified in addr. The length argument specifies the length of the mapping. - 函数参数
- addr:建立映射区的首地址,由Linux内核指定,使用时直接传递NULL 。
- length:想要创建的映射区的大小 。
- prot:映射区权限
- PROT_READ:可读
- PROT_WRITE:可写(PROT_READ | PROT_WRITE 读写)
- PROT_EXEC:可执行
- PROT_NONE:不可用
- flags:标志位参数,常用于设定更新物理区域、设置共享、创建匿名映射区
- MAP_SHARED:共享的,会将映射区所做的操作反映到物理设备(磁盘)上。也就是说,对内存的修改会影响到源文件。
- MAP_PRIVATE:私有的,映射区所做的修改不会反映到物理设备(磁盘)。
- fd:用来建立映射区的文件描述符,映射区是从文件映射来的,所以创建映射区肯定要打开一个文件。
- offset:偏移量,映射文件的偏移(4k的整数倍)。
- 函数返回值
- On success, mmap() returns a pointer to the mapped area. 成功返回创建的映射区首地址。
- On error, the value MAP_FAILED (that is, (void *) -1) is returned, and errno is set appropriately. 失败返回一个宏MAP_FAILED,并设置errno。
2.2 munmap函数释放映射区
- 包含头文件
#include <sys/mman.h>
- 函数原型
int munmap(void *addr, size_t length);
- 函数功能
释放mmap创建的映射区(可以联想malloc/free, new/delete等内存管理函数对)。 - 函数参数
- addr:mmap的返回值(由mmap返回的映射区首地址)
- length:mmap创建的映射区的长度
- 函数返回值
- On success, munmap() returns 0.
- On failure returns -1, and errno is set (probably to EINVAL).
3. mmap函数用法示例及注意事项
/************************************************************ >File Name : mmap_test.c >Author : Mindtechnist >Company : Mindtechnist >Create Time: 2022年05月22日 星期日 15时52分45秒 ************************************************************/ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <sys/mman.h> int main(int argc, char* argv[]) { int fd = open("mem.txt", O_RDWR); /*int fd = open("mem.txt", O_RDWR | O_CREAT | O_TRUNC, 0644); 创建文件并截断 ftruncate(fd, 8); 新创建文件必须进行扩展,大小不能为0*/ char* mem = mmap(NULL, 8, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); /*char* mem = mmap(NULL, 8, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);*/ if(mem == MAP_FAILED) { perror("mmap err"); return -1; } /*我们操作的是内存,但是这种操作会被同步到文件*/ strcpy(mem, "hello"); /*写入字符串,所以mmap()返回char*类型*/ int mret = munmap(mem, 8); if(mret < 0) { perror("munmap err"); } close(fd); return 0; }
mmap函数使用时的注意事项:
- 使用mmap()函数创建缓冲区返回的地址mem是多少,munmap()释放的时候传入的参数mem就是多少,不能更改,也就是说,要把mmap()返回的地址原模原样传进munmap()才能正常释放。(munmap传入的地址一定是mmap的返回地址,不能对地址指针进行++等位移操作。)
- mem.txt文件的大小对映射区操作也是有影响的,如果我们设置的映射缓冲区为len,而实际上的mem.txt文件大小大于len,假如说我们写入映射区的内容大于len,只要不超过文件大小,也是可以写进去的。如果写入的内容大于文件的长度,那么超出文件长度的部分就被截断。
- 文件偏移量必须为4K的整数倍。mmap()函数中,最后一个参数偏移量必须是4K的整数倍,这是操作系统的限制,文件最小门槛就是8个512,也就是4096。如果一个文件本身大小没有达到4K,那么它占用的空间大小也是4096。比如下面mem.txt实际上只有10字节大小,但是占据了8个512,这就证明文件最小占据4K空间。
- 映射区的释放与文件关闭无关,只要映射建立成功,文件可以立即关闭。这是因为,只要使用mmap()函数映射完缓冲区,那么这个映射区和文件之间的通道已经建立,即使提前关闭文件描述符,也不影响对映射区和文件的操作。
- 如果文件mem.txt的为0,也就是空文件,那么会报错“总线错误(核心已转储)”,所以映射时使用的文件大小不能是0(当映射文件大小为0时,不能创建映射区,用于映射的文件必须要有实际大小。mmap使用时常常会出现总线错误,通常是由于共享文件存储空间大小引起的)。
- 创建映射区的过程中,隐含着一次对映射文件的读操作,所以open打开文件时,必须要有读权限。
- 当MAP_SHARED时,要求:映射区的权限应小于等于文件打开的权限(出于对映射区的保护,因为映射区的操作会影响文件)。而MAP_PRIVATE则无所谓,因为mmap中的权限是对内存的限制。
- mmap创建映射区时出错概率非常高,一定要检查返回值,确保映射区建立成功再进行后续操作。