mmap

文章目录

使用示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>

int main(int argc, char* argv[])
{
int fd;
void *start;
struct stat sb;

fd = open("text.txt", O_RDONLY|O_CREAT); // 打开文件text.txt
printf("fd=%d\n",fd);
fstat(fd, &sb); // 获取文件状态
start = mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0); // 建立内存映射
if(start == MAP_FAILED){
return (-1);
}
strcpy((char*)start,"asd");
printf("%s\n", (char*)start); // 输出内存内容
munmap(start, sb.st_size); // 解除内存映射
close(fd); // 关闭文件

return 0;
}

请添加图片描述

这段代码实现将文件text.txt 打开,并用mmap函数将文件映射到虚拟内存中,通过执政start对文件进行读写,可以在中断中看到由文件写入的数据,程序结束后,可以查看text.txt文件,来查看写入的数据

函数原型

mmap

void *mmap(void *addr, size_t len, int prot, int flags, int fd, off_t offset);

  • addr :制定映射的起始地址,通常是NULL,由内核来分配,是一个虚拟地址
  • len:代表将文件中映射到内存的部分的长度,以及内存地址的区间大小
  • prot:映射区域(内存)的保护方式,这块地址的方式
    • PROT_EXEC:映射区域可执行,X
    • PROT_READ: 映射区域可读取,R
    • PROT_NONE: 映射区域不能存取,
    • PROT_WRITE: 映射区域可以写入 ,W
  • flag:映射区的特性标志位
    • MAP_SHARD:写入映射区的数据会复制回文件,和其他映射文件的进程共享,多个进程可以共享,实现 进程间通信
    • MAP_PRIVATE:对映射区的写入操作会产生一个映射区的复制,对此区域的修改不会写会原文件,这一部分的内容只会出现的内存,而不对文件修改,
  • fd:要映射到内存中的文件描述符,有open函数打开文件时返回的值,内核可以通过他得到对应的 struct file
  • offset:文件映射的偏移量,通常设置为0,代表从文件最前方开始对应,offset必须是分页大小(4K)的整数倍。offset移动,相当与从文件的不同位置进行映射

函数的返回值

实际分配的内存的起始地址,我们可以使用这个地址,来对文件进行修改,读取

munmap

int munmap( void * addr, size_t len )

该调用在进程地址空间中解除一个映射关系,来表明应用程序完成了对文件的操作,addr是mmap时返回的地址,len是映射区的长度

如果这个len就是内存中对应的映射区地址,这一块就直接释放掉了,如果不是,就把addr+len这一部分给解除映射,我们使用的addr移动len

解除映射之后,对原来映射地址的访问会导致段错误

传统读写文件

  • 把文件内容读入到内存中,从内核态拷贝回用户态,获得对应文件的数据。
  • 用户态修改文件相应的内容。
  • 把修改过的数据从用户态拷贝回内核态文件中。

请添加图片描述

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

其中,(页缓存) page cache类似inode cache,把磁盘中的数据缓存在内存中,减少和磁盘进行交互,提高效率,内核使用page cache 将文件的数据块关联起来,所以我们在读写文件的时候,实际上操作的是 page cache

最大的影响就是,读写都需要进行数据的拷贝,如果数据两很大,那对性能影响就很大

mmap 原理

请添加图片描述

请添加图片描述

与传统读写文件相比,mmap就是可以直接在用户空间读写 page cache,这样就可以免去将 page cache的数据在内核与用户之间的拷贝,mmap映射的正是文件的 page cache,而非磁盘

mmap 将文件映射到进程的虚拟内存空间中,通过对这段内存的 lordstore ,实现对文件的读取和修改,不使用 readwrite

off为映射的部分在文件中的偏移量,len为映射的长度

图中实际含义

从文件描述符对应的offset开始映射长度为len的内容到虚拟地址va(由内核决定),va+len,范围内都是其对应的虚拟地址

eager实现

如果内存使用的是eager方式来实现

对于文件的读写,内核会从文件的offset开始,将数据拷贝到内核中,设置好PTE指向物理内存的位置,后程序就可以使用load或者store来修改内存中文件的内容,完成后,使用munmap,将dirty block写回文件中,我们可以很容易找到哪个block是dirty,因为对应的PTE_D被设置了

lazy实现

但是现在的计算机都不会这样做,都是以 lazy的方式实现

  • 记录这个PTE属于这个文件描述符
  • 存储相应的信息在VMA(Virtual Memory Area))结构体中(这些信息来表示对应的虚拟地址的实际内容在哪里)
    • 文件描述符
    • 偏移量等
    • 地址范围
    • 标志位
    • 长度

调用mmap是不会开辟物理地址的,只会把数据存储起来,等待后续实际的调用,再实际的对对应的page进行开辟物理内存

  • 对VMA记录的某个范围内进行读写操作,触发page fault,就会实际的开辟物理页,将该va和该物理地址进行映射,将VMA中记录的offset标志位开始读取数据到对应的物理地址中

如果其他进程直接修改了文件的内容,内容不会出现在内存中,

mmap并不会主动将 mmap修改的page cache 同步到磁盘,而是需要用户进行触发

  • munmap解除文件映射的时候会触发
  • msync函数主动进行数据同步
  • 进程退出
  • 系统关机

请添加图片描述

  1. 虚拟地址空间获得一段连续的地址
  2. 在没有读写的时候,这个地址指向不存在的地方(所以上图中,起始地址和终止地址还没分配给进程)
  3. 根据偏移量,进程要读取文件了,数据占两个页
  4. 进程开始使用内存,所以OS要给这两个页分配内存,触发page fault
  5. 将对应的offset文件数据拷贝到物理内存对应的page上

缺点

  1. 如果文件很小,小于4KB,但是再内存中都是按照4KB为基本单位,就会造成一个内存空间的浪费
  2. 创建mmap,销毁munmap,page fault开销很大

mmap
http://example.com/2022/12/14/mmap/
作者
Zevin
发布于
2022年12月14日
许可协议