Linux内存管理3---分页机制

 1.前言

本文所述关于内存管理的系列文章主要是对陈莉君老师所讲述的内存管理知识讲座的整理。

本讲座主要分三个主题展开对内存管理进行讲解:内存管理的硬件基础、虚拟地址空间的管理、物理地址空间的管理.

本文将主要以X86架构为例来介绍Linux内存管理的分页机制。

 2.分页机制

  •  页(Page)

将线性地址空间划分成若干大小相等的片,称为页

  •  页框(Page Frame)

物理地址空间划分成与页大小相等的若干存储块,称为页框

图 线性地址空间与物理地址空间的映射

 上图说明线性地址空间是连续的(如程序经过编译链接后是连续的线性地址),而物理地址空间可能是不连续的。

  • 页表

页表是把线性地址映射到物理地址的一种数据结构

  • 页表项内容

1.物理页面的起始地址;

2.该页的属性,表示页的特性,比如该页是否在内存,是否可以被读出或写入,具体的属性位如下:

      (1) 第0位是P(Present),如果P=1,表示页装入到内存中,如果P=0,表示不在内存中。

  (2) 第1位是R/W(Read/Write),第2位是U/S(User/Supervisor)位,这两位为页表或页提供硬件保护。

  (3) 第3位是PWT(PageWrite-Through)位,表示是否采用写透方式,写透方式就是既写内存(RAM)也写高速缓存,该位为1表示采用写透方式

  (4) 第4位是PCD(PageCache Disable)位,表示是否启用高速缓存,该位为1表示启用高速缓存。

  (5) 第5位是访问位,当对相应的物理页面进行访问时,该位置1。

  (6) 第7位是PageSize标志,只适用于页目录项。如果置为1,页目录项指的是4MB的页

  (7) 第9~11位由操作系统专用,Linux也没有做特殊之用。

  •  两级页表

图 两级页表

所谓两级页表就是对页表再进行分页。第一级称为页目录,其中存放的是关于页表的信息,第二级称为页表

页目录的起始地址存放在CR3中的高20位,因此跟踪一个进程的时候,拿到CR3就等于拿到了进程的根:-D

线性地址的最高10位(即22位~ 31位)用来产生第一级的索引,线性地址的中间10位(即21位~12位)对物理页面进行索引,最低12位表示页内偏量

举例:4MB的页表按二级页表再次分页(4MB/4K)可以分为1K个页,同样对每个页的描述需要4个字节,于是可以算出页目录最多占用4KB个字节,正好是一个页

     页目录共有1K个表项,于是,线性地址的最高10位(即22位~ 31位)用来产生第一级的索引。

   两级表结构的第二级称为页表,每个页表也刚好存放在一个4K字节的页中,包含1K个表项,第二级页表由线性地址的中间10位(即21位~12位)进行索引

          最低12位表示页内偏量。

  • 线性地址结构

图 线性地址结构

如上结构对应的伪代码:

图 线性地址结构对应的伪代码

  • 页表项结构

图 页表项结构

页目录和页表中的页表项结构都相同,都占用4个字节的长度

  • 硬件保护机制

(1)内核态或用户态对页面的访问权限

对于页表,页的保护是通过页表项的U/S标志和R/W标志来控制的,U/S为0,只有处于内核态的操作系统才能对此页或页表进行寻址,否则内核态和用户态均可寻址

(2)对页可读或可写

如果R/W为0,说明相应的页表或页是只读的,否则是可读写的

  • 线性地址到物理地址的转换 

 

图  线性地址到物理地址的转换过程

 

(1)用32位线性地址的最高10位第31~22位作为页目录项的索引,将它乘以4,与CR3中的页目录的起始地址相加,获得相应目录项在内存的地址。

(2)从这个地址开始读取32位页目录项,取出其高20位,再给低12位补0,形成的32位就是页表在内存的起始地址。

(3)用32位线性地址中的第21~12位作为页表中页表项的索引,将它乘以4,与页表的起始地址相加,获得相应页表项在内存的地址。

(4)从这个地址开始读取32位页表项,取出其高20位,再将线性地址的第11~0位放在低12位,形成最终32位页面物理地址。

3. 分页示例

  • 假如操作系统给一个正在运行的进程分配的线性地址为:0x2000000~0x2003ffff,这个空间由64页组成

如上进程地址范围最高10位为页目录域0x80,指向进程页目录的128项,如果没有给这个进程分配其它的线性地址,则进程页目录表的其它1023项为0,

也就是这个进程在进程页目录表中只占用一项

图 只有一个目录项的页目录表和页表

  • 假设进程需要读取线性地址为0x20021406中的内容,这个地址由分页机制如何处理?

中间10位的值(即页表域的值)范围从0到0x03f,或十进制的从0到63。因而只有页表的前64个表项是有意义的,其余960表项填为0。

假设进程需要读线性地址0x20021406中的内容。这个地址由分页机制按下面的方法进行处理:

1.目录域的0x80用于选择页目录的第0x80目录项,此目录项指向页表。

2.页表域的第0x21项用于选择页表的第0x21表项,此表项指向页所对应的内存物理页面。

3.最后,偏移量0x406用于在目标物理页面中读偏移量为0x406中的字节。

注:如果页表第0x21表项的Present标志为0,说明此页还没有装入内存中;在这种情况下,分页机制在转换线性地址的同时产生一个缺页异常(参见内存管理一章)。

无论何时,当进程试图访问限定在0x20000000到0x2003ffff范围之外的线性地址时,都将产生一个缺页异常,因为这些页表项都填充了0,尤其是,它们的Present标志都为0。

4.页面高速缓存

图 页面高速缓存的作用

由于在分页情况下,页表是放在内存中的,这使CPU在每次存取一个数据时,都要至少两次访问内存,从而大大降低了访问速度

为了提高速度,在IA32中设置一个最近存取页的高速缓存硬件机制,它自动保持32项处理器最近使用的页表项,因此,可以覆盖128K字节的内存地址。

当访问线性地址空间的某个地址时,先检查对应的页表项是否在高速缓存中,如果在,就不必经过两级访问了,如果不在,再进行两级访问

平均来说,页面高速缓存大约有90%的命中率,也就是说每次访问存储器时,只有10%的情况必须访问两级分页机构。这就大大加快了速度,有些书上也把页面高速缓存叫做“联想存储器”或“转换旁视缓冲器(TLB)”。

5. Linux中的分页

  • Linux主要通过分页机制来实现虚拟存储器管理

原因如下:

Linux分段机制使得所有进程都使用相同的段寄存器值,所有进程使用相同的进程地址空间(0~4G)

Linux设计目标之一就是能够移植到大多数流行的处理器平台,但很多RISC处理器平台对分段机制支持有限

  • 为了保持可移植性,Linux采用三级分页模式而不是两级

图 LInux三级分页模式

注:尽管Linux采用的是三级分页模式,但我们的讨论还是以IA32的两级分页模式为主,因此,Linux忽略中间目录层

6.参考文献

[1] Linux内存管理讲座PPT-陈莉君

posted @ 2017-07-15 15:57  jasonactions  阅读(1355)  评论(0编辑  收藏  举报