自制操作系统-分页模式

分页模式

1. 为什么需要分页

  • 分页模式可以更大限度的利用内存空间,每个进程有自己连续的虚拟地址但是对应的物理地址可以不连续, 这样分页就相当于做了一个虚拟地址和物理地址的映射
  • 不使用分页的情况会导致当内存空间不连续时程序无法利用, 如当前系统还有两块儿离散的内存,一块儿10M另外一块儿5M, 但是当前有一个程序需要12M的内存,这种情况系统就无法分配内存给程序,只能让程序继续等待

2. 分页需要准备什么?

  • 本次实现采用了二级页表的方式
    • 页目录1024个: 起始地址0x100000
    • 页表1024个:起始地址0x101000, 一个页表可以表示4M的空间(1024 * 4K)
  • 虚拟内存规划
    • 3G以上空间留给操作系统, 也就是0xc000 0000以上的空间给OS
    • 3G以下的空间留给用户
  • 实际内存规划
    • 1M以内的内存留给OS使用, 也就是虚拟地址3G以上的空间需要映射到此处
  • 全局描述符表基地址改变
    • 此前基地址时直接为0x0, 但是目前规划OS在3G以上的空间所以基地址应该是0xc000 0000,这样开启分页之后才能正确的映射到1M以内我们定义的GDT地址上
    • 显存段的段描述符中的基地址需要加上0xc000 0000
  • 为什么第一个页目录也分配给了OS
    • 由于当前Loader运行地址任然处于1M内的内存为了保证开启分页后Loader能继续正常运行所以需要将页目录的第0个目录映射到和OS的第一个页表相同位置0x101000
    • 列如:当前访问0x900的代码,那么CS的值为0x0008, IP的值为0x900
      • 第一步根据前12位确定GDT描述符,可以知道是第一个CODE描述符
      • 第二步计算得到基地址0x0, 偏移地址0x900
      • 第三步得到虚拟地址, 0x 0000 0900
      • 第四步根据前十位得到页目录项为0
      • 第五步根据0号页目录得到页表地址0x101000
      • 第六步根据中间十位得到具体的页表项为0
      • 第七步最终得到实际物理地址为0
      • 第八步加上偏移地址得最终访问地址0x900

3. 准备页表及页目录

  • 公共信息准备(添加到公共配置文件common_info.conf中)
PG_P  equ   1b
PG_RW_R	 equ  00b 
PG_RW_W	 equ  10b 
PG_US_S	 equ  000b 
PG_US_U	 equ  100b 

PAGE_DIR_TABLE_POS equ 0x100000
  • 创建页表及页目录代码(存放在Loader中)
;-------------   创建页目录及页表   ---------------
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

;开始创建页目录项(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都存为第一个页表的地址,
;   一个页表可表示4MB内存,这样0xc03fffff以下的地址和0x003fffff以下的地址都指向相同的页表,
;   这是为将地址映射为内核地址做准备
   or eax, PG_US_U | PG_RW_W | PG_P	     ; 页目录项的属性RW和P位为1,US为1,表示用户属性,所有特权级别都可以访问.
   mov [PAGE_DIR_TABLE_POS + 0x0], eax       ; 第1个目录项,在页目录表中的第1个目录项写入第一个页表的位置(0x101000)及属性(7)
   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	     ; 使最后一个目录项指向页目录表自己的地址

;下面创建页表项(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

;创建内核其它页表的PDE
   mov eax, PAGE_DIR_TABLE_POS
   add eax, 0x2000 		     ; 此时eax为第二个页表的位置
   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

4. Loader 完整代码

  • 目前位置页目录初始化了第0个以及768-1024后的所有页目录,注意如果需要访问页目录本身实际上只需要访问最后一个页目录即可
%include "common_info.conf"
section loader vstart=LOADER_BASE_ADDR
LOADER_STACK_TOP equ LOADER_BASE_ADDR
jmp loader_start					
   
;构建gdt及其内部的描述符
   GDT_BASE:   	dd    0x00000000 
	       		dd    0x00000000

   CODE_DESC:  	dd    0x0000FFFF 
	       		dd    DESC_CODE_HIGH4

   DATA_STACK_DESC:  	dd    0x0000FFFF
		     			dd    DESC_DATA_HIGH4
;因为单位是4K, 所以limit=(0xbffff-0xb8000)/4k=0x7
   VIDEO_DESC: 	dd    0x80000007	       
	       		dd    DESC_VIDEO_HIGH4  

   GDT_SIZE   equ   $ - GDT_BASE
   GDT_LIMIT   equ   GDT_SIZE -	1 
   times 10 dq 0
   ; 相当于(CODE_DESC - GDT_BASE)/8 + TI_GDT + RPL0
   SELECTOR_CODE equ (0x0001<<3) + TI_GDT + RPL0         
   SELECTOR_DATA equ (0x0002<<3) + TI_GDT + RPL0	 ; 同上
   SELECTOR_VIDEO equ (0x0003<<3) + TI_GDT + RPL0	 ; 同上 

   ;以下是定义gdt的指针,前2字节是gdt界限,后4字节是gdt起始地址
   gdt_ptr  dw  GDT_LIMIT 
	    	dd  GDT_BASE


loader_start:
    mov	 sp, LOADER_BASE_ADDR
    mov byte [gs:0xa0],'L'
    mov byte [gs:0xa1],0xA4   

    mov byte [gs:0xa2],'O'
    mov byte [gs:0xa3],0xA4

    mov byte [gs:0xa4],'A'
    mov byte [gs:0xa5],0xA4

    mov byte [gs:0xa6],'D'
    mov byte [gs:0xa7],0xA4

    mov byte [gs:0xa8],'E'
    mov byte [gs:0xa9],0xA4

    mov byte [gs:0xaa],'R'
    mov byte [gs:0xab],0xA4

;----------------------------------------   准备进入保护模式   -----------------------------
;1 打开A20
;2 加载gdt
;3 将cr0的pe位置1
   ;-----------------  打开A20  ----------------
   in al,0x92
   or al,0000_0010B
   out 0x92,al
   ;-----------------  加载GDT  ----------------
   lgdt [gdt_ptr]
   ;-----------------  cr0第0位置1  ----------------
   mov eax, cr0
   or eax, 0x00000001
   mov cr0, eax

   jmp  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

; 创建页目录及页表并初始化页内存位图
   call setup_page

   ;要将描述符表地址及偏移量写入内存gdt_ptr,一会用新地址重新加载
   sgdt [gdt_ptr]	      ; 存储到原来gdt所有的位置

   ;将gdt描述符中视频段描述符中的段基址+0xc0000000
   mov ebx, [gdt_ptr + 2]  
   or dword [ebx + 0x18 + 4], 0xc0000000      ;视频段是第3个段描述符,每个描述符是8字节,故0x18。
					      ;段描述符的高4字节的最高位是段基址的31~24位

   ;将gdt的基址加上0xc0000000使其成为内核所在的高地址
   add dword [gdt_ptr + 2], 0xc0000000

   add esp, 0xc0000000        ; 将栈指针同样映射到内核地址

   ; 把页目录地址赋给cr3
   mov eax, PAGE_DIR_TABLE_POS
   mov cr3, eax

   ; 打开cr0的pg位(第31位)
   mov eax, cr0
   or eax, 0x80000000
   mov cr0, eax

   ;在开启分页后,用gdt新的地址重新加载
   lgdt [gdt_ptr]             ; 重新加载

   mov byte [gs:320], 'O'
   mov byte [gs:322], 'K'

   hlt

;-------------   创建页目录及页表   ---------------
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

;开始创建页目录项(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都存为第一个页表的地址,
;   一个页表可表示4MB内存,这样0xc03fffff以下的地址和0x003fffff以下的地址都指向相同的页表,
;   这是为将地址映射为内核地址做准备
   or eax, PG_US_U | PG_RW_W | PG_P	     ; 页目录项的属性RW和P位为1,US为1,表示用户属性,所有特权级别都可以访问.
   mov [PAGE_DIR_TABLE_POS + 0x0], eax       ; 第1个目录项,在页目录表中的第1个目录项写入第一个页表的位置(0x101000)及属性(7)
   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	     ; 使最后一个目录项指向页目录表自己的地址

;下面创建页表项(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

;创建内核其它页表的PDE
   mov eax, PAGE_DIR_TABLE_POS
   add eax, 0x2000 		     ; 此时eax为第二个页表的位置
   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

5. 效果

  • 打印效果

在这里插入图片描述

  • 同过info tab查看分页表

在这里插入图片描述

  • 5
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值