操作系统真象还原 --- 5.保护模式进阶,向内核迈进

一、获取物理内存容量

利用BIOS中断的0x15实现获取物理内存容量

  1. EAX=0xE820 遍历主机上全部内存
  2. AX=0xE801 分别检测低15MB和16MB~4GB的内存,最大支持4GB
  3. AH=0x88 最多检测出64MB内存,实际内存超过此容量也返回64MB

二、启用内存分页机制

分页机制寻址原理
二级页表模式:比如虚拟地址:0x1234567

  1. 取虚拟地址高10位,0x4*4+页目录表(PDE)得到页表的物理地址
  2. 取虚拟地址中间10位,0x234*4+页表(PTE)得到页地址
  3. 取虚拟地址最后12位,0x567+页地址得到最终的物理地址
    页目录项、页表项结构

在这里插入图片描述
因为4KB页对齐,所以页目录项和页表项后12位一定为0,可以用来记录页目录,页表相关属性

  • p位:Present,1表示页存在于物理内存中,0表示不在物理内存中。页式虚拟内存管理通过p位和pagefault异常实现
  • RW位:read/write。1表示可读可写,0表示可读不可写
  • US位:User/Supervisor。1表示User级。0表示特权级
  • PWT位:Page-level Write-Through。1表示通写方式,表示该页不仅是普通内存,还是高速缓存。
  • PCD位:Page-level Cache Disable,1表示启用高速缓存,0表示禁止该页的缓存。
  • A位:Accessed,访问位。1表示已经被cpu访问过。
  • D位:Dirty,当cpu对页面执行写操作时,设置D位为1
  • PAT位:page Attribute Table,内存属性
  • G位:Global,1表示是为全局页,在TLB中保存,可直接查询物理地址
  • AVL位:Available位,可用位。没啥用

启动分页机制,按顺序做好三件事

  1. 准备好页目录表及页表
  2. 将页表地址写入控制寄存器cr3
  3. 寄存器cr0的PG位置1,开启页表机制

如何划分页目录表
比较有意思的点是如何划分页目录表
页目录表和页表都在内存中,需要找一个物理地址存放。
页目录表放在0x100000(1MB),那么第一个页表就在0x101000处。
虚拟地址空间划分0-3GB是用户进程,3GB-4GB是操作系统
所以为了实现内核空间(0xc0000000和0x00000000开始的1MB能够直接映射到物理地址的1MB),将PDE的第0和第768 PTE指向同一个页表0x101000
PDE的最后一个PTE指向0x100000(PDE本身,方便对PTE项进行修改)

对第一个页表0x101000进行初始化,使其前256项分别映射低1MB的内容
页表项0=40960|PG_US_U|PG_RW_W|PG_P
页表项1=4096
1|PG_US_U|PG_RW_W|PG_P
页表项255=4096*255|PG_US_U|PG_RW_W|PG_P

对内核其他页表进行初始化
页目录表769=0x102000
页目录表770=0x103000
以此类推
在这里插入图片描述
实际内存映射

  • 0x00000000-0x000fffff被映射到了低1MB空间
  • 0xc0000000-0xc00fffff也被映射到了低1MB空间
    后面三项是由于页目录项1023指向的就是PDE导致的
  • 0xffc00000-0xffc00fff的页表为页目录项0,指向了0x101000-0x101fff的4KB页内
  • 0xfff00000-0xffffefff的页表为页目录项768-1022,它们指向了0x101000-0x10fffff(不知道为什么和书上不太一样)
  • 0xfffff000-0xfffffffff的页表为页目录项1023,指向了0x100000-0x101000
    如果要修改页目录项n只要修改0xfffff000+n*4的值就行
    在这里插入图片描述

三、加载内核

主要讲如何将ELF文件加载到内存中运行。ELF文件格式就不讲了,可以参考其他博客。
如何编译

gcc -m32 -c -o main.o main.c
ld main.o -m elf_i386 -Ttext 0xc0001500 -e main -o kernel.bin
将内核写入硬盘
dd if=kernel.bin of=hd60M.img bs=512 count=200 seek=9 conv=notrunc

虽然设置了-Ttext 0xc0001500但是还是有一个节.note.gnu.propert的虚拟地址是0x080480d4,后面运行mem_cpy会报错
在这里插入图片描述
需要将.note.gnu.property该节去除

strip --remove-section=.note.gnu.property kernel.bin

在这里插入图片描述

四、特权级深入浅出

最难的地方还是在DPL、RPL、CPL上。难理解的原因是:没有遇到从3环到0环的调用的应用程序。所以不好理解为什么要这么做。先简单理解一下,等我真的理解了再回来补吧。

  • CPL:当前进程的特权级别,保存在cs寄存器的低两位
  • RPL:进程对段访问的请求权限,在段选择子中。
  • DPL:存储在段描述符中,规定访问该段的权限级别,每个段的DPL固定
  • 一致性代码段:一致代码段是高特权级的代码共享给低特权级程序使用的,访问时不会更改特权级。
  • 非一致性代码段:避免低特权级访问而保护起来的系统代码,只允许同级访问。

普通跳转(没有经过门调用):jmp或call后面跟着16位段选择子:32位地址偏移。普通跳转不能使特权级发送跃迁,不会引起CPL的变化。

门跳转(任务/中断/陷阱/调用门):jmp或call后面跟着目标段选择子(指向门描述符)偏移没有用(门描述符中记录了代码偏移)。通常用于低特权级代码访问高特权级的代码段。检查还是挺麻烦的。(https://www.cnblogs.com/chenwb89/p/operating_system_004.html)
对call来说:当前CPL<=调用门描述符DPL,RPL<=调用门描述符DPL,当前CPL>=目的代码段描述符DPL;
  对jmp来说:除了跟call的“当前CPL<=调用门描述符DPL,RPL<=调用门描述符DPL”一样外,如果目的代码段的一致的话,CPL>=目的代码段的DPL,而如果目的代码段是非一致的话,CPL=目的代码段的DPL。
  另外,只有call指令可以将代码通过调用门转移到特权级更高的非一致性代码之中。对于非一致性代码的成功转移,CPL被目的代码的DPL刷新,会引起堆栈切换;对于一致性代码,不会刷新,也不会切换。

在这里插入图片描述

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值