操作系统真相还原----第五章页表(2)

启用分页机制

1.为什么要分页
内存碎片,导致利用率不高。
段是连续的,不好换进换出
2.一级页表
(1)分页机制原理
经过段部件处理后,保护模式的寻址空间是4GB,此寻址空间指的是线性地址空间,他在逻辑上是连续的。分页机制的思想是:通过映射,可以使连续的线性地址与任意物理内存地址相关联,逻辑上连续的线性地址对应的物理地址不一定连续。
分页机制打开前要将页表地址加载到控制寄存器 cr3 中,这是启用分页机制的先决条件之一,所以,在打开分页机制前加载到寄存器 cr3 中的是页表的物理地址,页表中页表项的地址自然也是物理地址了
虽然内存分页机制的作用是将虚拟地址转换成物理地址,但其转换过程相当于在关闭分页机制下进行,过程中所涉及到的页表及页表项的寻址,它们的地址都被 CPU 当作最终的物理地址(本来也是物理地址)直接送上地址总线,不会被分页机制再次转换(否则会递归转换下去)。
分页机制作用主要有两方面。
1.将线性地址转换位物理地址
![在这里插入图片描述](https://img-blog.csdnimg.cn/0ec9cbf4a8eb4c02abff0894057649c2.png在这里插入图片描述
2.将大小相等的页代替大小不等的段
在这里插入图片描述
我们所指的虚拟地址就是上面的线性地址。
在内存地址中,最简单的映射方法是逐字节映射,即一个线性地址对应一个物理地址。我们需要找个地方来存储这种映射关系, 这个地方就是页表( PageTable )。页表就是个 列的表格,页表中的每一行(只有一个单元格)称为页表项(PTE ),其大小是 字节,页表项的作用是存储内存物理地址。当访问一个线性地址时,实际上就是在访问页表项中所记录的物理内存地址
显然,这种映射并不划算,将 4GB间划分成 4G 个内存块,每个内存块大小是 宇节。页表也是存储在内存中的,为了表示 32 位地址,每
个页表项必须要 4字节,若按此方案,光是页表就要占 16GB 内存.
因此现在一般,页的大小为4KB。也就是说有1MB的内存块
得注意一下,页是地址空间的计量单位,并不是专属的物理地址或线性地址,只要是4KB的地址空间都可以叫做一页。
在这里插入图片描述
页表存储这个页的起始物理地址。
使用页表
页表地址根据CR3寄存器得出
一个页表项对应一个页,所以,用线性地址的高 20 位作为页表项的索引,每个页表项要占用4 字节
大小,所以这高 20 位的索引乘以 4后才是该页表项相对于页表物理地址的字节偏移量。
在这里插入图片描述
举个例子 0x00001234
这是我们的线性地址 32位对吧
我们一看就能得到 页表中对应的页表项是第0x00001个
而在页面的相对应的偏移位置是 0x234

那直接访问其地址可以通过 第0x10100*4字节
通过cr3中的页表地址 + (0x10100)乘上4 即可得到
3.二级页表
(1)衍生二级页表的原因
(a)一级页表中最多可容纳 IM ( 1048576 )个页表项,每个页表项是 字节,如果页表项全满的话,
便是 画面大小。
(b)一级页表中所有页表项必须要提前建好,原因是操作系统要占用 4GB 虚拟地址空间的高 IGB,
用户进程要占用低 3GB
(c)每个进程都有自己的页表,进程一多,光是页表占用的空间就很可观了
(2)结构
在这里插入图片描述
无论是几级页表,标准页的尺寸都是4KB 固定不变的,这一点是不变的。所以 4GB 线性地址空间最多有 1M标准页 一级页表是将这 1M 个标准页放置到 1张页表中,二级页表是将这 1M 个标准页平均放置 1K页表中,每个页表中包含有 1K 个页表项。这1k个页表存储再页目录表中。无论是页目录项还是页表项其大小都是4字节,因此页表和页目录表都是4KB。
(3)求实际物理地址
(a)用虚拟地址的高10位乘以4,作为页目录表内的偏移地址,加上页目录表的物理地址,所得的和,便是页目录项的物理地址。读取该页目录项,从中获取到页表的物理地址。
(b)用虚拟地址的中间10位乘以4,作为页表内的偏移地址,加上在第1步中得到的页表物理地址,所得的和,便是页表项的物理地址。读取该页表项,从中获取到分配的物理页地址。
(c)虚拟地址的高10位和中间10位分别是PDE和PTE的索引值,所以它们需要乘以4。但低12位就不是索引值啦,其表示的范围是0~~Oxff,作为页内偏移最合适,所以虚拟地址的低12位加上第2步中得到的物理页地址,所得的和便是最终转换的物理地址。
在这里插入图片描述

(4)页表项和页目录项在这里插入图片描述
其高二十位用来表示物理地址,其低12位用来表示属性
P->存在位,是否存在于物理内存
RW->读写位
US->表示用户权限
PWT->意为页级通写位,也称页级写透位 若为 表示此项采用通写方式,表示该页不仅是普通内存,还是高速缓存。
PCD->意为页级高速缓存 止位 若为 表示该页启用高速缓存
A->访问位。若为1表示该页被CPU访问过。
D->脏页位, CPU 个页面执行写操作时,就会设置对应页表项的 位为 此项仅针对页表项有效,并不会修改页目录项中的 位。
G->全局位,若为1表示该页为全局页,会将转化结果存入TLB中
其他位不太用得着,就不介绍了。

不过,真是奇妙呢。明明一个项(32位)正好可以存储一个地址,却分成20(物理地址)+12(属性),低十二位用虚拟地址的一部分表示,说时虚拟地址,却承载了一定的信息。物尽其用!!!

3.启用页表
(1)准备好页目录表及页表。
(2 )将页表地址写入控制寄存器 cr3
(3 )寄存器 cr0的 PG 位置1
cr3寄存器又称页目录基址寄存器
在这里插入图片描述
4.代码

%include "boot.inc"
section loader vstart=LOADER_BASE_ADDR
LOADER_STACK_TOP equ LOADER_BASE_ADDR
jmp loader_start
GDT_BASE: dd 0x00000000
		  dd 0x00000000

CODE_DESC: dd 0x0000FFFF
		   dd DESC_CODE_HIGH4

DATA_STACK_DESC: dd 0x0000FFFF
				 dd DESC_DATA_HIGH4

VIDEO_DESC: dd 0x80000007
			dd DESC_VIDEO_HIGH4

GDT_SIZE equ $ - GDT_BASE
GDT_LIMIT equ GDT_SIZE - 1
times 59 dq 0
times 5 db 0

total_mem_bytes dd 0	; save memory capacity

; gdt's pointer
gdt_ptr dw GDT_LIMIT
		dd GDT_BASE

ards_buf times 244 db 0 ; buffer_size
ards_nr dw 0 ; buffer_num

SELECTOR_CODE equ (0x0001 << 3) + TI_GDT + RPL0
SELECTOR_DATA equ (0x0002 << 3) + TI_GDT + RPL0
SELECTOR_VIDEO equ (0x0003 << 3) + TI_GDT + RPL0
loader_start:
;int 15h eax=0000E820h,edx=534D4150h('SMAP'),获取内存布局
    xor ebx,ebx         ;第一次调用时,ebx为0
    mov edx,0x534d4150  ;edx标志值
    mov di,ards_buf     ;ards结构缓冲区
.e820_mem_get_loop:     ;执行int 0x15,eax值变为上面edx的值
    mov eax,0x0000e820
;所以每次执行int前都要更新为子功能号
    mov ecx,20
    int 0x15
    jc .e820_failed_so_try_e801
;若cf位为1则有错误发生,尝试0xe801子功能
    
    add di,cx           ;使di增加20字节指向缓冲区中的新的ARDS结构位置
    inc word [ards_nr]  ;记录ARDS数量
    cmp ebx,0           ;若ebx为0且cf不为1,这说明ards全部返回
    jnz .e820_mem_get_loop
;在所有ards结构中
;找出(base_add_low + length_low)的最大值,即内存的容量
    mov cx,[ards_nr]
;遍历每一个ARDS结构体,循环次数为ARDS的数目
    mov ebx,ards_buf
    xor edx,edx
.find_max_mem_area:
;无需判断type是否为1,最大内存块一定可被使用
    mov eax,[ebx]
    add eax,[ebx+8]
    add ebx,20
    cmp edx,eax
;冒泡排序,找出最大,edx寄存器始终是最大的内存容量

    jge .next_ards
    mov edx,eax
.next_ards:
    loop .find_max_mem_area
    jmp .mem_get_ok
;E801获取内存大小,最大支持4G
;返回后,ax cx 值一样,以KB为单位,bx dx值一样,以64kb为单位
;在ax和cx寄存器中为低16MB,在bx和dx寄存器中为16MB-4GB
.e820_failed_so_try_e801:
    mov ax,0xe801
    int 0x15
    jc .e801_failed_so_try88    ;若当前e801方法失败,就尝试0x88方法
;1算出低15MB的内存
;ax和cx是以KB为单位的内存数量,将其转换为byte为单位
    mov cx,0x400
    mul cx
    shl edx,16
    and eax,0x000FFFF
    or  edx,eax
    add edx,0x100000
    mov esi,edx
;2 再将16MB以上的内存转换为byte为单位
;寄存器bx和dx中是以64KB为单位的内存数量
    xor eax,eax
    mov ax,bx
    mov ecx,0x10000
    mul ecx

    add esi,eax
;由于此方法只能测出4GB以内的内存,故32位eax足够了
;edx肯定为0,只有eax便可
    mov edx,esi
    jmp .mem_get_ok
.e801_failed_so_try88:
;int 15后,ax存入的是KB为单位的内存容量
    mov ah,0x88
    int 0x15
    jc .error_hlt
    add eax,0x0000FFFF
;16位乘法,被乘数ax,积在32位。积的高16位在dx中,积的低16位在ax中
    mov cx,0x400
    
    mul cx
    shl edx,16
    or edx,eax
    add edx,0x100000
.error_hlt:
    hlt    
.mem_get_ok:
    mov [total_mem_bytes],edx
    

; 1 打开A20 gate
; 2 加载gdt
; 3 将cr0 的 pe位置1
    
    in al,0x92                 ;端口号0x92 中 第1位变成1即可
    or al,0000_0010b
    out 0x92,al
   
    
    lgdt [gdt_ptr]
    
    
    mov eax,cr0                ;cr0寄存器第0位设置位1
    or  eax,0x00000001              
    mov cr0,eax   ;位置为0x0cbe
      
;-------------------------------- 已经打开保护模式 ---------------------------------------
    jmp dword SELECTOR_CODE:p_mode_start                       ;刷新流水线
 
 [bits 32]
 p_mode_start: 
    mov ax,SELECTOR_DATA
    mov ds,ax
    mov es,ax
    mov ss,ax
    mov esp,LOADER_STACK_TOP
    mov ax,SELECTOR_VIDEO
    mov gs,ax

    mov byte [gs:800], 'P'
    
;-----------------------------启动分页--------------------------------------
;创建页目录及页表并初始化页内存位图
   call setup_page
;将描述符表地址及偏移量写入内存gdt_ptr,一会用新地址重新加载
   sgdt [gdt_ptr]
;将gdt描述符中视频段描述符中的段基址+0xc0000000
   mov ebx,[gdt_ptr+2]
   or dword [ebx+0x18+4],0xc0000000
;视频段是第三段描述符,每段描述符是8字节,故0x18
;段描述符的高四字节的最高位是段基址的31-24;将gdt的基址加上0xc0000000使其成为内核所在的高地址p
   add dword[gdt_ptr+2],0xc0000000
   add esp,0xc0000000       ;栈指针同样映射到内核地址
;将页目录地址赋给cr3
   mov eax,PAGE_DIR_TABLE_POS
   mov cr3,eax
;打开cr0的pg位
   mov eax,cr0
   or eax,0x80000000
   mov cr0,eax
;开启分页后,用gdt的新地址重新加载
   lgdt [gdt_ptr]    ;重新
   
   mov byte [gs:880],'V'

   jmp $
;------------------------创建页目录及页表---------------------------------
setup_page:
;先把页目录所占用的空间字节清0
    mov ecx,4096
    mov esi,0
.clear_page_dir:
    mov byte [PAGE_DIR_TABLE_POS + esi],0
    inc esi
    loop .clear_page_dir
;开始创建页目录项
.create_pde:
    mov eax,PAGE_DIR_TABLE_POS
    add eax,0x1000 ;eax为第一个页表·位置以及属性
    mov ebx,eax   ;.vreate_pte做准备,ebx为基址
;下面将页目录项00xc00都存为第一个页表的地址,每个页表表示4M
;这样0xc03fffff以下的地址和0x003fffff以下的地址都指向相同的页表
;这是为将地址映射为内核地址做准备
    or eax,PG_US_U | PG_RW_W | PG_P
;页目录项的属性RW和P位为1,US为1,表示用户属性,所有特权级别都可以访问
    mov [PAGE_DIR_TABLE_POS + 0x0],eax ;第一个目录项
    ;将页目录表中的第一个目录项写入第一个页表位置及属性
    
    mov [PAGE_DIR_TABLE_POS + 0xc00],eax ;0xc00表示第768个页表占用的目录项,0xc00以上的目录项用于内核空间
    ;也就是页表的0xc0000000-0xffffffff1G属于内核
    sub eax,0x1000
    mov [PAGE_DIR_TABLE_POS + 4092],eax
;使最后一个目录项指向页目录表自己的地址
;下面创建页表项
    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
;创建内核其他的页表的PDE
    mov eax,PAGE_DIR_TABLE_POS
    add eax,0x2000          ;此时eax为第二个页表的位置
    or eax,PG_US_U|PG_RW_W|PG_P         ;页目录项的属性
    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
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值