mmap应用层+内核 一文全总结

目录

1. 前文

2. 原理

3. 应用层

4. 内核


1. 前文

X86 CR3寄存器:

        作用:用于存储页全局目录表(PGD)的物理地址。

        进程切换时会将CR3更新为新进程的全局页目录表物理地址。其他体系架构一样。

        所以每个进程的同一虚拟地址,经查询页表后得到不同物理地址。

PTE:多级页表的最后一级,存储最终虚拟地址对应的物理地址。

2. 原理

mmap含义:

        将一个文件或其他对象(驱动中某设备物理地址)映射到进程虚拟地址空间。进程直接操作虚拟内存地址,实现文件或其他对象的读写。

mmap映射分类:

        1. 文件映射和匿名映射:

               1.1 文件映射:将文件物理地址映射到进程虚拟地址空间。对应内存页为文件页。

               1.2 匿名映射:没有对应数据源(如文件),就单纯映射一块内存物理地址到进程虚拟地址空间。对应内存页为匿名页。

        2.  共享映射和私有映射:

               2.1 共享映射:修改映射区域时,其他进程可见,如果是文件共享映射,会修改磁盘文件。

               2.2 私有映射:修改数据时复制副本,并修改副本,其他进程看不见,不影响数据源。

私有文件映射页表PTE为只读,写文件时会发生缺页中断,中断处理函数会COW写时复制。

共享文件映射页表PTE为可写,写文件时不会缺页中断,而是直接写入页缓存,最终回写到磁盘。

mmap原理:

        1. 共享文件映射过程不涉及任何物理内存分配,只分配一段虚拟内存(vm_area_struct),新建共享映射时,文件没有对应页缓存。

        2. 在各进程页表中,该映射的虚拟内存页表项PTE为空,读写该虚拟内存时,MMU产生缺页中断, 内核执行缺页中断处理程序。

        3. 中断处理程序通过vm_area_struct->vm_pgoff在页缓存中查找文件。如果文件不在页缓存中,则分配内存页,并加入到页缓存中。

        4. 最后调用readpage,来读取磁盘文件到新分配的内存页,最后填写页表项PTE。

mmap优点:

        优点:零拷贝,按需加载,共享内存,适用于大文件。

mmap使用场景:

        malloc分配大内存:使用mmap匿名映射。

        文件IO:不使用read、write、页缓存。

        进程间通信:匿名映射或文件映射。

        驱动设备:将驱动buffer物理地址,映射到进程虚拟地址空间。

                fd = open("/dev/mtdblock1",);

                mmap映射该fd,最终调用mtdblock驱动struct file_operations中mmap。

        零拷贝网络收发包:将文件映射到内存,直接从内存收发数据,无需用户空间和内核空间的多次拷贝。

open函数O_DIRECT标志:文件I/O数据的传送绕过页高速缓存,直接立刻回写到磁盘文件。

如下图:mmap映射区域在进程虚拟地址空间的堆和栈之间:

3. 应用层

应用层mmap函数原型:

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

        addr:建立映射区的虚拟地址首地址,通常为NULL,即由Linux内核指定地址。

        length:映射区长度。

        prot:期望的映射区权限,不能与文件fd打开模式冲突。

                PROT_READ:页内容可读

                PROT_WRITE:页内容可写

                PROT_EXEC:页内容可执行

                PROT_NONE:页内容不访问

        flags:指定映射类型。

                MAP_PRIVATE:进程私有映射,修改映射时创建副本,不影响磁盘文件和其他进程。

                MAP_SHARED:共享映射,修改映射时会回写到磁盘文件,可实现进程间通信。

                MAP_ANONYMOUS:匿名映射。

        fd:文件描述符。若匿名映射,则fd为-1。

总结:匿名映射通常是私有映射。共享匿名映射只能在父子进程共享。

那为什么共享匿名映射只能在父子进程共享?

        因为匿名映射,选定的映射源物理内存地址是内核随机找的,其他进程在调用mmap无法找到这个映射源物理内存地址。而子进程fork时使用了CLONE_VM,父子进程都可通过同一虚拟地址(mmap返回的虚拟地址),找到相同物理内存地址。

4. 内核

常规文件读写过程:

       1. 进程发起读文件请求。

       2. 内核fd->struct file -> inode

       3. 在inode的 struct address_space *i_mapping; 或struct file 的struct address_space *f_mapping;查找请求文件页是否在页缓存中。

                如果存在,直接返回该文件页内容。

                如果不存在,通过inode-> i_sb定位到文件磁盘物理地址,将文件复制到页缓存。

struct file {        //表示一个打开文件

        struct address_space    *f_mapping;        包含该文件的页缓存信息。

}

struct inode {        //表示磁盘上一个文件的元信息

        struct address_space    *i_mapping;        包含该文件的页缓存信息。

}

struct address_space {

        struct radix_tree_root    page_tree;        基数树,连接该文件所有占用的内存页(页缓存)。

}

如何判断一个文件指定偏移是否在页缓存中?

       struct page *find_get_page(struct address_space *mapping, pgoff_t offset)
                调用radix_tree_lookup(), 搜索拥有指定偏移量的基数树的叶子节点。

        使用举例:page =   find_get_page(inode->i_mapping, offset);

     

mmap映射过程:

        1.1 在进程虚拟空间寻找空闲区域。

        1.2 为此虚拟区域区分配并初始化vm_area_struct。

        2.1 通过文件描述符fd,找到struct file,再调用struct file_operations中mmap,默认mmap为generic_file_mmap(struct file *filp, struct vm_area_struct *vma)。

        2.2 通过inode->i_sb 超级块,得到文件块映射表,最后得到文件物理地址。

        2.3 调用install_file_pte创建页表,此时页表没有对应物理地址。

        ps:前两个阶段仅创建映射,没有拷贝文件到内存。

        3.1 进程读写该映射虚拟地址,通过查询页表,页表没有对应物理地址,引发缺页异常。

        3.2 缺页异常中断处理程序中,发起调页请求。

        3.3 调页过程先在swap cache中寻找内存页,若失败则调用nopage函数把文件拷贝到内存

        3.4 之后进程对该内存读写,延时回写脏页面到物理地址,完成读写文件。

  • 23
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

山下小童

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

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

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

打赏作者

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

抵扣说明:

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

余额充值