NIO - 内存映射文件
内存映射文件一直没弄明白,这几天在网上到处搜索,看了两篇文章,总算是弄明白了。在讲内存映射文件前,先讲讲 MMU 和内存映射到底是是什么。
MMU 是 Memory Management Unit 的缩写,中文名是内存管理单元,它是中央处理器(CPU)中用来管理虚拟内存、物理内存的控制线路,同时也负责虚拟地址映射为物理地址,以及提供硬件机制的内存访问授权。
这里提到了虚拟内存,虚拟地址,物理地址,虚拟地址和物理地址的映射。词语多了,有点晕。我们来举一个例子来说明下。
任何时候,计算机上都存在一个程序能够产生的地址集合,我们称之为地址范围。这个范围的大小由 CPU 的位数决定。例如一个 32 位的 CPU,它的地址范围是 0 ~ 0xFFFFFFFF(4G)。这个范围就是我们的程序能够产生的地址范围,我们把这个地址范围称为虚拟地址空间。该空间中的某一个地址我们称之为虚拟地址。与虚拟地址空间和虚拟地址相对应的则是物理地址空间和物理地址。对于这台机器,如果我们配置的是 2G 的内存,那么,它的虚拟地址空间范围是 0x00000000 ~ 0xFFFFFFFF(4G),而物理地址空间范围是 0x000000000 ~ 0x7FFFFFFF(2G)。每个进程都有自己的 4G 地址空间(32 位操作系统),从 0x00000000 ~ 0xFFFFFFFF。
目前大多数操作系统都会采用分页(paging)机制。虚拟地址空间的划分以页(page)为单位,而相应的物理地址空间的划分以页桢(frame)为单位。页和页桢的大小必须相同。为了简化描述,这里我们把虚拟地址空间和物理地址空间来划分空间的单位均称为页。
使用了分页机制之后,4G 的虚拟地址空间被分成了固定大小的页,每一页或者被映射到物理内存,或者被映射到硬盘上的交换文件(虚拟内存)中,或者没有映射任何东西。对于一般程序来说,4G 的地址空间,只有一小部分映射了物理内存,大片大片的部分是没有映射任何东西。CPU 是依据于一个叫做页目录和页表的结构来将虚拟地址转换成物理地址。CPU 在执行内存指令时,会自动根据页目录和页表中的信息,把虚拟地址转换成物理地址。
物理内存分页,一个物理页的大小为 4K 字节,页的索引从 0 开始,即 第 0 页,第 1 页,第 2 页,等等。因此,第 0 个物理页从物理地址 0x00000000 开始,由于一页为 4K,即 0x1000,所以,第 1 个物理页从物理地址 0x00001000 开始,第 2 个物理页从物理地址 0x00002000 开始,以此类推,第 12 个物理页从物理地址 0x0000C000 开始。注意,这里我们描述的是页的起始地址,如果要找某页的第 N(N < 4096,即 4K,因为一页 4K 个字节) 个字节,那么 N 就是所谓的偏移量。很明显,一页的偏移量,即最大 4K,用 12bit 表示即可。因此,对于 32 位 CPU,用 32bit 地址的低 12bit 表示偏移量,剩下的高 20bit 用来寻址(寻找页的起始地址)。64 位的 CPU 同理,一页同样是 4K,所以用低 12bit 表示偏移量,剩下的高 52bit 用来寻址。
接下来要讲的是页表和页目录的概念,这两个概念比较重要,因为 CPU 就是依赖这两个信息来将虚拟地址砖为物理地址的。页表和页目录都是存放在物理页中的。
一个页表大小为 4K,因此一个页表是存放在一个物理页中的。一个页表由 1024 个页表项组成,所以,一个页表项大小为 4 个字节,即 32bit。页表项会存储一些信息,高 20bit 存储一个物理页的起始地址,低 12bit 存放一些标志。所以,一个页表可以记录 1024 个物理页的起始地址。如果是 64 位的 CPU,20bit 不足以存放物理页的起始地址,我也没弄明白如何处理,当然这不是讨论的范畴。我猜测,64 位 CPU,一个页表由 512 个页表项组成,一个页表项 8 个字节,这样就由足够的 bit 来寻址了。
一个页目录大小为 4K,因此一个页目录是存放在一个物理页中的。和页表类似,一个页目录由 1024 个页目录项组成,一个页目录项大小为 4 个字节,即 32bit。页目录项同样会存储一些信息,高 20bit 存放一个页表(页表是放在一个物理页中)所在物理页的起始地址,低 12bit 存放一些标志。对于 64 位 CPU,我依然和页表的猜测是一样的思路。
因此,只要能找到页目录,就能找到页表,进而找到物理页。事实上,对于 x86 系统,页目录的物理地址放在 CPU 的 CR3 寄存器中。
这里,我们梳理一下几个概念(以 32bit CPU 为例):
每个进程都有独立的 4G 的虚拟地址。因此,对于某个虚拟地址,请问下自己,它是属于哪个进程的。 每个进程都有一个入口的物理地址。这个物理地址存放于 CR3 寄存器中,其实就是该进程的页目录表基地址(物理地址)。可以理解成:进程的入口地址 = CR3 = 页目录表的基地址。 不同的进程可以有相同的虚拟地址,但它们映射成的物理地址是不一样的。因为 CR3 寄存器存的页目录基地址是不一样的。 在用户进程空间中,只有一部分虚拟地址映射着实际物理地址。
接下来,就开始讲 CPU 将虚拟地址转换成物理地址的过程了,请仔细阅读了。一个虚拟地址 32bit,我们分成三段:高 10bit,中间 10bit,低 12bit。它们依次代表着页目录的索引,页表的索引和物理页的偏移量。对于一个要转换成物理地址的虚拟地址(假设为 0x01AF5518),将按照以下步骤来操作: