深入学习 mmap和munmap 原理

发布于 18 天前  166 次阅读


序言

最近在看《Linux高性能服务器编程》过程中,在高级I/O函数章节发现了mmap和munmap函数的原理和用法,感觉对于个人理解零拷贝以及共享内存有着更深一步的理解,因此想记录下来,供大家一起学习学习。

1. 传统的读写文件

一般来说,修改一个文件的内容需要如下3个步骤:

  • 把文件内容读入到内存中。
  • 修改内存中的内容。
  • 把内存的数据写入到文件中。

过程如图 1 所示:

如果使用代码来实现上面的过程,代码如下:

read(fd, buf, 1024);  // 读取文件的内容到buf
...                   // 修改buf的内容
write(fd, buf, 1024); // 把buf的内容写入到文件

从图 1 中可以看出,页缓存(page cache) 是读写文件时的中间层,内核使用 页缓存 与文件的数据块关联起来。所以应用程序读写文件时,实际操作的是 页缓存

2. 使用 mmap 读写文件

从传统读写文件的过程中,我们可以发现有个地方可以优化:如果可以直接在用户空间读写 页缓存,那么就可以免去将 页缓存 的数据复制到用户空间缓冲区的过程。

那么,有没有这样的技术能实现上面所说的方式呢?答案是肯定的,就是 mmap

使用 mmap 系统调用可以将用户空间的虚拟内存地址与文件进行映射(绑定),对映射后的虚拟内存地址进行读写操作就如同对文件进行读写操作一样。原理如图 2 所示:

前面我们介绍过,读写文件都需要经过 页缓存,所以 mmap 映射的正是文件的 页缓存,而非磁盘中的文件本身。由于 mmap 映射的是文件的 页缓存,所以就涉及到同步的问题,即 页缓存 会在什么时候把数据同步到磁盘。

Linux 内核并不会主动把 mmap 映射的 页缓存 同步到磁盘,而是需要用户主动触发。同步 mmap 映射的内存到磁盘有 4 个时机:

  • 调用 msync 函数主动进行数据同步(主动)。
  • 调用 munmap 函数对文件进行解除映射关系时(主动)。
  • 进程退出时(被动)。
  • 系统关机时(被动)。

3.mmap的使用方式

3.1 mmap函数

void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);
  • start:指定映射的虚拟内存地址,可以设置为 NULL,让 Linux 内核自动选择合适的虚拟内存地址。
  • length:映射的长度。
  • prot:映射内存的保护模式,可选值如下:
    • PROT_EXEC:可以被执行。
    1. PROT_READ:可以被读取。
    2. PROT_WRITE:可以被写入。
    3. PROT_NONE:不可访问。
  • flags:指定映射的类型,常用的可选值如下:
    • MAP_FIXED:使用指定的起始虚拟内存地址进行映射。
    1. MAP_SHARED:与其它所有映射到这个文件的进程共享映射空间(可实现共享内存)。
    2. MAP_PRIVATE:建立一个写时复制(Copy on Write)的私有映射空间。
    3. MAP_LOCKED:锁定映射区的页面,从而防止页面被交换出内存。
  • fd:进行映射的文件句柄。
  • offset:文件偏移量(从文件的何处开始映射)。

3.2 mmap的使用

int fd = open(filepath, O_RDWR, 0644);                           // 打开文件
void *start = mmap(NULL, 8192, PROT_WRITE, MAP_SHARED, fd, 4096); // 对文件进行映射

在上面例子中,我们先通过 open 函数以可读写的方式打开文件,然后通过 mmap 函数对文件进行映射,映射的方式如下:

  • start 参数设置为 NULL,表示让操作系统自动选择合适的虚拟内存地址进行映射。
  • length 参数设置为 8192 表示映射的区域为 2 个内存页的大小(一个内存页的大小为 4 KB)。
  • prot 参数设置为 PROT_WRITE 表示映射的内存区为可读写。
  • flags 参数设置为 MAP_SHARED 表示共享映射区。
  • fd 参数设置打开的文件句柄。
  • offset 参数设置为 4096 表示从文件的 4096 处开始映射。

mmap 函数会返回映射后的内存地址,我们可以通过此内存地址对文件进行读写操作。我们通过图 3 展示上面例子在内核中的结构:

4.munmap的使用方式

4.1 munmap函数

4.2munmap的使用

int munmap(void *start,size tlength );
  • start:指定映射的虚拟内存地址,由mmap()方法返回的起始虚拟内存地址。
  • length:映射的长度。

4.2 mmap的使用

int fd = open(filepath, O_RDWR, 0644);                           // 打开文件
void *start = mmap(NULL, 8192, PROT_WRITE, MAP_SHARED, fd, 4096); // 对文件进行映射
int errNo = munmap(start,8192 ); // 解除映射,并返回错误码

在上面例子中,我们先通过 open 函数以可读写的方式打开文件,然后通过 mmap 函数对文件进行映射,映射的方式如下:

5.总结

从对mmap和munmap函数的理解看,我们可以通过方法参数以及返回的结果去加深对虚拟内存映射的理解,这对于我们之后学习零拷贝和共享内存其实是有助益的。

6.参考文档

  1. https://juejin.cn/post/6961303425715798023?searchId=202405270031451D4519D65847FA0C20B2

繁华落尽,雪花漫天飞舞。