开启分页机制
必须说明的是,必须在保护模式下才能启动分页功能。本章的引导程序依然用第13章的。
开启分页机制之前,必须创建页目录和页表。因为内核是在开启页功能之前加载的,段在内存中的位置已经固定。在这种情况下,即使开启了分页功能,线性地址也必须和物理地址相同才行,不然就会“跑飞”。
因为我们的内核很小,将其加载到内存后,连低端的1MB空间都没有占完全,所以只需要将低端1MB内存做特殊处理,使这部分内存的线性地址和物理地址相同即可。这样做的好处是:内核不用做任何变动即可在分页模式下继续正常工作,再也不会担心“跑飞”了。
对于页目录和页表,页目录必须在内存中,页目录和页表必须各自占用一个自然页,也就是说它们的物理地址的低12位必须全零。
一个页表最多可以容纳1024个表项,对应1024个页,也就是4MB内存。对于我们的内核,还不到1MB,所以一个页目录和一个页表足矣。作者的设计是把页目录放在物理地址0x0002_0000处,把页表放在0x0002_1000处。
926 ;页目录表清零
927 mov ecx,1024 ;1024个目录项
928 mov ebx,0x00020000 ;页目录的物理地址
929 xor esi,esi
930 .b1:
931 mov dword [es:ebx+esi],0x00000000 ;页目录表项清零
932 add esi,4
933 loop .b1
以上代码将页目录和页表的所有目录项清零。之所以这样做,主要是使所有目录项的P位为0.
935 ;在页目录内创建指向页目录自己的目录项
936 mov dword [es:ebx+4092],0x00020003
937
938 ;在页目录内创建与线性地址0x00000000对应的目录项
939 mov dword [es:ebx+0],0x00021003 ;写入目录项(页表的物理地址和属性)
创建2个目录项。
936:P=1;RW=1;US=0;这个目录项指向了自己,为的是可以方便地用线性地址访问自己。
939:添加一个表项,使其指向页表。P=1;RW=1;US=0。
942 mov ebx,0x00021000 ;页表的物理地址
943 xor eax,eax ;起始页的物理地址
944 xor esi,esi
945 .b2:
946 mov edx,eax
947 or edx,0x00000003
948 mov [es:ebx+esi*4],edx ;登记页的物理地址
949 add eax,0x1000 ;下一个相邻页的物理地址
950 inc esi
951 cmp esi,256 ;仅低端1MB内存对应的页才是有效的
952 jl .b2
953
954 .b3: ;其余的页表项置为无效
955 mov dword [es:ebx+esi*4],0x00000000
956 inc esi
957 cmp esi,1024
958 jl .b3
上面的代码用来填写页表项。填写后,内核的页目录和页表视图如下。
960 ;令CR3寄存器指向页目录,并正式开启页功能
961 mov eax,0x00020000 ;PCD=PWT=0
962 mov cr3,eax
963
964 mov eax,cr0
965 or eax,0x80000000
966 mov cr0,eax ;开启分页机制
961~962:PWT=0,PCD=0;登记页目录表的基地址。
964~966:正式开启分页机制。
分页机制开启后,根据上面的页目录和页表视图,可以算出:
线性地址:0x0000_0000~0x0000_FFFF
映射到
物理地址:0x0000_0000~0x0000_FFFF;
线性地址:0x8000_0000~0x8000_FFFF
也映射到
物理地址:0x0000_0000~0x0000_FFFF;