mmap是linux中提高文件读写效率的一种手段,这里简单整理一下mmap的原理和使用。
在介绍文件读写之前需要先了解下页缓存的机制,有助于理解文件读写的底层实现。
在linux文件读写中,内核会将文件内容缓存到物理内存中,以页为单位进行映射。用户针对文件的读写都是直接与页缓存打交道的,如果访问的内容在页缓存中则直接访问,如果不在则由内核去将文件内容读入缓存后再继续。对内容的修改直接作用于缓存,由内核周期性的将修改写回磁盘,也可以通过调用fsync()强制写回。
传统的文件读写代码如下:
char buf[1024];
int fd = open("filename", O_CREAT|O_RDWR, 0666);
read(fd, buf, 1024);
// do something to buf
write(fd, buf, 1024);
在上面的代码中,文件的打开只是一次性的,主要的操作就是文件的读写,文件读写的效率决定了程序运行的效率。看一下在文件的读写过程中操作系统都做了哪些操作:
可以看到在一次文件读写的过程中,发生了两次系统调用(四次内核态与用户态的上下文切换)和两次数据拷贝,频繁的上下文切换和数据拷贝会严重降低数据读写的效率。
那么问题来了,我们可不可以越过上图中的红线,直接去操作页缓存呢?答案是可以,这就要用到后面要介绍的mmap机制。
mmap内存映射机制可以将文件的页缓存直接映射到用户空间进行读写,读写过程就和操作用户空间的内存一样,完美的避开了系统调用的上下文切换和数据拷贝。
mmap函数原型如下:
#include <sys/mman.h>
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
参数介绍:
简单代码示例:
int size = 4096;
int fd = open("filename", O_CREAT|O_RDWR, 0666);
ftruncate(fd, size);
char* p = (char*)mmap(NULL, size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
// do something to p
一般在调用mmap时,还没有对文件进行读写访问,此时是没有对文件进行缓存的。当用户实际访问数据时,如果数据没有被缓存,就会触发页错误(page fault),然后由内核分配内存用作页缓存并填充数据。可以通过MAP_POPULATE标志位来强制mmap做预读(read-ahead),提前分配好缓存,有助于减少后面访问数据时页错误导致的阻塞。
在对磁盘文件做内存映射的方式中,文件读写实际上是跟缓存打交道,数据的变化并不会实时的反馈到磁盘中的文件,调用msync()可以强制将修改的数据写回磁盘。
在mmap中,无论是文件缓存还是内存映射都是以页为单位的。实际访问内存时要注意两个边界,文件的可映射内存边界和length的访问边界:
所以最优的情况是文件的大小和length相同且都是page size的整数倍。
mmap除了可以用作普通文件的内存映射外,还可以创建匿名文件内存映射,即不依赖磁盘文件的内存映射。本质上是将初始化为0的多个内存页映射到用户空间,一般被用作大内存的动态分配。具体的实现需要借助memfd_create函数或MAP_ANONYMOUS标志位。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。