创建页目录表及页表
二级页表布局 |
---|
。。。 |
第二个页表 (0x102000) |
第一个页表 (0x101000) |
页目录表(起始地址0x100000) |
;---------------- 页表配置 ---------------------------
PAGE_DIR_TABLE_POS equ 0x100000 ;物理内存地址1MB处
;---------------- 页表相关属性 ---------------------------
PG_P equ 1b
PG_RW_R equ 00b
PG_RW_W equ 10b
PG_US_S equ 000b
PG_US_U equ 100b
1:确定页表地址 0x100000 物理内存1MB处
2: 先把页目录占用的空间逐字节清0(4096字节,1024个页目录项,4kb,2^12, 0x1000)
;先把页目录占用的空间逐字节清0
mov ecx, 4096
mov esi, 0
.clear_page_dir:
mov byte [PAGE_DIR_TABLE_POS + esi], 0
inc esi
loop .clear_page_dir
3: 开始创建页目录项(第0个和第768个(3G开头第一个),装第一个页表和属性,把低端1MB内存映射到高端3G的第一个1MB)
页目录(1024个pte) |
---|
... |
第768个页目录项也装第一个页表的位置及其属性 |
... |
... |
... |
第0个页目录项装第一个页表的位置及其属性 |
开始创建页目录项(PDE)
.create_pde: ; 创建Page Directory Entry
mov eax, PAGE_DIR_TABLE_POS
add eax, 0x1000 ; 此时eax为第一个页表的位置及属性
mov ebx, eax ; 此处为ebx赋值,是为.create_pte做准备,ebx为基址。
; 下面将页目录项0和0xc00(第768个,3/4)都存为第一个页表的地址,
; 一个页表可表示4MB内存,这样0xc03fffff(3G以上的第一个4MB)以下的地址
; 和0x003fffff(第一个4MB)以下的地址都指向相同的页表,
; 这是为将地址映射为内核地址做准备
or eax, PG_US_U | PG_RW_W | PG_P ; 页目录项的属性RW和P位为1,US为1,
; 表示用户属性,所有特权 级别都可以访问.111b = 7
mov [PAGE_DIR_TABLE_POS + 0x0], eax ; 第1个目录项,
; 在页目录表中的第1个目录项写入第一个页表的位置(0x101000)及属性(3)
mov [PAGE_DIR_TABLE_POS + 0xc00], eax ; 一个页表项占用4字节,
; 0xc00表示第768个页表占用的目录项,0xc00以上的目录项用于内核空间,
; 也就是页表的0xc0000000~0xffffffff共计1G属于内核,
; 0x0~0xbfffffff共计3G属于用户进程.
sub eax, 0x1000
mov [PAGE_DIR_TABLE_POS + 4092], eax ; 使最后一个目录项指向页目录表自己的地址
4 创建低端1MB内存对应的页表项(第一个页表的前256个页表项)
;下面创建页表项(PTE)
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_pte
5:创建内核其他页表的pde(页目录项)(从高端3GB 4MB开始
;创建内核其它页表的PDE
mov eax, PAGE_DIR_TABLE_POS
add eax, 0x2000 ; 此时eax为第二个页表的位置0x102000
or eax, PG_US_U | PG_RW_W | PG_P ; 页目录项的属性US,RW和P位为1
mov ebx, PAGE_DIR_TABLE_POS
mov ecx, 254 ; 范围为第769~1022的所有目录项数量
mov esi, 769
.create_kernel_pde:
mov [ebx+esi*4], eax
inc esi
add eax, 0x1000
loop .create_kernel_pde
ret
总结,
内核空间占整个4GB地址空间的高端1GB,且本项目内核较小,总共不超过70KB,就放在物理内存低端1MB里面了(找了个连续空闲内存用)。然后把低端1MB和高端3GB的第一个1MB做了对等映射。一个页目录表管4GB内存,1024个页目录项,一个页目录项管一个页表,那一个页表就管理4MB内存,一个页表有1024个页表项,一个页表项管1个物理页框,那一个页框就4KB内存大小。内核空间占了1GB,就需要256个页目录项来管,开头的第一个页目录项(0),和内核开头的第一个页目录项(768)都存了第一个页表的地址,这是为了保证loader在分页机制下运行正确(加载内核之前一直是loader在运行,它本身代码在1MB之内,保证之前段机制下的线性地址和和分页后的虚拟地址对应的物理地址一致。)和让虚拟地址3GB以上的第一个1MB指向物理内存的第一个1MB,让内核顺利执行。
虚拟地址高端1GB的第一个4MB(256个页目录表中的第一个页目录表)指向了物理地址对应的低端4MB(开头的第一个也指向了这里),因为内核在低端1MB里面,地址转换流程是:cr3(页目录项地址)-》页目录项-〉页表项-》物理页框。所以只需要创建低端1MB物理页框对应的页表项,这里是连续分配的,就是在低端1MB内存中,虚拟地址=物理地址。(且虚拟地址0~1MB和3G~3G 1MB 对应相同的物理地址0~1MB)
同时为了在以后的内存管理系统中,获得虚拟地址的页目录项指针和页表项指针(这里指针也是虚拟地址)就把页目录表的最后一个页目录项中写入页目录表自己的物理地址,相当于在最后,页目录表指向了自己。(所以本系统内核空间的大小是1GB-4MB,因为有一个页目录项被当作指针指向自己用去了)
为了让所有用户进程共享高1GB(实际略小与,有4MB物理空间未做映射)内核空间,就先把内核的256个页目录项写好(第0个和第255个目的都明确了,其余的254个暂时先不用管,先写好占位)。在未来为用户进程创建页表时,把内核(第768~1022)共255个页目录项复制的用户进程页目录表中的相同位置。(1023个指向本身当指针用了,这个不用复制)。