Linux黑科技|mmap实现详解 |
您所在的位置:网站首页 › linux系统物理地址 › Linux黑科技|mmap实现详解 |
故事的开始是这样的,某天在脉脉上看到有人发了下面的帖子:
想不到 都成了黑科技了,为了让大家都能了解这个黑科技,所以还是写篇文章来详细介绍一下 的实现吧。 其实,源码分析是比较难写的,主要有两个原因: 一方面是源码实现一般会涉及多个知识点,所以在分析源码时需要穿插多个知识点,从而增加分析的难度。 另一方面是源码实现会处理很多细节问题,这些细节问题虽然不是设计的主要框架,但忽略了有时会让人摸不着头脑。 所以,为了降低分析的难度和让读者能够更容易看懂,在分析源码时更注重知识点的实现,而在不影响理解的情况下,我会忽略一些细节问题。而对于穿插其他知识点的时候,会先跳过其实现,并且在后续的文章对其进行分析。 mmap 原理 在之前的文章中,我们也介绍过 的原理,比如这篇:《 原来 mmap 这么简单 》。当然这篇文章只是简单介绍了 的原理,但是 的实现远不止那么简单,这是因为 涉及多个子系统,如:内存管理、文件系统、中断处理等。 好消息是,这几个子系统我们都有对应的文章介绍过: 内存管理:《 Linux虚拟内存空间管理 》 文件系统:《 什么是页缓存 》 中断处理:《 Linux中断处理 》 在阅读本文前,最好复习一下上面的文章。 虽然在《 原来 mmap 这么简单 》一文中,我们简单介绍过 的原理。但为了方便分析源码,下面还是简单回顾一下 的原理吧。 的全称是 ,中文意思是 。其用途是将文件映射到内存中,然后可以通过对映射区的内存进行读写操作,其效果等同于对文件进行读写操作。 下面我们通过一幅图来对 的原理进行阐述:
从上图可以看出,mmap 的原理就是将虚拟内存空间映射到文件的页缓存,在《 什么是页缓存 》一文中可知,对文件进行读写时需要经过页缓存进行中转的。所以当虚拟内存地址映射到文件的页缓存后,就可以直接通过读写映射区内存来对文件进行读写操作。 mmap 实现 在分析 的实现前,最好先了解其使用方式, 的使用可以参考《 原来 mmap 这么简单 》这篇文章。 1. 文件映射 当我们使用 系统调用对文件进行映射时,将会触发调用 内核函数来完成工作,我们来看看 函数的实现(经过精简后): 经过精简后的 函数主要完成 2 个工作: 首先,调用 函数来获取进程没被使用的虚拟内存区,并且返回此内存区的首地址。 然后,调用 函数继续进行映射操作。 在 32 位的操作系统中,每个进程都有 4GB 的虚拟内存空间,应用程序在使用内存前,需要先向操作系统发起申请内存的操作。操作系统会从进程的虚拟内存空间中查找未被使用的内存地址,并且返回给应用程序。 操作系统会记录进程正在使用中的虚拟内存地址,如果内存地址没被登记,说明此内存地址是空闲的(未被使用)。 我们继续来看看 函数的实现,代码如下(经过精简后): 函数主要完成以下 4 件事情: 申请一个 结构(vma),内核使用 vma 来管理进程的虚拟内存地址,关于 vma 的详细介绍可以参考:《 Linux虚拟内存空间管理 》。 设置 vma 结构各个字段的值。 通过调用文件对象的 回调函数来设置vma结构的 回调函数,一般文件对象的 回调函数为:。 把新创建的 vma 结构连接到进程的虚拟内存区链表和红黑树中。 内核使用 结构来管理进程的虚拟内存地址。当进程需要使用内存时,首先要向操作系统进行申请,操作系统会使用 结构来记录被分配出去的内存区的大小、起始地址和权限等。 我们来看看 结构的定义: 当把文件映射到虚拟内存空间时,需要把 结构的 字段设置为要映射的文件对象,然后调用文件对象的 回调函数来设置 结构的 回调函数。 结构的 回调函数的作用是:当虚拟内存区没有映射到物理内存地址时,将会触发缺页异常。而在缺页异常处理中,将会调用此回调函数来对虚拟内存映射到物理内存。 我们来看看 函数是怎么设置 结构的 回调函数的: 至此,文件映射的过程已经分析完毕。我们来看看其调用链: 2. 缺页异常 前面介绍了 系统调用的处理过程,可以发现 只是将 的 字段设置为被映射的文件对象,并且将 的 回调函数设置为 。也就是说, 系统调用并没有对虚拟内存进行任何的映射操作。 我们在《 漫画解说 “内存映射” 》一文中介绍过,虚拟内存必须映射到物理内存才能使用。如果访问没有映射到物理内存的虚拟内存地址,CPU 将会触发缺页异常。也就是说,虚拟内存并不能直接映射到磁盘中的文件。 那么 mmap() 是怎么将文件映射到虚拟内存中呢?我们在《 什么是页缓存 》一文中介绍过,读写文件时并不是直接对磁盘上的文件进行操作的,而是通过 作为中转的,而页缓存就是物理内存中的内存页。所以, 可以通过将文件的页缓存映射到虚拟内存空间来实现对文件的映射。 但我们在 系统调用的实现中,也没看到将文件页缓存映射到虚拟内存空间。那么映射过程是在什么时候发生的呢? 答案就是:缺页异常。 由于 系统调用并没有直接将文件的页缓存映射到虚拟内存中,所以当访问到没有映射的虚拟内存地址时,将会触发 。当 CPU 触发缺页异常时,将会调用 函数来修复触发异常的虚拟内存地址。 我们主要来看看 函数对文件映射的实现部分,其调用链如下: 所以我们直接来看看 函数的实现: 函数对处理文件映射部分主要分为 3 个步骤: 调用虚拟内存管理区结构(vma)的 回调函数(也就是 函数)来获取到文件的页缓存。 通过页缓存的物理内存页来生成一个页表项值,可以参考《 漫画解说 “内存映射” 》一文。 将虚拟内存地址映射到页缓存的物理内存页(也就是将进程的页表项设置为上面生成的页表项的值)。 对于 函数是怎样读取文件页缓存的,本文不作解释,有兴趣的可以自行阅读源码。 最后,我们以一幅图来描述一下虚拟内存是如何与文件进行映射的:
从上图可以看出, 是通过将虚拟内存地址映射到文件的页缓存来实现的。当对映射后的虚拟内存进行读写操作时,其效果等价于直接对文件的页缓存进行读写操作。对文件的页缓存进行读写操作,也等价于对文件进行读写操作。 一口Linux |
今日新闻 |
推荐新闻 |
CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3 |