第九学 linux内核——内存寻址——分页机制(1)

时间:2021-08-17 06:41:27

         先前我们介绍段机制的时候说到,x86的段机制把程序的逻辑地址转换成线性地址,这里要讲的分页机制是把线性地址映射成物理地址,也就说说,x86其实是用了两套机制把逻辑地址转换成物理地址的。我们也提到linux内核是怎样绕过段机制从而让x86的分段机制看起来不起作用的,我们还说到这样的处理造成了段的数据保护的问题。下来我们就来讨论x86的分页机制。我们的思路依然是先介绍x86的分页机制,然后再讨论linux在上边的实现。

      我们还是从实现linux分页机制的软硬件构造上入手。实现分页机制的硬件结构有:四个专用于分页机制的32位寄存器:CR0、CR1、CR2、CR3,分页部件,页面高速缓存;软件结构有:页,页表,页目录,页表项。

1、四个寄存器

        我们说CR0这个寄存器决定了我们是否启用分页机制,CR0=0时表示不启用分页机制,此时经过段机制转换来的地址直接映射到物理地址(大家可以想到,如果这样的话,像上节linux内核的处理方法,势必会带来段的数据保护问题的),反之,映射到4G的线性地址。

        CR1暂时没用。

        CR2是缺页线性地址寄存器,当发生缺页异常的时候,该寄存器保存最后一次出现缺页的全32位线性地址,这在异常处理时会介绍到,这里我们不深究它的实现过程。

        CR3就厉害了,它被叫做页目录基址寄存器,顾名思义,它保存的是页目录的物理地址。我们就是通过该寄存器里边的值,找到对应的页目录中的页表项,从而知道我们的一个页是存储里内存中的哪个位置的。

2、页与页表

        1)、页

       分页机制是这么一个思路:我们将线性地址空间划分成若干相等的片,这一片就称为一个页,并给各页编号,从0开始,如第1页,第2页等。相应的,我们也把物理地址空间划分成与页大小相等的若干存储块,也同样为他们编号,仍然可以从0开始。程序的映射过程是这样的,现在我们的程序的容量是以页为单位(即要么占一个页大小,要么占几个页大小),那么我们要控制线性地址空间中的每个页怎么往物理地址里边存就比较容易了。

        这样就有一个问题了,因为页是固定大小的,那么它过大或者过小都会影响内存使用率。页过大,有时候会存在冗余空间;页过小,若有分支指令,则会存在在页之间跳转的情况,同样影响效率。x86支持的标准页大小为4KB(也支持4MB)。

        2)、页表

        页表是把线性地址转换成物理地址的一种数据结构。它的作用相当于段描述符表。它包括两个成员:

        物理页面基地址:线性地址空间中的一个页装入内存后所对应的物理页面的起始地址。

        页的属性:描述一些页的属性信息。

第九学 linux内核——内存寻址——分页机制(1)

        我们说过页面大小为4KB,即一个页面会占4KB的空间,那么每个页面的物理页面基地址必然是4KB的整数倍,这样其地址的最低12位总是0,那么我们就可以用这12位存放页的属性,这样用32位的地址就完全可以描述页的映射关系,也就是页表中一个表项占四个字节就够了。

        不过,4GB的线性地址空间可以被划分为1M个4KB大小的页,每个页表项占4个字节,则1M个页表项就需要占4MB的空间,而且还要求是连续的,这显然是不现实的,于是把这4MB的页表再以4KB为大小分页,分为1K个页,同样对每个项描述需要四个字节,这就是两级页表的管理方式。

        3)、两级页表

第九学 linux内核——内存寻址——分页机制(1)

                             两级页表结构

        两级页表的第一级把它叫做页目录,用它来管理1M个页的页表。上边说我们把1M个页表项以4KB为页分了1K个页,于是我们用10位就可以检索到这1K个页目录的每个目录项了。现在我们把这1K个页目录(占4KB)放进内存的某个位置,这个位置就叫做页目录起始地址。我们把这个起始地址放进CR3中,现在来一个线性地址,我们根据线性地址的前10位,再根据CR3中的页目录起始地址,就可以得到目录项在内存中的地址。那我们读取这个目录项,其高20位就是页表在内存中的起始地址。于是我们用线性地址的中间10位和这个页表起始地址就可以计算出页表项的位置,这个页表项和线性地址的的低12位就可以把这个线性地址定位到内存中具体的位置了。下图是32位线性地址往物理地址的转换过程。

第九学 linux内核——内存寻址——分页机制(1)

3、页面高速缓存

        知道cache原理的童鞋们对这个理解起来就比较容易了。它无非就是为了加快访问速度。

        由于在分页情况下,页表是放在内存中的,这使CPU每次至少两次去访问内存。页面高速缓冲器保存最近处理过的32项页表项。当访问线性地址空间的某个地址时,先检查对应的页表项是否在缓存中,如果在就没必要两次访问内存了。高速缓存的命中率哈市相当高的。