第五步 设计我们的LOADER——开启内存分页机制


查看系列文章点这里: 操作系统真象还原

前言

  现代 CPU 都采用的虚拟内存空间技术,其功能是依靠硬件实现的,但是它所需要的某些数据仍然需要我们进行设置,本章就来看看实现最简单的虚拟内存,我们需要做些什么。


一、如何开启分页机制?

  在进入保护模式之后,我们需要做的第一件事,就是开启分页机制,让操作系统可以使用虚拟地址。开启的方法非常简单,和进入保护一样,分为三步:

  1. 准备好页目录表及页表;
  2. 将页目录表地址写入控制寄存器CR3;
  3. 将寄存器CR0的PG位置1;

  是不是跟进入保护模式的步骤非常相似,第二步就是告诉CPU这些个表在内存的哪里,第三步就是打开分页机制。重点是第一步,如何设计物理内存与虚拟内存直接的映射关系,咱们下一节慢慢说。

  如果大家不了解什么是虚拟内存,什么是分页机制,建议先搜来学习一下,不然可能会云里雾里的。

二、如何设计页目录和页表?

  因为我们是自己设计操作系统,所以在内存管理方面,我们完全可以按照我们自己的想法来做。我们知道一个用户进程必须要有操作系统配合,才能在计算机上运行起来,也就是操作系统必须要在内存中才行,不然用户进程就找不到操作系统。

  我们一共有4GB的虚拟空间,为了简单,我们干脆直接将3GB ~ 4GB这部分,固定划分给操作系统,任何用户进程想要找到操作系统,就直接访问3GB ~ 4GB这段虚拟地址就行。当然,我们自己实现的操作系统远远没有这么大,这么分配完全是为了图个方便。

  在这个基础上,页目录的设计就简单了,将操作系统内核所在的上1GB虚拟内存(3GB~4GB)映射到物理内存0~1GB,这部分很简单不用特别强调,主要有两个地方可以需要特别说明一下:

  第一就是,第0个页目录项与第768个页目录项指向同一个页表,其目的是为了保证 LOADER 在分页机制开启后,仍能正常运行。第二就是最后一个,也就是第1023个目录项,其所对应的内存不是内核,而是页页目录表,这是为了我们修改页目录表。

  其它就没什么了,让我们来实践检验一下!

三、实践检验!

  首先是 boot.inc 中一些新增常量的定义,如下:

------------------------------------
; 页目录表属性
;------------------------------------

; 页目录表的起始地址
PAGE_DIR_TABLE_POS equ 0x100000

;------------------------------------
; 页表属性
;------------------------------------

; present 位 -> 表示页是否存在于内存中
PG_P equ 1b

; 读写位
PG_RW_R equ 00b
PG_RW_W equ 10b

; 用户和根位 -> 是否可以被特权级别为 3 的程序访问
PG_US_S equ 000b
PG_US_U equ 100b

  loader.S 新增的部分如下:

;============================================================
;启动分页模式
;============================================================
    ;第一步:创建页目录表并初始化页内存位图 
    call setup_page

    ;修改视频(显示)内存段的段描述符中的段基址,以反映当前的虚拟地址。
    ;sgdt指令将全局描述符表寄存器(GDTR)的内容存储到指定地址的内存中
    sgdt [gdt_ptr]
    ;gdt_ptr + 2 是 GDT 的地址
    mov ebx, [gdt_ptr+2]
    ;ebx+0x18+4 是第三个段描述符(视频内存段描述符)的高4字节地址
    ;与0xc0000000进行OR操作是为了修改这个段描述符的最高字节,并将视频内存段映射到4GB的上1GB(即内核空间)
    or dword [ebx+0x18+4], 0xc0000000

    ;修改GDT本身的基址
    add dword [gdt_ptr+2], 0xc0000000
    add esp, 0xc0000000

    ;第二步:将PDT的地址放入CR3寄存器
    mov eax, PAGE_DIR_TABLE_POS
    mov cr3, eax

    ;第三步:将CR0寄存器中的pg位(第31位)置为1
    mov eax, cr0
    or eax, 0x80000000
    mov cr0, eax

    ;在开启分页后,用GDT新的地址值重新加载
    lgdt [gdt_ptr]
    mov byte [gs:162], 'V'
    mov byte [gs:0x280], '5'
    mov byte [gs:0x281], 0xA4
    mov byte [gs:0x282], ' '
    mov byte [gs:0x283], 0xA4
    mov byte [gs:0x284], 'O'
    mov byte [gs:0x285], 0xA4
    mov byte [gs:0x286], 'P'
    mov byte [gs:0x287], 0xA4
    mov byte [gs:0x288], 'E'
    mov byte [gs:0x289], 0xA4
    mov byte [gs:0x28a], 'N'
    mov byte [gs:0x28b], 0xA4
    mov byte [gs:0x28c], ' '
    mov byte [gs:0x28d], 0xA4
    mov byte [gs:0x28e], 'P'
    mov byte [gs:0x28f], 0xA4
    mov byte [gs:0x290], 'A'
    mov byte [gs:0x291], 0xA4
    mov byte [gs:0x292], 'G'
    mov byte [gs:0x293], 0xA4
    mov byte [gs:0x294], 'E'
    mov byte [gs:0x295], 0xA4

    jmp $


; ============================================================
; 创建页目录及页表
; ============================================================

;逐字清除占用页面目录的4KB字节
setup_page:
    mov ecx, 4096
    mov esi, 0

.clear_PDT:
    mov byte [PAGE_DIR_TABLE_POS+esi], 0 ;全部初始化为0
    inc esi
    loop .clear_PDT

;------------------------
;创建页目录项(PDE 1、768、1024)
;------------------------
.create_PDE:
    mov eax, PAGE_DIR_TABLE_POS
    ;页目录表(PDT)从0x100000开始,其本身占用0x1000字节。因此,第一个页表位于地址0x101000
    add eax, 0x1000
    mov ebx, eax

    or eax, PG_US_U | PG_RW_W | PG_P
    ;创建第1个页目录项(PDE)
    mov [PAGE_DIR_TABLE_POS + 0x0], eax
    ;创建第768个页目录项(PDE) -> 目的是将虚拟地址3GB (0xc0000000)~3GB+4MB (0xc03fffff)映射到第一个页表,然后将其映射到物理地址0~4MB,即第一个标准页面。
    mov [PAGE_DIR_TABLE_POS + 0xc00], eax

    ;让最后一个页目录项存储PDT的起始地址,为了能动态操作页表
    sub eax, 0x1000
    mov [PAGE_DIR_TABLE_POS+4092], eax

;------------------------
;创建页表项(PTE)
;------------------------
    ;一个完整的页表对应于4MB的物理内存,但内核只需要1MB(256 * 4KB)的空间。因此,首先只创建了256个页表项。
    mov ecx, 256
    mov esi, 0
    mov edx, PG_US_U | PG_RW_W | PG_P

.create_PTE:
    mov [ebx+esi*4], edx
    ;一个页表项对应4KB的物理内存,所以要加4096。
    add edx, 4096
    inc esi
    loop .create_PTE

;------------------------
; 为操作系统内核创建页目录项(PDE 769~1022)
;------------------------
    ;将操作系统内核所在的上1GB虚拟内存(3GB~4GB)映射到物理内存0~1GB。
    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    ;一共创建254 PDEs
    mov esi, 769    ;从第769项开始

.create_kernel_PDE:
    mov [ebx+esi*4], eax
    inc esi
    add eax, 0x1000
    loop .create_kernel_PDE
    ret

  编译、链接、运行!

在这里插入图片描述
  接下来我们来检查一下,我们内存映射是否正确,输入info gdt与info tab查看GDT与内存映射关系,如下图:

在这里插入图片描述
  如果你的结果和我是一样的,那就说明正确啦,大家可以思考一下为什么这样就是正确的的,尤其是内存映射!


  持续更新中~~

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值