参考文章

重点参考:Linux内存寻址之分页机制

还有:Linux内存寻址之分段机制

分段和分页

操作系统之内存管理科普

深入理解 Linux 内存管理

好文:
CPU阿甘之烦恼

进一步参考:

IBM: 探索 Linux 内存模型

Linux内核学习笔记3——分段机制和分页机制

Linux内存寻址之分页机制

Linux内存寻址之分段机制

引文

分页与分段的主要区别

分页和分段有许多相似之处,比如两者都不要求作业连续存放.但在概念上两者完全不同,主要表现在以下几个方面:

(1)页是信息的物理单位,分页是为了实现非连续分配,以便解决内存碎片问题,或者说分页是由于系统管理的需要.段是信息的逻辑单位,它含有一组意义相对完整的信息,分段的目的是为了更好地实现共享,满足用户的需要.

(2)页的大小固定,由系统确定,将逻辑地址划分为页号和页内地址是由机器硬件实现的.而段的长度却不固定,决定于用户所编写的程序,通常由编译程序在对源程序进行编译时根据信息的性质来划分.

(3)分页的作业地址空间是一维的.分段的地址空间是二维的.

分段机制把一个逻辑地址转换为线性地址;接着,分页机制把一个线性地址转换为物理地址。

个人总结

不同的程序都有自己的代码段、数据段、堆栈段,为了方便管理,需要对其实施分段机制。

由于局部性原理(时间局部性【不久之后很可能访问相同的数据和代码】,空间局部性【附近的存储器很有可能被访问】),使用分页机制,把程序分成小块(页框,4k),按块加载到内存里。虚拟内存使用了跟实际页框一样大的虚拟页面,维持一个页表实现映射。

重点总结一下分页机制

引文

分页机制将整个线性地址空间及整个物理内存看成由许多大小相同的存储块组成的,并把这些块作为页(虚拟空间分页后每个单位称为页)或页帧(物理内存分页后每个单位称为页帧)进行管理。不考虑内存访问权限时,线性地址空间的任何一页,理论上可以映射为物理地址空间中的任何一个页帧。最常见的分页方式是以 4KB 单位划分页,并且保证页地址边界对齐,即每一页的起始地址都应被4K整除。在4KB的页单位下,32位机的整个虚拟空间就被划分成了 2^20 个页。因为虚拟地址是按页全部被映射到相同大小的页帧,并且页面边界对齐,因此虚拟地址的后12位可以直接作为物理地址的低12位使用。

为了节省储存页表所需的内存空间(2^20 * 4B = 4M),32位操作系统常使用两级页表结构记载虚拟地址空间分页现状。因此每个虚拟地址就由三部分组成,高10位是页目录(Page Directory)中内容的索引,中间10位是页表索引,低12位则作为对应物理地址在页帧中的偏移量。

页目录保存在CR3寄存器中,可以直接访问。访问时以线性地址高10位作为索引,直接检索并得到对应索引的 32 位页目录项。32位页目录项的结构如图4中Page Directory部分所示,目录项的高20位用以给出该目录项对应的页表在内存中的物理地址的高 20 位,1024个目录项刚好能给出1024个页表的入口地址。目录项的低12位是一些标志位,其中P标志指明当前目录项对应的页表是否在内存中;U标志指明当前目录项对应的页的访问权限;S标志指明页的大小是4KB或4MB,等等。另外,由于每个页目录项的长度为32位,即4个字节,页目录中共有1024个页目录项,所以页目录的总大小为 4KB。

这里写图片描述

页表保存在内存中。页表项的长度是32位,每个页表中有1024个页表项,可得出每个页表的大小是 4 KB。页表在内存中存放时,与物理分页的大小(4KB)对齐,所以每个页表所在的物理内存的起始物理地址的后12位都是0。而该物理地址的高20位又由页表对应的页目录项中的高20位指定,这样就可以得到找到物理内存中的页表了。找到页表后,以线性地址的中间10位为索引,检索到该索引对应的32位页表项。和页目录项类似,页表项的高20用以给出其对应页帧的起始物理地址的高20位。页表项的低12位是关于页的标志位。

页帧对应物理内存。根据前面的两步找得到页帧的起始物理地址的高20位后,由于物理内存按4KB大小划分成页帧,所以页帧的起始物理地址的低12位都是0。这样高20位加低12位,得到页帧的起始物理地址。找到页帧后,使用线性地址的低10位作为偏移量,加上页帧的起始物理地址后能找到线性地址对应的物理地址了。需要注意的是,页帧和页表项的对应关系并不是确定的,页表项指向的页首先是虚拟页,然后该虚拟页的内容被储存在任何合适的页帧中。

操作系统按页为每个进程分配虚拟地址范围,理论上根据程序需要最大可使用4G的虚拟内存。但由于操作系统需要保护内核进程内存,所以将内核进程虚拟内存和用户进程虚拟内存分离,前者可用空间为1G虚拟内存,后者为3G虚拟内存。进程执行时,操作系统为其分配的页的页目录会被加载到CR3寄存器,页表会被加载到物理内存。分页单元将线性地址转换为物理地址的过程中,会检查当前进程是否有访问该分页的权限,以及线性地址对应的页数据是否在物理内存中,如果上述检查条件未被通过,分页单元将会生成页错误异常,进而中止进程或将相应分页数据加载到物理内存。

以32位的分页机制举个例子。

32位的地址分为三段(10位|10位|12位)。

假设现在有一个虚拟地址是
a|b|c

10位10位12位
abc

假设他映射的物理地址是
x|y|c

10位10位12位
xyc

首先可以肯定的是虚拟地址的最后12位与物理地址的最后12位是相同的,都是c。

然后,我们每个进程都需要一个页目录(Page Directory),这个页目录是存放在CR3寄存器里的。然后我们还需要一些页表(Page Table),这些页表是存放在内存里的。

页目录和页表其实都是一个映射表,他们的结构很相似,都是有2^10=1024个项目,每个项目都是32位(4字节),因此整个映射表的大小位4KB。个人感觉可以把他们理解为一个数组,数组的长度是1024,数组中的每个元素都是32位的信息。

好,那么咱们就开始利用虚拟地址和页目录、页表来查找物理地址了。

首先,拿到虚拟地址的前10位a,直接在CR3中的页目录(PageDirectory)中找对应的项目,发现
PageDirectory[a] = l|m|n(也是10位的l,10位的m,12位的n)

在这里得到的32位信息l|m|n,最后的12位n是一些标志位,先不用管。前面20位l|m是某个页表(PageTable)的起始地址的前20位,而且我们知道页表的起始地址的后12位肯定都是0(因为页表的大小也是4KB,12位),所以我们就可以得到某个页表的起始地址了,是l|m|0,于是,找到了某个页表。

然后,我们要在这个页表中找物理地址。把虚拟地址的中间10位b作为索引,来查找页表的项目。同理,
PageTable[b] = x|y|z

同样,最后12位z也是一些标志位,先不管。前面20位x|y便是物理地址的前20位了,最后再加上虚拟地址的最后12位偏移量c,就可以得到物理地址x|y|c了。

再总结一下页面的分配

引文

进程的建立和执行

执行程序时,操作系统会创建一个执行该程序的进程,然后装载程序或程序片段等,然后开始顺序执行代码段。在这个过程中,操作系统总的来说做三件事情:

(1) 为进程创建一个独立的虚拟地址空间(范围)
例如在32位系统常规分页状态下,操作系统发现待执行程序的指令和数据总和为32KB,那么操作系统会为进程分配8个页的虚拟内存空间,并分配页目录和页表,把页目录装入CR3,把进程用到的页表加载到内存。但并不把指令和数据加载到内存。

(2) 读取程序可执行文件文件头,并且建立虚拟空间与可执行文件中的代码段、数据段的逻辑地址的映射关系
这一步将程序指令和数据映射到虚拟内存空间中。

(3) 将 CPU 的指令寄存器设置成可执行文件的入口地址,启动运行
执行程序过程时,如果当前指令或数据之在虚拟地址空间中,而实际上并不在物理内存中(前两步都没有将指令或数据加载到物理内存),将发生页错误,这时操作系统再从物理内存分配一个空闲的物理页帧,并将虚拟地址页对应的数据从磁盘拷贝加载到物理页帧中,并建立页表项和页帧的映射关系。随着进程的执行,页错误也会不断产生,操作系统也会响应每个页错误并为进程分配物理内存页帧。但物理内存是有限的,为一个进程可分配的物理内存也有限。全部可用物理内存都分配给进程后,如果进程继续抛出页错误请求更多物理内存,这时候操作系统根据自身的页置换操作算法,在保证进程正常运行的前提下,将先前为进程分配的物理内存页帧收回,重新分给该进程。

以引文的例子做具体说明。

程序需要32KB的内存,8个页,也就是8个页框。1个页表可以保存1024个页框的信息,因此分配1个页表即可。页目录也是1个。

Logo

更多推荐