进入保护模式
保护模式是在loader.bin
中进入的,除了要更新loader.S
之外,还应该更新两个文件:mbr.S
和include/boot.inc
由于loader.bin
超过了512字节,所以要把mbr.S
中加载loader.bin
(loader.bin
由mbr.bin
中的函数rd_disk_m_16
读入)的读入扇区增大,由1扇区增加到4扇区,待读入的扇区数(cx)应该大于loader.bin
的大小
1 | ;在mbr.S中修改 |
mbr.S
完整程序代码
1 | ;主引导程序 |
include/boot.inc
1 | ;------------- loader和kernel ---------- |
equ
是nasm的伪指令,即equal。符号名采用DESC_字段名_字段相关信息
的形式,DESC_G_4K
是4K粒度
loader.S
是关键
1 | %include "boot.inc" |
在bochs中,控制寄存器和状态寄存器的相应位为1,bochs会用大写,0对应小写
文件相关:
创建文件:
touch a.txt
创建文件夹:
mkdir
NewFolder
删除文件:
rm
a.txt
删除文件夹:
rmdir
NewFolder
删除带有文件的文件夹:
rm -r NewFolder
编译:
nasm -I include/ -o mbr.bin mbr.S
nasm -I include/ -o loader.bin loader.S
写入硬盘
dd if=/root/bochs-2.7/mbr.bin of=/root/bochs-2.7/hd60M.img bs=512 count=1 conv=notrunc
dd if=/root/bochs-2.7/loader.bin of=/root/bochs-2.7/hd60M.img bs=512 count=4 seek=2 conv=notrunc
!!! count=4 不是 1
在书中会显示一些细节信息:creg
,info gdt
等,而bochs一直在循环过程中,要想结束循环,键盘Ctrl+c
可中止程序进行查看详细信息。
处理器微架构
流水线
指令执行单元EU是执行指令的唯一部件,一次只能执行一个指令,在单核CPU的情况下,只有一个指令处于执行中。
CPU的指令执行过程分为取指令、译码、执行三个步骤,每个步骤都是独立的,CPU是按照程序中指令顺序来填充流水线的,也就是按照PC寄存器里的值来装载流水线,如果有jmp指令,后面的就没有用了,所以CPU在遇到无条件的转移指令jmp时会清空流水线。
乱序执行
指CPU运行的指令并不按照代码中的顺序执行,而是按照一定的策略打乱,也许后面的指令先执行,得保证指令之间不具备相关性。
X86最初使用CISC指令集(complex instruction set computer)复杂指令集计算机,与之相对的是RISC(reduced…)精简指令集计算机
缓存
CPU中的L1、L2、L3级缓存,都被称为SRAM,静态随机访问存储器。
为程序的局部性采取缓存原理:时间局部性(最近访问过的指令和数据)、空间局部性(靠近当前访问内存空间的内存地址)。
对于无条件跳转,没有什么,所谓的预测是针对有条件的跳转,最简单的统计是根据上一次跳转的结果来预测本次;最简单的方法是2位预测法,用2位bit的计数器来记录跳转状态,每跳转一次加1,直到3就不加了,如果未跳转减1,减到0停止,如果计数器大于1则跳转,小于等于1不跳。
静态预测器策略:向上跳转则转移会发生,向下跳转则转移不发生。
如果分支预测错误,只要清空流水线就行,只是代价较大
使用远跳转指令清空流水线
上述代码中的无条件跳转指令
jmp dword SELECTOR_CODE:p_mode_start
为什么要用jmp远转移
- 段描述符缓冲寄存器未更新,之前还是实模式下的值,进入保护模式后要填入正确的信息
- 流水线中指令译码错误
内存段的保护
段描述符的属性字段中,每个字段都不是多余的,这些属性是用来描述一块内存的性质,给CPU参考,当有实际动作在这片内存上发生时,CPU用这些属性检查动作的合法性
向段寄存器加载选择子时的保护
- 首先验证段描述符是否超过界限:描述符表基地址+选择子的索引值*8+7 <= 描述符表基地址+描述符表界限值
- 检查段寄存器的用途与段类型是否匹配
- 检查内存段是否存在,P位为1则存在,若为0说明转储到硬盘上了,这时会抛出异常,等加载到内存中,P变为1
代码段和数据段的保护
CPU每访问一个地址,都要确认该地址不能超过内存段范围,实际的段界限值:(描述符中段界限+1)*(段界限粒度:4K或1)-1
对于代码段,段中的数据是各种指令,使用CS:EIP访问指令的起始地址,满足:EIP中的偏移地址+指令长度-1 <= 实际段界限大小
栈段的保护
将段界限地址+1视为栈可以访问的下限