mmap简介
mmap是零拷贝技术的一种实现
使用系统调用读写文件
修改一个文件的内容需要三个步骤:
1.把文件内容(File)读入内存中(page cache)
2.修改内存中的内容(在用户空间)
3.把修改后用户空间的数据写入page cache,再同步到File中
使用如下代码实现上面过程:
read(fd,buf,1024); // 把文件内容读取到buf
... // 修改buf的内容
write(fd,buf,1024); // 把buf的内容写入到文件
从图中可以看出,页缓存(page cache)是读写文件时的中间件,内核使用页缓存与文件的数据块关联起来,所以应用程序读写文件时,实际操作的是页缓存。
使用mmap读写文件
在传统读写文件的过程中,如果可以直接在用户空间读写页缓存,就可以免去页缓存数据复制到用户空间缓冲区的过程,这样就减少了拷贝次数,提高了效率
使用mmap系统调用可以将用户空间的虚拟内存地址和文件进行映射(绑定),对映射后的虚拟内存地址进行读写操作就如同对文件进行读写操作一样。
读写文件都要经过页缓存,所以mmap映射的就是文件的页缓存,而非磁盘中的文件本身。由于mmap映射的是文件的页缓存,所以就涉及到同步的问题。
Linux内核不会主动把mmap映射的页缓存同步到磁盘,而是需要用户主动触发。触发mmap映射的内存到磁盘有4个时机:
- 调用msync函数主动进行数据同步(主动)
- 调用munmap函数对文件进行解除映射关系时(主动)
- 进程退出时(被动)
- 系统关机时 (被动)
mmap使用方法
函数原型
void *mmap(void *addr,size_t length.int port,int flags,int fd,off_t offset);
参数说明
- addr:指定的虚拟内存空间,可以设置为NULL由Linux内核自动选择合适的虚拟内存地址
- length:映射的长度
- prot:映射内存的保护模式,可选值:
- PROT_EXEC:可以被执行。
- PROT_READ: 可以被读取
- PROT_WRITE:可以被写入
- PROT_NONE:不可访问
- flags:指定映射的类型,常用的可选值如下:
- MAP_FIXED:使用指定的起始虚拟内存地址进行映射。
- MAP_SHARED:与其他所有映射到这个文件的进程共享映射空间(可实现共享内存)
- MAP_PRIVATE:家里一个写时复制(Copy on Write)的私有映射空间。
- MAP_LOCKED:锁定映射区的页面,从而防止页面被交换出内存
- fd:进行映射的文件句柄
- offset:文件偏移量(从文件的何处开始映射)。
使用方法
int fd = open(filepath,O_RDWR,0644); // 打开文件
void *addr = mmap(NULL,8192,PROT_WRITE,MAP_SHARED,fd,4096); // 对文件进行映射
上面的例子,先通过open函数以可读写的方式打开文件,然后通过mmap函数对文件进行映射,映射的方式如下:
- addr 参数设置为 NULL,表示让操作系统自动选择合适的虚拟内存地址进行映射。
- length 参数设置为 8192 表示映射的区域为 2 个内存页的大小(一个内存页的大小为 4 KB)。
- prot 参数设置为 PROT_WRITE 表示映射的内存区为可读写。
- flags 参数设置为 MAP_SHARED 表示共享映射区。
- fd 参数设置打开的文件句柄。
- offset 参数设置为 4096 表示从文件的 4096 处开始映射。
mmap函数会返回映射后的内存地址,可以通过此内存地址对文件进行读写操作
总结
使用 mmap 对文件进行读写操作时可以减少内存拷贝的次数,并且可以减少系统调用的次数,从而提高对读写文件操作的效率。
由于内核不会主动同步 mmap 所映射的内存区中的数据,所以在某些特殊的场景下可能会出现数据丢失的情况(如断电)。为了避免数据丢失,在使用 mmap 的时候可以在适当时主动调用 msync 函数来同步映射内存区的数据。
mmap是直接将进程的虚拟地址和内核空间的page cache进行映射从而省去了从内核空间向用户空间的copy
转载文章: