【x86】内存页式管理

基本概念

将内存切分为等大小的页面,例如一个页位4096 B = \(2^{12}\) B,也就是4 KB。这样,内存的最小单位变成了4 KB。

4 GB (\(2^{32}\) B) 的内存地址范围为:0x 0000 0000 ~ 0x FFFF FFFF

4 KB 的内存范围为:0x 0000 0000 ~ 0x 0000 0FFF

也就是说,如果要变成以4 KB为一页来管理内存,地址就变成了

0x FFFF F000 ~ 0x FFFF FFFFF

.

.

.

.

0x 0000 2000 ~ 0x 0000 2FFF

0x 0000 1000 ~ 0x 0000 1FFF

0x 0000 0000 ~ 0x 0000 0FFF

这样的排布,也就是说,4 GB被分为 \(2^{32-12} = 2^{20}\) 个大小为4 KB的块。

这样,地址的高20位,对应着16进制地址的高5位,就代表了分页地址。找一个地方把这高20位保存起来,再加上12位的页属性,就变成了一个4个字节大小的,页表项或者叫做分页入口。

页表 Page Table | 分页

页地址高20位+页属性 | 0x FFFF F000 ~ 0x FFFF FFFFF

. | .

. | .

. | .

. | .

页地址高20位+页属性 | 0x 0000 2000 ~ 0x 0000 2FFF

页地址高20位+页属性 | 0x 0000 1000 ~ 0x 0000 1FFF

页地址高20位+页属性 | 0x 0000 0000 ~ 0x 0000 0FFF

左边的页表,总共\(2^{20}\)个页表项,一个页表项是4 个字节,也就是 \(2^{2+20} = 2^{22}\) = 4 MB的大小,即页表大小是4 MB就可以管理整个4 GB大小的所有内存空间。

这个4 MB的页表也同样用4 KB的分页来管理,\(\frac{4MB}{4KB}=\frac{2^{22}}{2^{12}}=2^{10}=1024\) ,所以总共4MB的页表也被划分为了1024个分页。

一个分页中,有\(\frac{4KB}{4}=\frac{2^{12}}{2^2}=1024\) 个页表项。

这1024个页表,放在内存中,不是专门有一个区域的,也就是可能完全杂乱随机,不按照顺序存放的。因此,我们需要一个页表目录,去存放记录页表项的入口都在哪里。

仍然是使用4个字节来存放页表入口(4个字节:页表地址高20位+页表属性),因为我们是32位的系统,总共1024个页表项,那就是\(2^{12}\)=4KB大小的页表目录,刚好是一个分页的大小。其实这都是严格设计的,保证了数据结构的4K对齐。

使用专门的寄存器CR3,来保存页目录的起始地址。

之前有说过,为了简化内存的管理,会使用平坦内存模型。也就是每个段都是4GB,每个段的起始地址都是0x 0000 00000。因此,每个段的区别是读写访问权限的不同。

分页地址计算

加上了今天的内存分页之后,物理地址的计算就变成:

逻辑地址(段选择子+偏移地址) --分段机制找到段基地址--> 线性地址(虚拟地址)(段基地址+偏移地址) --分页机制--> 物理地址

每个32位的线性地址被划分为三个部分:页目录索引(10位),页表索引(10位):偏移(12位即4K)。

首先根据分段机制,计算出来了线性地址。依据下面的步骤进行地址转换:

1、 装入进程的页目录地址(操作系统在调度进程时,把这个地址装入CR3寄存器),CR3中的值是预先存储好的。

2、 根据现行线性地址的前10位,在页目录中找对应的索引项,页目录中的项是一个页表的地址。

3、 根据线性地址的中间10位,在页表中找到页的起始地址。

4、 将页的起始地址与线性地址的最后12位相加,得到物理地址。

这样的二级模式是可以覆盖4G的物理地址空间的。

64位时代

CPU进入了64位时代之后,分段机制逐渐被弱化。但是,为了保证兼容性,没有被抛弃,段机制也无法完全关闭,而是通过将所有的段基址全部都当作0,而且忽略段选择子中的段界限的大小。

![[Pasted image 20250705153329.png]]

段寄存器中的DS/ES/SS被直接忽略,而FS/GS/CS只关注其中的几位,也就是一部分属性的大小。

![[Pasted image 20250705153455.png]]

这样,就进入了平坦模式。只需要关注Paging的几种方法

![[Pasted image 20250705153529.png]]