文章目录
一、保护模式概述
1.实模式的缺点
- 实模式下操作系统和用户程序是属于同一特权级
- 用户程序所引用的地址都是指向真实的物理地址,即逻辑地址=物理地址
- 用户程序可以自由修改段基址
- 访问超过64KB的内存区域要切换段基址
- 一次只能运行一个程序
- 共20条地址线,最大可用内存为1MB
2.保护模式答疑
- 我们所说的实模式是指32位CPU运行在实模式下的状态,不是指CPU就是16位的。
- 在保护模式下,程序的地址(虚拟地址)要被转化为物理地址再去寻址。
- 地址转换是由处理器和操作系统共同协作完成的,处理器在硬件上提供地址转换部件,操作系统提供转换过程中所需的页表。
- 进入保护模式的步骤:
- 打开A20
- 加载gdt
- 将cr0的pe位置1
3.寄存器拓展
- 最大寻址范围4GB,寄存器宽度拓展为32位
- 除段寄存器16位外,其他的通用寄存器、指令指针寄存器和标志寄存器都由原来的16位扩展为32位。
二、初见保护模式
1. 全局描述符表
- 每一个表项为段描述符,其大小为64字节,用来描述各个内存段的起始地址、大小、权限等信息。
- 段描述符在内存中,访问内存对于CPU来说效率不高
- 段描述符的格式很奇怪,一个数据要放在三个地方存,所以CPU要将这些七零八落的数拼合成一个完整数据要花费时间
- 段寄存器中保存的不再是段基址,而是选择子selector,类似于数组下标。
2. 段描述符缓冲寄存器
- 为了提高获取信息的效率,对段寄存器率先采用缓存技术——段描述符缓冲寄存器,CPU每次将获取到的内存段信息整理成完整形式后,便存入段描述符缓冲寄存器中。
- 段描述符缓冲寄存器并不是保护模式专属,在实模式也可用。例如将段基址+段内偏移地址放入该寄存器中,方便下次使用。
- 失效时间:理论上,只要往段寄存器中赋值,CPU就会更新段描述符缓冲寄存器。
3.寻址扩展
属性 | 实模式 | 保护模式 |
---|---|---|
基址寄存器 | bx bp | 所有32位通用寄存器 |
变址寄存器 | si di | 除esp之外的所有32位通用寄存器 |
段寄存器 | 除了bp的默认段寄存器是ss,常用于访问栈外,其他寄存器的默认段寄存器皆为ds,常用于访问数据段 | ds |
偏移量 | 16位 | 32位 |
比例因子 | 无 | 1、2、4、8 |
- 在基址寻址中,有效地址(即偏移地址)由两部分组成。一部分存放在基址寄存器中,另一部分为常量 。
- 在变址寻址中,有效地址(即偏移地址)由两部分组成。一部分存放在变址寄存器中(可能这部分还要乘以一个系数),另一部分为常量 。
变址寻址与基址寻址的不同,就在于偏移地址的计算更复杂了点。此时 CPU 不去基址寄存器,而是去变址寄存器。
//实模式:
mov ax,[si] //去内存[ds:si]中取数
mov ax,[di] //去内存[ds:di]中取数
mov ax,[bx] //去内存[ds:bx]中取数
mov ax,[bx+si] //去内存[ds:bx+xi]中取数
mov ax,[bx+si+0x1234] //去内存[ds:bx+si+0x1234]中取数
mov ax,[bx+di] //去内存[ds:bx+di]中取数
mov ax,[bx+di+0x1234] //去内存[ds:bx+di+0x1234]中取数
//保护模式:段寄存器默认为ds
mov eax,[eax+edx*8+0x12345678] //去内存[ds:eax+edx*8+0x12345678]中取数
mov eax,[eax+edx*2+0x8] //去内存[ds:eax+edx*2+0x8]中取数
mov eax,[ecx*4+0x1234] //去内存[ds:ecx*4+0x1234]中取数
//esp虽然无法用作变址寄存器,但可以用作基址寄存器
mov eax,[esp] //去内存[ds:esp]中取数
mov eax,[esp+2] //去内存[ds:esp+2]中取数
4.运行模式反转
-
在32位CPU中有实模式和保护模式两种模式
-
伪指令bits:
- 用来向编译器传达:我下面的指令要编译成xx位,因为我知道下面的代码的运行环境是xx位。即用于指定运行模式
- 指令格式:bits 16/32
- 在未使用bits指令的地方,默认是bits 16
-
操作数大小反转前缀0x66(编译时显示)
- 若当前运行模式是16位,则操作数大小将变成32位
- 若当前运行模式是32位,则操作数大小将变成16位
bits 16 mov ax,0x1234 mov eax,0x1234 bits 32 mov ax,0x1234 mov eax,0x1234
- 寻址方式反转前缀0x67(编译时显示)
不仅可以使用对方模型的操作数,还可以使用对方模式下的寻址方式bits 16 mov word [bx],0x1234 mov word [eax],0x1234 //eax用到了32位寻址方式->0x67 mov dword [eax],0x1234 //eax用到了32位寻址方式->0x67 && dword表示一次读写4字节,操作数大小为4字节->0x66 bits 32 mov dword [eax],0x1234 mov word [eax],0x1234 //word表示一次读写2字节,改变操作数大小->0x66 mov dword [bx],0x1234 //bx用到了16位寻址方式->0x67
- 操作数0x66和0x67用于临时将当前运行模式下的操作数大小和寻址方式转变成另外一种模式下的操作数大小和寻址方式。
三、指令扩展
1.乘除运算
- mul
- 无符号数相乘指令
- 指令格式:mul 寄存器/内存(乘数)
- 若乘数是8/6/32位,则把寄存器al /ax /eax当作另一个乘数,结果存入ax/ eax/ edx:eax (高32位:低32位)中
- imul 有符号数相乘指令,和mul一样用法
- div
- 无符号数除法指令
- 指令格式:div 寄存器/内存(除数)
- ①如果除数是8位,被除数就是16位,位于ax中;结果的商放在al中,余数放入ah中
②如果除数是16位,被除数就是32位,位于dx和ax(高16位,低16位)中;结果的商放在ax中,余数放入dx中
③如果除数是32位,被除数就是64位,位于edx和eax(高32位,低32位)中;结果的商放在eax中,余数放入edx中
2. push
- push 立即数
- 8位立即数:
- 因为寄存器宽度是16/32位,所以在实模式下8位立即数会被扩展为16位,保护模式下会被扩展为32位再入栈。
- 在实模式下:
- 当压入8位立即数时,会被扩展为16位后入栈,sp-2【sp为栈指针寄存器】
- 当压入16位立即数时,直接入栈,sp-2
- 当压入32位立即数时,直接入栈,sp-4
- 在保护模式下:
- 当压入8位立即数时,会被扩展为32位后入栈,sp-4【sp为栈指针寄存器】
- 当压入16位立即数时,直接入栈,sp-2
- 当压入32位立即数时,直接入栈,sp-4
- 16位立即数
- 32位立即数
- 8位立即数:
- push 寄存器和内存
- 对于段寄存器的入栈,即cs、ds、es、fs、gs、ss,无论在哪种模式下,都是按照当前模式的操作数大小入栈的。
section loader vstart=0x900 mov sp,0x900 push cs //实模式下sp-2;保护模式下sp-4 push ds push es jmp $
- 对于通用寄存器和内存,无论在实模式还是保护模式下
- 如果压入16位数据,栈指针-2
- 如果压入32位数据,栈指针-4
section loader vstart=0x900 mov sp,0x900 push ax //sp-2 实模式和保护模式都是一样 push eax //sp-4 push word [0x1234] //sp-2 push dword [0x1234] //sp-4 jmp $
四、全局描述符表GDT
- 全局描述符表是保护模式下的内存段登记表
1.段描述符
- CPU工程师既要保证保护模式下的内存寻址依然是"段基址:段内偏移地址"的形式,又要有效提高安全性
- 保护模式下内存寻址需要哪些属性:
实模式下内存寻址存在的问题 | 保护模式下应该做出的改善 |
---|---|
用户进程可以存储代码的内存区域 | 添加"内存段类型"属性 |
用户进程操作系统是同一级别的 | 添加"特权级"属性 |
- 内存段是一片内存区域,访问内存就要提供段基址属性
- 为了限制程序访问内存的范围,要对段大小进行约束,所以要有段界限属性
- 段描述符属性
-
段描述符是连续的8字节大小
-
保护模式下的总线宽度是32位,段基址需要用32位地址来表示
-
段界限:段内偏移的最值
- 代码段和数据段:段向上扩展,地址越来越高,表示段内偏移的最大值.
- 栈段:向下扩展,地址越来越低,表示段内偏移的最小值
- 单位(粒度大小) : 字节/4KB
- 段界限 : 从0开始计数
- 段界限的边界值 = (段描述符+1) * 粒度大小(字节/4KB) - 1
- "-1"是因为是从0开始编号的
- 在"段基址:段内偏移地址"中,段界限是用来限制段内偏移地址的,若段内偏移地址 > 段界限,则CPU会抛出异常
-
G位 : 粒度,G = 0 表示段界限粒度大小为1字节;G=1 表示段界限粒度大小为4KB
-
S位 : 段描述符有两大类,系统段和数据段,S = 0表示系统段;S = 1表示数据段
在CPU眼中,凡是硬件运行需要用到的东西都称为系统
在CPU眼中,凡是软件需要的东西都称为数据,包括代码,数据,栈 -
type
- 指定本描述符的类型,当S位确定后,type才有意义
- A : Accessed,当该段被CPU访问过后,CPU将其置1,当创建一个新段描述符时,应该将其置0
- C : Conforming,一致性代码.如果自己是转移的目标段,并且自己是一致性代码段,那么自己的特权级一定要高于当前特权级,转移后的特权级与转移前的低特权值一致.C=1表示是一致性代码
- R : 可读,R = 1表示可读,R = 0表示不可读
- X : Executable 可执行. X = 1表示代码可执行
-
DPL : descriptor privilege level 描述特权级,将计算机按照不同权力划分为4种特权级,数字越小,特权级越大. 当CPU由实模式进入保护模式后,特权级 为0;用户模式通常为3特权级
-
P位 : present 表示段是否存在.如果段存在于内存中,P=1.[当段在内存与硬盘中换入换出时有用处,当保护模式开启分页后,可以按照页来换入换出]
-
AVL : 可用的
-
L位 : 是否是64位代码段,L=1表示是64位 ; L=0表示是32位
-
D/B位 : 用来指定有效地址(段内偏移地址)及操作数的大小
对于代码段来说,此时是D位,D = 0表示指令中的有效地址和操作数都是16位,指令的有效地址用IP寄存器
-
段属性 | D/B | 意义 | 使用的寄存器 |
---|---|---|---|
代码段 | D | 0表示有效地址和操作数是16位 | 有效地址使用IP寄存器 |
代码段 | D | 1表示有效地址和操作数是32位 | 有效地址EIP寄存器 |
栈段 | B | 0表示操作数是16位 | 栈指针使用sp寄存器 |
栈段 | B | 1表示操作数是32位 | 栈指针使用esp寄存器 |
2.全局描述符表GDT,局部描述符表LDT和选择子
-
GDT : 描述符的数组,数组中的每个元素是一个8字节的描述符
-
GDT中第0个段描述符不可用,要用于表示未初始化的段描述符
-
GDTR
- 定义 : GDT的指针,用来存储GDT的内存地址及大小
- 构成 : GDTR是个48位的寄存器,前16位是GDT以字节为单位的界限值,相当于GDT的字节大小 - 1,后32位是GDT的起始地址
- 访问GDTR : lgdt 48位内存数据
-
选择子
- 定义 : 提供下标在GDT中索引描述符
- 大小 : 因为段寄存器是16位,所以选择子也是16位
- RPL : 请求特权级,有0 1 2 3四种特权级
- TI位 : 用来指示选择子是在GDT中还是LDT中索引描述符
-
保护模式下CPU寻址过程 : 段基址在段描述符中,用选择子索引出段描述符,CPU自动从段描述符中取出段基址,再加上段内偏移地址去寻址
-
由于保护模式是32位寄存器,所以不需要"段基址左移4位+段内偏移地址"来寻址,直接用"段基址+段内偏移地址"就是要访问的内存
-
LDT : 局部描述符表,在现代很少用到
3.打开A20地址线
- 实模式下地址回绕
- 在实模式下理论最大可寻址地址为0xFFFF0+0xFFFF=0x10FFEF,超过了实际最大地址0xFFFFF。
- 由于没有A20地址线,所以理论超出部分会自动绕回0地址
- 保护模式下的A20开关:将端口0x92的第一位置1
in al,0x92 or al,0000_0010B out 0x92,al
4.保护模式开关
- 控制寄存器CRx:控制寄存器既可以用来展示CPU的内部状态,又可以控制CPU的运行机制。
- PE位(Protection Enable)
- CR0寄存器的第0位
- 作用:用于开启保护模式
mov eax,cr0 or eax,0000_0001 mov cr0.eax
五、清空流水线
- 流水线:是CPU为了提高执行效率而采用的一种工作方式,CPU将当前指令及后面几条指令同时放在流水线中重叠执行。
- 当当前指令既有16位,又有32位时,需要清空流水线,不然将32位指令按照16位编译会出错。
六、保护模式之内存段的保护
1.向段寄存器加载选择子时的保护
为避免非法引用内存段的情况,处理器会做以下几方面的检查:
- 根据选择子的值验证段描述符是否越界
- 选择子的索引值要 <= GDT中描述符的个数
- 因为段描述符是8字节,所以“GDT地址 + 选择子中的索引值*8 + 7 <= GDT基地址 + GDT界限值”
- GDT中的第0个选择子是空描述符,不允许向cs和ss段寄存器中加载0号选择子。虽然可以向ds、es、fs、gs中加载0号选择子,但真正使用时CPU会报错。
- 在选择子后,要通过type字段检查段的类型:
具备属性 | 可加载到的段寄存器 |
---|---|
可执行属性(代码段)[表示当前可执行此段] | cs |
只具备执行属性的段(代码段) | 除cs外的段寄存器 |
可写属性(数据段) | ss栈段寄存器 |
可读属性 | ds、es、fs、gs |
- 通过p位检查内存段是否存在
- P = 1:将选择子载入段寄存器,将段描述符缓冲寄存器更新为该选择子对应段描述符的内容。CPU将段描述符的A位置1,表示该段已经访问过。
- P = 0:该内存段不存在(不在内存中),CPU抛出异常。自动转去执行相应的异常处理程序。异常处理程序将该段从硬盘加载到内存中,然后将P位置1,随后返回,CPU继续执行刚才的操作:判断P位。
2.代码段和数据段的保护
- 对于代码段和数据段而言,CPU每访问一个地址,都要确认该地址不能超过所在内存段的范围。
- 实际段界限的值为:(描述符中的段界限+1)*(段界限的粒度大小:4k/1)-1
- 若粒度=1:段界限 = 描述符中的段界限
- 若粒度=4k:段界限 = 描述符中的段界限*0x1000+0xFFF
- CS:EIP只是指令的起始地址,指令本身也是有长度的。“EIP中的偏移地址+指令长度 <= 实际段界限大小”
- 数据段也一样,“偏移地址+数据长度-1 <= 实际段界限大小”
- 例:假设数据段描述符的段界限为0x12345,段基址为0x0000_0000
若G=0,则实际段界限为0x12345。若G=1,则实际段界限为0x12345*0x1000+0xFFF = 0x12345FFF。如果访问的数据地址是0x12345FFF,则要看访问的数据宽度:- 若数据大小为1字节,如mov ax,byte [0x12345FFF],则内存操作没有问题,数据完全在实际段界限之内
- 若数据大小为2字节,如mov ax,word [0x12345FFF],则内存操作超出实际段界限,数据所在地址为0x12345FFF和0x12346000这两个字节中,CPU抛出异常。
3.栈段的保护
- 栈段的向上下扩展:
- 对于向上扩展的段,实际的段界限是段内可以访问的最后一字节
- 对于向下扩展的段,实际的段界限是段内不可以访问的第一个字节
- 栈的默认扩展方向: 高地址 -> 低地址
- 为避免碰撞,将段界限+1视为栈可以访问的下限。段界限+1才是栈指针可达的下边界。
- 由于压栈的操作数是有长度的,所以“实际段界限+1 <= esp-操作数大小 <= 栈指针最大可访问地址”
- 例:架设esp的指针为0xFFFF_E002,段描述符G位是1,描述符中的段界限为0xFFFFD。所以实际段界限为:0xFFFFD*0x1000+0xFFF = 0xFFFFDFFF。
- 当执行push ax时,压入2字节的操作数,即esp-2=0xFFFF_E002-0x2=0xFFFF_E000,新的esp的值 >= 实际段界限0xFFFF_DFFF+1。
- 当执行push eax时,压入4字节数据,esp-4=0xFFFF_DFFE,小于实际段界限0xFFFF_DFFF,故CPU会抛出异常。
---------------------代码---------------------
- “/home/lily/OS/boot/include/boot.inc”
;------------- loader和kernel ----------
; loader 在内存中的位置
LOADER_BASE_ADDR equ 0x900
; loader 在硬盘上的逻辑扇区地址(LBA)
LOADER_START_SECTOR equ 0x2
;-------------- gdt描述符属性 -------------
; 查下划线的作用 其实没有任何作用 这里仅仅为了方便 确定哪些位为我们想要设置数而专门用的下划线分割
DESC_G_4K equ 1_00000000000000000000000b ; 第23位G 表示4K或者1MB位 段界限的单位值 此时为1则为4k
DESC_D_32 equ 1_0000000000000000000000b ; 第22位D/B位 表示地址值用32位EIP寄存器 操作数与指令码32位
DESC_L equ 0_000000000000000000000b ; 第21位 设置成0表示不设置成64位代码段 忽略
DESC_AVL equ 0_00000000000000000000b ; 第20位 是软件可用的 操作系统额外提供的 可不设置
DESC_LIMIT_CODE2 equ 1111_0000000000000000b ; 第16-19位 段界限的最后四位 全部初始化为1 因为最大段界限*粒度必须等于0xffffffff
DESC_LIMIT_DATA2 equ DESC_LIMIT_CODE2 ; 数据段与代码段段界限相同
DESC_LIMIT_VIDEO2 equ 0000_0000000000000000b ; 第16-19位 显存区描述符 VIDEO2 这里的全是0为高位 低位即可表示段基址
DESC_P equ 1_000000000000000b ; 第15位 P present判断段是否存在于内存
DESC_DPL_0 equ 00_0000000000000b ; 第13-14位 Privilege Level 0-3
DESC_DPL_1 equ 01_0000000000000b ; 0为操作系统 权力最高 3为用户段 用于保护
DESC_DPL_2 equ 10_0000000000000b
DESC_DPL_3 equ 11_0000000000000b
DESC_S_sys equ 0_000000000000b ; 第12位为0 则表示系统段 为1则表示数据段
DESC_S_CODE equ 1_000000000000b ; 第12位与type字段结合 判断是否为系统段还是数据段
DESC_S_DATA equ DESC_S_CODE
;x=1 e=0 w=0 a=0
DESC_TYPE_CODE equ 1000_00000000b ; 第9-11位表示该段状态 1000 可执行 不允许可读 已访问位0
;x=0 e=0 w=1 a=0
DESC_TYPE_DATA equ 0010_00000000b ; 第9-11位type段 0010 可写
; 代码段描述符高位4字节初始化 (0x00共8位 <<24 共32位初始化0), 0x00 代表16进制, 一个十六进制可表示4个二进制位, 0x00共8位 8+24=36位
; 4KB为单位 Data段32位操作数 初始化的部分段界限 最高权限操作系统代码段 P存在表示 状态
DESC_CODE_HIGH4 equ (0x00<<24) + DESC_G_4K + DESC_D_32 + \
DESC_L + DESC_AVL + DESC_LIMIT_CODE2 + \
DESC_P + DESC_DPL_0 + DESC_S_CODE + DESC_TYPE_CODE + 0x00
; 数据段描述符高位4字节初始化
DESC_DATA_HIGH4 equ (0x00<<24) + DESC_G_4K + DESC_D_32 + \
DESC_L + DESC_AVL + DESC_LIMIT_DATA2 + \
DESC_P + DESC_DPL_0 + DESC_S_DATA + DESC_TYPE_DATA + 0x00
; 显存段描述符高位4字节初始化
; 显存的起始地址是0xb8000, 在段描述符低4字节中段基址0-15位存储的是0x8000, 所以段描述符高4字节最初8位是段基址的23-16位的值应该是0xB
DESC_VIDEO_HIGH4 equ (0x00<<24) + DESC_G_4K + DESC_D_32 + \
DESC_L + DESC_AVL + DESC_LIMIT_VIDEO2 + \
DESC_P + DESC_DPL_0 + DESC_S_DATA + DESC_TYPE_DATA + 0x0B
;-------------------- 选择子属性 --------------------------------
;第0-1位 RPL 特权级比较是否允许访问 第2位TI 0表示GDT 1表示LDT 第3-15位索引值
RPL0 equ 00b
RPL1 equ 01b
RPL2 equ 10b
RPL3 equ 11b
TI_GDT equ 000b
TI_LDT equ 100b
- “/home/lily/OS/boot/loader.S”
%include "boot.inc"
section loader vstart=LOADER_BASE_ADDR
LOADER_STACK_TOP equ LOADER_BASE_ADDR ; loader在保护模式下的栈指针地址,esp
jmp loader_start ; 下面存放数据段 构建gdt 跳跃到下面的代码区
; 构建GDT及其内部描述符, 每个描述符8个字节, 拆分为高低各4字节(32位)
GDT_BASE: dd 0x00000000 ; 第0个描述符,不可用
dd 0x00000000
CODE_DESC: dd 0x0000ffff ; 低32位31~16位为段基址15~0位, 15~0位为段界限15~0位
dd DESC_CODE_HIGH4
DATA_STACK_DESC: dd 0x0000ffff ; 数据段(栈段)描述符
dd DESC_DATA_HIGH4
VIDEO_DESC: dd 0x80000007 ; 0xB8000 到0xBFFFF为文字模式显示内存 段界限:limit=(0xbffff-0xb8000) / 4k=0x7
dd DESC_VIDEO_HIGH4 ; 0xB
GDT_SIZE: equ $ - GDT_BASE ; 当前位置减去GDT_BASE的地址 等于GDT的大小
GDT_LIMIT: equ GDT_SIZE - 1 ; SIZE - 1即为最大偏移量
times 60 dq 0 ; 预留60个 四字型 描述符空位, 用于后续扩展
SELECTOR_CODE equ (0x0001<<3) + TI_GDT + RPL0 ; 段选择子: 低3位为TI RPL状态, 其余为描述符索引值
SELECTOR_DATA equ (0x0002<<3) + TI_GDT + RPL0
SELECTOR_VIDEO equ (0x0003<<3) + TI_GDT + RPL0
;gdt指针, 前2字节为gdt界限, 后4字节为gdt起始地址(共48位)
gdt_ptr dw GDT_LIMIT
dd GDT_BASE
loadermsg db '2 loader in real.' ;相当于数组,数组的名字就是地址,loadermsg就是个地址
;用来显示要进入保护模式了
loader_start:
;---------------------------------------------------------
;INT 0x10 功能号:0x13 功能描述符:打印字符串
;---------------------------------------------------------
; 输入:
; AH 子功能号=13H
; BH = 页码
; BL = 属性(若AL=00H或01H)
; CX = 字符串长度
; (DH,DL)=坐标(行,列)
; ES:BP=字符串地址
; AL=显示输出方式
; 0——字符串中只含显示字符,其显示属性在BL中。显示后,光标位置不变
; 1——字符串中只含显示字符,其显示属性在BL中。显示后,光标位置改变
; 2——字符串中只含显示字符和显示属性。显示后,光标位置不变。
; 3——字符串中只含显示字符和显示属性。显示后,光标位置改变。
; 无返回值
mov sp, LOADER_BASE_ADDR
mov bp, loadermsg ; ES:BP 字符串地址, 在mbr es设置位了0, 现在在平坦模式下cs=es=0
mov cx, 17 ; 字符串长度
mov ax, 0x1301 ; AH=13h,AL=01h
mov bx, 0x001f ; 页号为0(BH=0h),蓝底粉红字(BL=1fh)
mov dx, 0x1800 ; (DH,DL)=坐标(行,列) (24,0), 意思是最后一行 0列开始
int 0x10 ; int 10 BIOS中断
; --------------------------------- 设置进入保护模式 -----------------------------
; 1 打开A20 gate
; 2 加载gdt
; 3 将cr0 的 pe位(第0位)置1
; ----------------- 打开A20 ----------------
in al, 0x92 ; 端口号0x92 中的第1位变成 1 即可
or al, 0000_0010B
out 0x92, al
; ----------------- 加载GDT ----------------
lgdt [gdt_ptr]
; ----------------- cr0第0位置1 ----------------
mov eax, cr0
or eax, 0x00000001
mov cr0, eax
; -------------------------------- 已经打开保护模式 ---------------------------------------
jmp dword SELECTOR_CODE:p_mode_start ; 刷新流水线,因为后面的指令为32位
[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:160], 'P' ;向显存中第二行首字符的位置写入P,因为一行80个字符(0-79)
jmp $
- 编译代码
nasm -I include/ -o mbr.bin mbr.S
nasm -I include/ -o loader.bin loader.S
dd if=/home/lily/OS/boot/mbr.bin of=/home/lily/bochs/hd60M.img bs=512 count=1 conv=notrunc
dd if=/home/lily/OS/boot/loader.bin of=/home/lily/bochs/hd60M.img bs=512 count=4 seek=2 conv=notrunc
- 运行代码
启动bochs:
bin/bochs -f bochsrc.disk
//在bochs目录下运行