Linux-0.11-06

Peach Fuzzer Professional本文是Linux 0.11系列学习记录的正式的第六篇。
从本篇开始,在每篇文章中会加入自己的理解和补充,各位可按需查看。
09 Intel 的内存管理:分段与分页
上文说到 head.s
代码在重新设置了 gdt
和 idt
之后,此时的内存分布如下:
然后待执行的代码如下:
|
|
1. 分页机制
在前面有介绍,在保护模式下,要先经过分段机制的转换才能变成物理地址:
在没有开启分页的时候,分段机制回顾:
- 分段机制涉及的4个关键内容:逻辑地址、段描述符(描述段的属性)、段描述符表(包含多个段描述符的“数组”)、段选择子(段寄存器,用于定位段描述符表中表项的索引)。
- 转换逻辑地址到物理地址分过程如下:CPU把逻辑地址(由段选择子selector和段偏移offset组成)中的段选择子的内容作为段描述符表的索引,找到表中对应的段描述符,然后把段描述符中保存的段基址加上段偏移值,形成线性地址。
但是开启分页之后,会多一步转换:
可以看到,在开启分页后,逻辑地址经过分段机制的转换后,不会直接获得物理地址,而是一个线性地址,然后需要再通过一次分页机制转换才能得到最终的物理地址,此时其过程如下:
而对于从线性地址到分页物理地址的转换过程如下(使用32-bit分页机制):
以4K页为例,线性地址的前10位表示页表目录,中间10位表示页表项,最后12位表示页内偏移。
首先根据高10位在页目录表中找到一个页目录项,这个页目录项的值加上中间10位拼接后的地址去页表中寻找一个页表项,这个页表项的值再加上后12位的偏移地址,就是最终的物理地址。
接下来以一个例子来感受分页机制:
假设经过分段机制转换后的线性地址是15M,二进制表示为 0000000011_0100000000_000000000000
,其转换过程如下:
上述管转换过程的操作由MMU也就是内存管理单元完成,其主要作用就是将虚拟地址转换为物理地址。所以整个过程OS作为软件层,只需要提供好页目录表和页表即可,这种页表方案叫做二级页表,第一级叫做页目录表PDE,第二级叫做页表PTE,其结构如下:
之后再开启分页机制的开关,其实就是更改 cr0 寄存器中的第31位即可。在开始保护模式时,也是更改该寄存器中的第0位的值:
然后,MMU 就可以帮我们进行分页的转换了。此后指令中的内存地址(就是程序员提供的逻辑地址),就统统要先经过分段机制的转换,再通过分页机制的转换,才能最终变成物理地址。
2. 开启分页机制
下面看分页机制如何开启,也就是 setup_paging
部分,主要是帮我们把页表和页目录表在内存中写好,然后开启 cr0 寄存器的分页开关:
|
|
当时 linux-0.11 认为,总共可以使用的内存不会超过 16M,也即最大地址空间为 0xFFFFFF。而按照当前的页目录表和页表这种机制,1 个页目录表最多包含 1024 个页目录项(也就是 1024 个页表),1 个页表最多包含 1024 个页表项(也就是 1024 个页),1 页为 4KB(因为有 12 位偏移地址),因此,16M 的地址空间可以用 1 个页目录表 + 4 个页表搞定。
4(页表数)* 1024(页表项数) * 4KB(一页大小)= 16MB
所以,上面这段代码就是,将页目录表放在内存地址的最开头。
|
|
之后紧挨着这个页目录表,放置 4 个页表,代码里也有这四个页表的标签项。
|
|
最终将页目录表和页表填写好数值,来覆盖整个 16MB 的内存。随后,开启分页机制。此时内存中的页表相关的布局如下。
这些页目录表和页表放到了整个内存布局中最开头的位置,就是覆盖了开头的 system
代码了,不过被覆盖的 system
代码已经执行过了,所以无所谓。同时,如 idt 和 gdt 一样,我们也需要通过一个寄存器告诉 CPU 我们把这些页表放在了哪里,就是这段代码。
|
|
我们相当于告诉 cr3 寄存器,0 地址处就是页目录表,再通过页目录表可以找到所有的页表,也就相当于 CPU 知道了分页机制的全貌了。
至此后,整个内存布局如下。
那么具体页表设置好后,映射的内存是怎样的情况呢?那就要看页表的具体数据了,就是这一坨代码。
|
|
很简单,对照刚刚的页目录表与页表结构看。
前五行表示,页目录表的前 4 个页目录项,分别指向 4 个页表。比如页目录项中的第一项 [eax] 被赋值为 pg0+7,也就是 0x00001007,根据页目录项的格式,表示页表地址为 0x1000,页属性为 0x07 表示改页存在、用户可读写。后面几行表示,填充 4 个页表的每一项,一共 4*1024=4096 项,依次映射到内存的前 16MB 空间。
画出图就是这个样子,其实刚刚的图就是。
最终的效果就是,经过这套分页机制,线性地址将恰好和最终转换的物理地址一样。
现在只有四个页目录项,也就是将前 16M 的线性地址空间,与 16M 的物理地址空间一一对应起来了。
对于上述内容可以整理总结如下:
Intel 体系结构的内存管理可以分成两大部分,也就是标题中的两板斧,分段和分页。
分段机制在之前几回已经讨论过多次了,其目的是为了为每个程序或任务提供单独的代码段(cs)、数据段(ds)、栈段(ss),使其不会相互干扰。
分页机制是本回讲的内容,开机后分页机制默认是关闭状态,需要我们手动开启,并且设置好页目录表(PDE)和页表(PTE)。其目的在于可以按需使用物理内存,同时也可以在多任务时起到隔离的作用,这个在后面将多任务时将会有所体会。
在 Intel 的保护模式下,分段机制是没有开启和关闭一说的,它必须存在,而分页机制是可以选择开启或关闭的。所以如果有人和你说,它实现了一个没有分段机制的操作系统,那一定是个外行。
再说说那些地址:
逻辑地址:我们程序员写代码时给出的地址叫逻辑地址,其中包含段选择子和偏移地址两部分。
线性地址:通过分段机制,将逻辑地址转换后的地址,叫做线性地址。而这个线性地址是有个范围的,这个范围就叫做线性地址空间,32 位模式下,线性地址空间就是 4G。
物理地址:就是真正在内存中的地址,它也是有范围的,叫做物理地址空间。那这个范围的大小,就取决于你的内存有多大了。
虚拟地址:如果没有开启分页机制,那么线性地址就和物理地址是一一对应的,可以理解为相等。如果开启了分页机制,那么线性地址将被视为虚拟地址,这个虚拟地址将会通过分页机制的转换,最终转换成物理地址。
扩展资料
关于逻辑地址-线性地址-物理地址的转换,可以参考 Intel 手册:
Intel 3A Chapter 3 Protected-Mode Memory Management
而有关这些地址的定义和说明,在本小节中也做了详细的说明,看这里的介绍是最权威也是最透彻的。相信我,它很简单。
页目录表和页表的具体结构,可以看
Intel 3A Chapter 4.3 32-bit paging