mmap(memory-mapped file)

使用示例

#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

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

传统读写文件

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

请添加图片描述

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 将文件映射到进程的虚拟内存空间中,通过对这段内存的 lord (读取)和 store (写入),实现对文件的读取和修改,不使用 read write,对内存的操作效率高

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开销很大
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Zevin~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值