实模式到保护模式

开机-实模式到保护模式

实模式到保护模式这个过程已经接触过很多次了。最早从软院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,即看扇区尾巴是不是0x550xaa,是的话自举滴任务就完成辣!

其实还没有,自举谢幕前还要进行重要的一步,就是转换回实模式。

原因是要向后提供兼容。不是i386之前的架构的请自觉转到保护模式。

总结为如下图:

image-20240904112540022

实模式到保护模式

这部分是写本文最主要的目的,所以单独拉一个标题来写。

实模式到保护模式主要是两步:设置段表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表示描述符表中的编号(下标)。

结合虚拟地址、段选择符和段表的相关概念,在分段机制中,将虚拟地址转换成线性地址(此时即为物理地址)的过程可描述如下:

  1. 根据段选择子中的TI位选择GDT或LDT(总是GDT);
  2. 根据段选择子中的index部分到GDT中找到对应位置上的段描述符;
  3. 读取段描述符中的base部分,作为32位段基址(总是0),加上32位段内偏移量获取最终的物理地址。

总结

这部分内容最重要的就是为什么需要保护模式、保护模式下的寻址以及段表和段选择子是什么东西。这部分搞懂了基本就没什么问题了。

在OS设计与实现的实验里这部分比较简单,概念搞清楚几行代码就搞定了。