本文参考书:操作系统真相还原

上篇文章   主要说了虚拟内存的应用,本篇来说具体实现过程,以内核虚拟地址空间的二级页表为例

 

ovz架构使用虚拟内存 虚拟内存 实现_页表项

 

 要想实现虚拟内存,首先要生成页表,页目录和页表连续存放。 如上图,

①就是要在内存中选择一个内存块存放页表,这里我们选的是0x100000。

0x100000 作为页表的基址是要放在cr3寄存器中的,如下图

ovz架构使用虚拟内存 虚拟内存 实现_初始化_02

 

 

 

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的结构

ovz架构使用虚拟内存 虚拟内存 实现_页表_03

 

 我这里就不一项一项介绍了,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项处。

。。。。。

一一对应。