Linux黑科技|mmap实现详解

您所在的位置:网站首页 linux系统物理地址 Linux黑科技|mmap实现详解

Linux黑科技|mmap实现详解

2023-03-11 14:41| 来源: 网络整理| 查看: 265

故事的开始是这样的,某天在脉脉上看到有人发了下面的帖子:

想不到 都成了黑科技了,为了让大家都能了解这个黑科技,所以还是写篇文章来详细介绍一下 的实现吧。

其实,源码分析是比较难写的,主要有两个原因:

一方面是源码实现一般会涉及多个知识点,所以在分析源码时需要穿插多个知识点,从而增加分析的难度。

另一方面是源码实现会处理很多细节问题,这些细节问题虽然不是设计的主要框架,但忽略了有时会让人摸不着头脑。

所以,为了降低分析的难度和让读者能够更容易看懂,在分析源码时更注重知识点的实现,而在不影响理解的情况下,我会忽略一些细节问题。而对于穿插其他知识点的时候,会先跳过其实现,并且在后续的文章对其进行分析。

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