【自制操作系统05】开启内存分页机制
通过前四章的努力,我们成功将控制权转交给了 loader.asm 这个程序,并且从实模式跨越到了保护模式。第四章讲保护模式的时候我说过,这是我们操作系统的第一个精彩之处。但其实这只是针对之前我们进行的只是无意义的输出,以及硬盘的加载等工作。但到了这一章,之前一步步的努力进入到了保护模式,也只能说是做了很多苦力,其实很多代码都是固定的,给我们发挥的空间也不大。
但是到了本章,可以说终于有能体现出我们设计能力的地方了。
一、实现分页要做哪些事
还是先直接简单说要做的事,再说为什么,实现分页要做以下三件事:
- 在内存某位置写好页表
- 页目录地址赋值给 cr3 寄存器
- 将 cr0 寄存器的 pg 位置 1
我们对比下进入保护模式中实现段描述符机制需要做的三件事:
- 在内存某位置写好段描述符表
- 段描述符表地址赋值给 gdtr 寄存器
- 将 cr0 寄存器的 pe 位置 1(这个其实是开启保护模式)
你看,是否是非常相似呢?都是内存某位置准备xxx,把起始地址赋值给一个特定的寄存器,然后将另一个特殊寄存器的某位置 1 表示开启。所以上一章我说过,cpu 与操作系体打配合,这种模式运用得非常多。我们写操作系统的人不用管 cpu 的具体实现,只需要按照指定步骤操作即可,之后硬件会帮我们完成所需要的功能。
二、为什么要分页
说实话我也想不明白为什么要分页,主要是我说不上来为什么不是其他方式,所以这块我也只能跟着官方说的去理解了。
如果只用段式管理的话,段大小不一致,且同一个程序逻辑地址和物理地址都是连续的。段大小不一致导致内存有大段有小段,也会留下一些内存碎片,过大的段查不进来,过小的段插进去又会产生更小的碎片。同一个段内所有的程序地址都是连续的,这也导致不灵活,我们希望能有一套机制使得程序所用的逻辑地址连续,但实际映射到的物理地址并不连续,增加这么一个层来解决这个问题。
我们本讲只是准备一些必要的页表,然后开启页表机制。等到后面多任务的时候才能真正体会到页表的用处以及好处,所以我们姑且先简单理解下,至于具体的好处,其实有好多细节的,等以后用到的时候慢慢体会。
三、页表长什么样以及虚拟地址到物理地址的转换
我们可以类比段的转化,我们最初给的地址是 段选择子:段内偏移值,在保护模式下,用段选择子去内存中的段描述符表中,找到段描述符,取出段基址,再+段内偏移地址,得到最终的物理地址。
页的转化也是类似的,上一步通过段描述符得到的“物理地址”,再开启分页后叫做逻辑地址。这个逻辑地址也是分成 前半部分:后半部分 这种形式,用前半部分的值在页表中寻找并换出一个页地址(也可以理解成基址这个概念),然后再拼接上后半部分的值,得到最终的物理地址。
只不过,现在的页表方案一般是二级页表,第一级叫页目录表(PDE),第二级叫页表(PTE)。然后这个逻辑地址就是被看成 高10位:中间10位:后12位。高10位负责再页目录表中找到一个页目录项,这个页目录项的值加上中间10位拼接后的地址去页表中去寻找一个页表项,这个页表项的值,再加上后12位,拼接后的地址就是最终的物理地址。
12位可以表示 4K,所以也就是一个页可表示的内存大小为 4KB。10位可以表示 1K,所以页目录表中最多有 1024 个页目录项,一个页表中最多有 1024 个页表项,那最大可表示的内存范围就是 1024 * 1024 * 4KB = 4G。其实这也是废话,你可以仔细想想看,不论你分成几级页表,只要是通过这种方式寻址的,只要是一个 32 位的地址,总是可以表示 4G 大小的。只不过通过你的不同分法,可能导致页大小,页目录项数目,页表数目,以及假如你定了 n 级页表后的 n 级页表的页表项数目不同而已。
页目录表和页表的数据结构
虚拟地址到物理地址的转换
四、页表设计
我们这样设计页表:
- 页目录表的第 0 项和第 768 项,都对应紧接着的第一个页表,映射了低端 1M 的物理内存(0x00000-0x100000),也就是说逻辑地址的开端 1M 和 3G 以上的第一个 1M 地址,都对应这物理内存的地段 1M。
- 页目录表的第 769~1022 项,分别往后对应 254 个页表,不过这些页表还没有写,先空着
- 页目录表的第 1023 项,其地址指向该页目录表本身(也就是把页目录表当作页表去理解了),通过这种方式可以访问页目录表本身。(这块其实我也没理解为啥要这么搞,无非就是想用虚拟地址访问到这个页表本身嘛。
为什么这样设计呢?
因为我们分页之前的代码(loader)都在低端 1MB 范围内,所以开启分页之后的逻辑地址开始的 1M 也要一一对应上物理地址的开始 1M,所以有了第 0 个页目录项。第 768 个页目录项对应着逻辑地址 3G 以上的 4M( 0xc0000000~0xc03fffff 不过我们页表只写了 256 项也就是规划了 1M),这是因为我们决定将操作系统内核写在 3G 以上的 1M 空间里。
我们规划,虚拟地址的 0~3G 是用户空间,3~4G 是内核空间,所以我们提前把页目录表的第 769~1022 项建好,至于为什么以后再说。
五、上代码
loader.asm
...
;创建页表并初始化(页目录和页表)
PAGE_DIR_TABLE_POS equ 0x100000
call setup_page
;重新加载 gdt,因为已经变成了虚拟地址方式
sgdt [lgdt_value]
mov ebx,[lgdt_value+2]
or dword [ebx+0x18+4],0xc0000000
add dword [lgdt_value+2],0xc0000000
add esp,0xc0000000
;页目录表起始地址存入 cr3 寄存器
mov eax,PAGE_DIR_TABLE_POS
mov cr3,eax
;开启分页
mov eax,cr0
or eax,0x80000000
mov cr0,eax
;重新加载 gdt
lgdt [lgdt_value]
mov byte [gs:0x1e0],'p'
mov byte [gs:0x1e2],'a'
mov byte [gs:0x1e4],'g'
mov byte [gs:0x1e6],'e'
mov byte [gs:0x1ea],'o'
mov byte [gs:0x1ec],'n'
jmp $
setup_page:
;先把页目录占用的空间逐字清零
mov ecx,4096
mov esi,0
.clear_page_dir:
mov byte [PAGE_DIR_TABLE_POS+esi],0
inc esi
loop .clear_page_dir
;开始创建页目录项(PDE)
.create_pde:
mov eax,PAGE_DIR_TABLE_POS
add eax,0x1000; 此时eax为第一个页表的位置及属性
mov ebx,eax
or eax,111b
mov [PAGE_DIR_TABLE_POS],eax
mov [PAGE_DIR_TABLE_POS+0xc00],eax
sub eax,0x1000
mov [PAGE_DIR_TABLE_POS+4*1023],eax
;开始创建页表项(PTE)
mov ecx,256
mov esi,0
mov edx,111b
.create_pte:
mov [ebx+esi*4],edx
add edx,4096
inc esi
loop .create_pte
;创建内核其他页表的页目录项(PDE)
mov eax,PAGE_DIR_TABLE_POS
add eax,0x2000
or eax,111b
mov ebx,PAGE_DIR_TABLE_POS
mov ecx,254
mov esi,769
.create_kernel_pde:
mov [ebx+esi*4],eax
inc esi
add eax,0x1000
loop .create_kernel_pde
ret
...
六、运行
Makefile 仍然和上一章一样,所以直接执行 make brun
可以看到分页开启后,成功在屏幕输出了 pageon 字符串
七、学到这的一些感悟
我之前写过一篇文章 究竟什么是技术,还被好多人骂了。我文章里说的就是感觉现在做的事情(Java),以及好多好多所谓的技术,都只是应用而已,甚至觉得只有基础科学,只有研究质子中子电子,这些东西才算是真正的技术,其他的只是应用而已。
不过现在我知道自己的问题所在了,因为我研究操作系统就是想做点真正的技术。但现在看来,如果还延续当时的想法,像开启分页,进入保护模式,往显卡映射的内存写数据,这些都应该只叫做应用。因为这些的底层原理是 cpu 硬件电路的布线方式,我们的操作系统只是应用了它们,把一些操作封装起来再暴露给用户而已。
但如果真这样深入下去,其实是没完没了的,你的求知欲又会深入到物理层面,这其实跟计算机技术已经相差甚远了。所以我现在觉得,把底层细节当作已知,在这上面建立一套完善的体系,这本身就是这一层的技术了,每一层有每一层技术的复杂性,不能说越底层的才越接近技术,越接近真理。
所以,你可以不断深入探索底层的技术,但大可不必对自己所研究层次的知识妄自菲薄。
写在最后:开源项目和课程规划
如果你对自制一个操作系统感兴趣,不妨跟随这个系列课程看下去,甚至加入我们(下方有公众号和小助手微信),一起来开发。
参考书籍
《操作系统真相还原》这本书真的赞!强烈推荐
项目开源
当你看到该文章时,代码可能已经比文章中的又多写了一些部分了。你可以通过提交记录历史来查看历史的代码,我会慢慢梳理提交历史以及项目说明文档,争取给每一课都准备一个可执行的代码。当然文章中的代码也是全的,采用复制粘贴的方式也是完全可以的。
如果你有兴趣加入这个自制操作系统的大军,也可以在留言区留下您的联系方式,或者在 gitee 私信我您的联系方式。
课程规划
本课程打算出系列课程,我写到哪觉得可以写成一篇文章了就写出来分享给大家,最终会完成一个功能全面的操作系统,我觉得这是最好的学习操作系统的方式了。所以中间遇到的各种坎也会写进去,如果你能持续跟进,跟着我一块写,必然会有很好的收货。即使没有,交个朋友也是好的哈哈。
目前的系列包括
公众号 - 低并发编程