前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >linux mmap

linux mmap

原创
作者头像
腹黑熊
修改2022-05-21 02:15:21
2.2K0
修改2022-05-21 02:15:21
举报
文章被收录于专栏:大熊爱学习大熊爱学习

mmap是linux中提高文件读写效率的一种手段,这里简单整理一下mmap的原理和使用。

高速页缓存

在介绍文件读写之前需要先了解下页缓存的机制,有助于理解文件读写的底层实现。

在linux文件读写中,内核会将文件内容缓存到物理内存中,以页为单位进行映射。用户针对文件的读写都是直接与页缓存打交道的,如果访问的内容在页缓存中则直接访问,如果不在则由内核去将文件内容读入缓存后再继续。对内容的修改直接作用于缓存,由内核周期性的将修改写回磁盘,也可以通过调用fsync()强制写回。


传统文件读写

传统的文件读写代码如下:

代码语言:c
复制
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内存映射机制可以将文件的页缓存直接映射到用户空间进行读写,读写过程就和操作用户空间的内存一样,完美的避开了系统调用的上下文切换和数据拷贝。

mmap函数原型如下:

代码语言:txt
复制
#include <sys/mman.h>
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);

参数介绍:

  • addr:若addr不为零,则将内存映射到该地址上。若addr为NULL,则表示由内核决定映射到哪块地址,并通过返回值返回。推荐使用第二种方案,代码可移植性更好。
  • length:映射内存的大小,实际映射内存是刚好大于等于length的page size的整数倍。
  • prot:标志位,用来映射内存的访问权限,分为可读(PROT_EXEC)、可写(PROT_WRITE)、可执行(PROT_EXEC)或不可访问(PROT_NONE)。该标志为可以是PROT_NONE或其他一个或多个权限的或,该权限不能与打开文件时的权限冲突。
  • flags:标志位,用来决定了缓存的行为策略。主要使用的标志位就是MAP_PRIVATE和MAP_SHARED。若使用MAP_PRIVATE,则表示每个调用mmap的进程独有一块缓存(写时复制),对该内存的更新不会写入文件。若使用MAP_SHARED,则表示所有相关进程共享一份缓存(可用作共享内存),同时对缓存的更新会周期性的写入文件。
  • fd:打开的文件描述符,mmap函数返回后可以直接把文件关闭,不会影响内存映射。
  • offset:映射内存相对文件起始位置的偏移,offset需要满足page size的整数倍,不然会报EINVAL(Invalid argument)错误。

简单代码示例:

代码语言:c
复制
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的访问边界

  • 文件的可映射内存边界:文件的可映射内存边界是刚好大于等于文件大小的page size的整数倍,超出文件大小却未超出内存边界的话是可以访问的,但是数据不会写入文件。如果mmap超出了这个内存边界,在访问边界外的数据时会报bus error,导致程序终止。
  • length的访问边界:mmap实际映射的内存是刚好大于等于length的page size的整数倍,超出length但是未超出映射内存部分的访问和修改都是正常的。但是超出实际映射内存边界外的访问会报segmentation fault,使程序终止。

所以最优的情况是文件的大小和length相同且都是page size的整数倍。

拓展知识

mmap除了可以用作普通文件的内存映射外,还可以创建匿名文件内存映射,即不依赖磁盘文件的内存映射。本质上是将初始化为0的多个内存页映射到用户空间,一般被用作大内存的动态分配。具体的实现需要借助memfd_create函数或MAP_ANONYMOUS标志位。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 高速页缓存
  • 传统文件读写
  • mmap内存映射
    • 拓展知识
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
    http://www.vxiaotou.com