实模式到保护模式
开机-实模式到保护模式
实模式到保护模式这个过程已经接触过很多次了。最早从软院OS实验的例行检查里背过相关内容,然后便不无意外地忘掉了。这次在OSDI课上又涉及到了这部分内容,趁此机会重温一下并写一个通俗易懂的笔记,以便以后查阅(可能并不会)。
实模式
摆龙门阵
Intel为了兼容8086搞出来的玩意,在8、90年代很有用,但是对于现在来说还是太old-school了,所以新的UEFI启动方式已经渐渐取代了原来的BIOS,尽管我的装机U盘上的PE系统还保留了Legacy
启动。
这里还有个UEFI和BIOS的区别 - 知乎专栏,有时间再看。
我想,实模式作为入门OS的很大原因在于UEFI比较复杂,大学计算机课程只能以BIOS为框架进行教学,所以绕不开这个实模式到保护模式的转换。并且这个转换的过程能体现操作系统内存虚拟化的很多芝士,比如段页式内存管理。所以这一部分内容的理解还是有助于成为操作系统领域大神的www。
实模式小介绍
摘自课程助教编写的实验文档
在QEMU中,i386
为了保持向后兼容性,一开机并不是我们熟悉的保护模式
,而是实模式
。
实模式简单来说就是一个16位的CPU,和保护模式相比,最主要有三点区别:
一是寄存器
不一样,实模式里只会用我们熟悉的寄存器的低16位,所以名字就少了前缀“E”,具体地有:
- 通用寄存器(16 位):AX,BX,CX,DX,SP,BP,DI,SI
- 段寄存器(16 位):CS,DS,SS,ES
- 状态和控制寄存器(16 位): FLAGS,IP
二是寻址方式不一样,在实模式里,虽然有段寄存器,但没有保护模式里的段表,更没有页表,物理地址就是 (段寄存器«4) + 偏移地址 。段寄存器16位,偏移地址也是16位,所以寻址空间就是2^20=1MiB。
三是中断处理不一样,不过我们现在也不关心这个东西,有兴趣的话可以去搜索一下实模式的中断向量表,现在只需要大概知道中断都是由BIOS代办的就行了。
BIOS执行过程以及两个模式
启动的第一条指令地址是0xffff0
。这个地址是BIOS ROM地址的最后,指令跳到ROM稍微靠前的地址[CS:IP] 0f000:E05Bh
。这个地址开始执行BIOS POST。
这里会立马发生很重要的一个转换,就是从实模式转成保护模式。
原因是POST需要在保护下执行,否则实模式那点空间不够。
自检完后通过INT 19h
开始自举。
自举就是依次把每个磁盘的0扇区加载到0x7c00
然后看是不是MBR,即看扇区尾巴是不是0x55
和0xaa
,是的话自举滴任务就完成辣!
其实还没有,自举谢幕前还要进行重要的一步,就是转换回实模式。
原因是要向后提供兼容。不是i386之前的架构的请自觉转到保护模式。
总结为如下图:
实模式到保护模式
这部分是写本文最主要的目的,所以单独拉一个标题来写。
实模式到保护模式主要是两步:设置段表GDT,再将CR0寄存器最低位置1。
现在先只涉及GDT,LDT不管。
保护模式的寻址怎么实现
保护模式比实模式牛的地方之一就是实现了段式内存管理。我们都知道段式内存管理本质就是个二维寻址。
保护模式下的寻址方式如下:
$$逻辑地址 = 段选择子_{16bits} \implies 段基址_{32bits} + 段内偏移_{32bits}$$
段选择子
,可以根据字面意思理解记忆,就是记录选择了哪个段的东西。它存放在段寄存器
中。
所以怎么从段选择子
拿到段基址
呢?这就要靠**段表
**了。
段表细节见后面小标题,这里只需要知道通过段选择子
能找到段表中正确的项,而这个项(有名字,叫段描述符
)含有我们想要的很多信息,主要就是段基址
。
实验中提到了"扁平模式",即将段基址全置0,这样偏移量就直接是逻辑地址了。
这样就实现了保护模式的寻址。
段表
段表
是个一位线性表。表项是前面说的段描述符
,一个段描述符
有64位,下面粗略说一下段描述符有哪些信息。
- 段基址:最主要的信息之一。占32位。
- 段限长:段的长度,保证内存访问不会越界。也比较重要,占20位。
- 其他控制位。包括段限长粒度、特权位等。
详细来说,如下
DESCRIPTORS USED FOR APPLICATIONS CODE AND DATA SEGMENTS
31 23 15 7 0
+-----------------+-+-+-+-+---------+-+-----+-+-----+-+-----------------+
| | | | |A| | | | | | | |
| BASE 31..24 |G|X|O|V| LIMIT |P| DPL |S| TYPE|A| BASE 23..16 | 4
| | | | |L| 19..16 | | | | | | |
|-----------------+-+-+-+-+---------+-+-----+-+-----+-+-----------------|
| | |
| SEGMENT BASE 15..0 | SEGMENT LIMIT 15..0 | 0
| | |
+-----------------------------------+-----------------------------------+
A - ACCESSED
AVL - AVAILABLE FOR USE BY SYSTEMS PROGRAMMERS
DPL - DESCRIPTOR PRIVILEGE LEVEL
G - GRANULARITY
P - SEGMENT PRESENT
具体大致看一下就行了,毕竟真要用到也不会有人记得清的。
另外,这个段表
的访问也大有学问。段表
的基址和长度放在一个叫GDTR
的寄存器里。
GDT REGISTER
+--------------------------------+---------------+
| GDT BASE | GDT LIMIT |
+--------------------------------+---------------+
47 15 0
段选择子
以下内容直接copy于实验手册
15 3 1 0
+-------------------------+-+---+
| |T| |
| INDEX | |RPL|
| |I| |
+-------------------------+-+---+
TI - TABLE INDICATOR, 0 = GDT, 1 = LDT
RPL - REQUESTOR'S PRIVILEGE LEVEL
TI
位表示该段选择子为全局段(查GDT)还是局部段(查LDT),实验中只会用到前者,RPL
表示该段选择子的特权等级,13位Index
表示描述符表中的编号(下标)。
结合虚拟地址、段选择符和段表的相关概念,在分段机制中,将虚拟地址转换成线性地址(此时即为物理地址)的过程可描述如下:
- 根据段选择子中的
TI
位选择GDT或LDT(总是GDT); - 根据段选择子中的
index
部分到GDT中找到对应位置上的段描述符; - 读取段描述符中的
base
部分,作为32位段基址(总是0),加上32位段内偏移量获取最终的物理地址。
总结
这部分内容最重要的就是为什么需要保护模式、保护模式下的寻址以及段表和段选择子是什么东西。这部分搞懂了基本就没什么问题了。
在OS设计与实现的实验里这部分比较简单,概念搞清楚几行代码就搞定了。