本文参考书:操作系统真相还原
上篇文章 主要说了虚拟内存的应用,本篇来说具体实现过程,以内核虚拟地址空间的二级页表为例
要想实现虚拟内存,首先要生成页表,页目录和页表连续存放。 如上图,
①就是要在内存中选择一个内存块存放页表,这里我们选的是0x100000。
0x100000 作为页表的基址是要放在cr3寄存器中的,如下图
1 ;先把页目录占用的空间逐字节清0
2 mov ecx, 4096
3 mov esi, 0
4 .clear_page_dir:
5 mov byte [0x100000 + esi], 0
6 inc esi
7 loop .clear_page_dir
上篇文章说过页目录有1024项,每项大小4字节,所以这里在0x100000的位置清理出4096字节。页表项其实也要清空,但是我们这里给小于1M的简易操作系统内核规划页表,页表索引PTE仅需要一项就够啦。
内核大小为1M,PTE每项可以映射4K物理地址,所需页表项PTE的的数量为:
1*1024k / 4K = 256 项
所以将PTE的前256项进行初始化指向内存空间的低1M空间即可。(内核假设在物理地址空间的低1M)
我们上面说了页目录索引PDE,需要4096字节,紧挨着的页表索引PTE的第一项地址既为0x100000 + 0x1000,将此地址存放在0x100000处。
接下来我们介绍如何进行初始化:
先看下PDE和PTE的结构
我这里就不一项一项介绍了,PDE和PTE要内存对齐都必须要放在4K的倍数的位置,所以其后12位必然全都为0。仅仅需要记录前几位就足够了,所以一个页表基址在PDE中仅仅记录20位。
还有其他的一些属性,比如这部分内存地址是数据段还是栈段,向上增长还是向下增长,可读还是可写,是否存在等等属性,请大家查看其他书籍。
;页表属性
PG_P equ 1b
PG_RW_R equ 00b
PG_RW_W equ 10b
PG_US_S equ 000b
PG_US_U equ 100b
mov eax, PAGE_DIR_TABLE_POS
add eax, 0x1000 ; 此时eax为第一个页表的位置及属性
mov ebx, eax ; 此处为ebx赋值,是为.create_pte做准备,ebx为基址。
; 下面将页目录项0和0xc00都存为第一个页表的地址,
; 这是为将地址映射为内核地址做准备
or eax, PG_US_U | PG_RW_W | PG_P ; 页目录项的属性RW和P位为1,US为1,表示用户属性,所有特权级别都可以访问.
mov [PAGE_DIR_TABLE_POS + 0x0], eax
以上代码初始化了页目录索引第一项。
页表项初始化
;下面创建页表项(PTE)edx
mov ecx, 256 ; 1M低端内存 / 每页大小4k = 256
mov esi, 0
mov edx, PG_US_U | PG_RW_W | PG_P ; 属性为7,US=1,RW=1,P=1
.create_pte: ; 创建Page Table Entry
mov [ebx+esi*4],edx ; 此时的ebx已经在上面通过eax赋值为0x101000,也就是第一个页表的地址
add edx,4096
inc esi
loop .create_ptemov
edx中存放PTE,每次增加4096字节,指向下一段4K空间。
此时还没结束,对各种进程比较了解的人应该知道,32位系统中3GB以上的空间要指向内核。虽然我们初始化的是内核的虚拟地址页表,但是也要将3GB以上的的虚拟地址再指向内核,方便后续的进程复制页表。
mov eax, PAGE_DIR_TABLE_POS
add eax, 0x1000 ; 此时eax为第一个页表的位置
or eax, PG_US_U | PG_RW_W | PG_P ; 页目录项的属性RW和P位为1,US为0
mov ebx, PAGE_DIR_TABLE_POS
mov ecx, 255 ; 范围为第768~1022的所有目录项数量
mov esi, 768
.create_kernel_pde:
mov [ebx+esi*4], eax
inc esi
add eax, 0x1000
loop .create_kernel_pde
一个PTE有1024项,每个项可以代表4K的数据,一个PTE可以表示4M空间。
那从PDE第768项开始表示3G以上空间。
从第一个页表的位置0x101000,复制到项目录索引第768项处。
第二个页表位置0x102000,复制到769项处。
。。。。。
一一对应。