经过前四章的努力,咱们成功将控制权转交给了 loader.asm 这个程序,而且从实模式跨越到了保护模式。第四章讲保护模式的时候我说过,这是咱们操做系统的第一个精彩之处。但其实这只是针对以前咱们进行的只是无心义的输出,以及硬盘的加载等工做。但到了这一章,以前一步步的努力进入到了保护模式,也只能说是作了不少苦力,其实不少代码都是固定的,给咱们发挥的空间也不大。html
可是到了本章,能够说终于有能体现出咱们设计能力的地方了。git
仍是先直接简单说要作的事,再说为何,实现分页要作如下三件事:数据结构
咱们对比下进入保护模式中实现段描述符机制须要作的三件事:oop
你看,是不是很是类似呢?都是内存某位置准备xxx,把起始地址赋值给一个特定的寄存器,而后将另外一个特殊寄存器的某位置 1 表示开启。因此上一章我说过,cpu 与操做系体打配合,这种模式运用得很是多。咱们写操做系统的人不用管 cpu 的具体实现,只须要按照指定步骤操做便可,以后硬件会帮咱们完成所须要的功能。学习
说实话我也想不明白为何要分页,主要是我说不上来为何不是其余方式,因此这块我也只能跟着官方说的去理解了。操作系统
若是只用段式管理的话,段大小不一致,且同一个程序逻辑地址和物理地址都是连续的。段大小不一致致使内存有大段有小段,也会留下一些内存碎片,过大的段查不进来,太小的段插进去又会产生更小的碎片。同一个段内全部的程序地址都是连续的,这也致使不灵活,咱们但愿能有一套机制使得程序所用的逻辑地址连续,但实际映射到的物理地址并不连续,增长这么一个层来解决这个问题。设计
咱们本讲只是准备一些必要的页表,而后开启页表机制。等到后面多任务的时候才能真正体会到页表的用处以及好处,因此咱们姑且先简单理解下,至于具体的好处,其实有好多细节的,等之后用到的时候慢慢体会。code
咱们能够类比段的转化,咱们最初给的地址是 段选择子:段内偏移值,在保护模式下,用段选择子去内存中的段描述符表中,找到段描述符,取出段基址,再+段内偏移地址,获得最终的物理地址。htm
页的转化也是相似的,上一步经过段描述符获得的“物理地址”,再开启分页后叫作逻辑地址。这个逻辑地址也是分红 前半部分:后半部分 这种形式,用前半部分的值在页表中寻找并换出一个页地址(也能够理解成基址这个概念),而后再拼接上后半部分的值,获得最终的物理地址。blog
只不过,如今的页表方案通常是二级页表,第一级叫页目录表(PDE),第二级叫页表(PTE)。而后这个逻辑地址就是被当作 高10位:中间10位:后12位。高10位负责再页目录表中找到一个页目录项,这个页目录项的值加上中间10位拼接后的地址去页表中去寻找一个页表项,这个页表项的值,再加上后12位,拼接后的地址就是最终的物理地址。
12位能够表示 4K,因此也就是一个页可表示的内存大小为 4KB。10位能够表示 1K,因此页目录表中最多有 1024 个页目录项,一个页表中最多有 1024 个页表项,那最大可表示的内存范围就是 1024 * 1024 * 4KB = 4G。其实这也是废话,你能够仔细想一想看,不论你分红几级页表,只要是经过这种方式寻址的,只要是一个 32 位的地址,老是能够表示 4G 大小的。只不过经过你的不一样分法,可能致使页大小,页目录项数目,页表数目,以及假如你定了 n 级页表后的 n 级页表的页表项数目不一样而已。
由于咱们分页以前的代码(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 私信我您的联系方式。
本课程打算出系列课程,我写到哪以为能够写成一篇文章了就写出来分享给你们,最终会完成一个功能全面的操做系统,我以为这是最好的学习操做系统的方式了。因此中间遇到的各类坎也会写进去,若是你能持续跟进,跟着我一块写,必然会有很好的收货。即便没有,交个朋友也是好的哈哈。
目前的系列包括