上一篇: 从零开始的操作系统 (第三章:引导扇区编程(16位实模式))
第四章:进入32位保护模式
虽然计算机已经在16位实模式下工作得很好,但为了更充分地利用CPU,并更好地了解CPU架构的发展是如何使现代操作系统受益的, 即硬件中的内存保护,那么我们继续探索进入32位保护模式.
32位保护模式的主要区别是:
- 寄存器扩展到32位,对16位寄存器名称加上前缀e来表示32位寄存器,例如:
mov ebx,0x274fe8fe
- 为方便起见,增加了两个额外的通用段寄存器fs和gs。
- 可以使用32位内存偏移,因此偏移量可以引用高达4 GB的内存(0xffffffff)
- CPU支持更复杂的 - 虽然稍微复杂一点 - 内存分段的方法,它提供了两大优势:
– 可以禁止一个段中的代码在其他特权段中执行代码,因此可以保护内核代码免受用户应用程序的影响
– CPU可以为用户进程实现虚拟存储器,使得进程存储器的页面(即固定大小的块)可以根据需要在磁盘和存储器之间透明地交换。 这确保了主存储器的有效使用,因为很少执行的代码或数据不需要占用宝贵的存储器。 - 中断处理也更复杂
将CPU从16位实模式切换到32位保护模式最困难的部分是我们必须在内存中准备一个称为全局描述符表(GDT)的复杂数据结构,它定义了内存段及其保护模式属性。。 一旦我们定义了GDT,我们就可以使用特殊指令将其加载到CPU中,然后在特殊CPU控制寄存器中设置一个位以进行实际切换。
如果我们不必用汇编语言定义GDT,那么这个过程就很容易了,但遗憾的是,如果我们如果希望加载一个更高级语言编译的内核,那么这种低级转换是不可避免的,例如C语言,通常C语言将编译为32位指令而不是效率较低的16位指令。
适应没有BIOS的生活
确实如此:在我们充分利用CPU的过程中,我们必须放弃BIOS提供的所有有用的例程。 正如我们将在32位保护模式切换时更详细地了解的那样,已编码为仅在16位实模式下工作的BIOS例程在32位保护模式下不再有效。 实际上,尝试使用它们可能会使机器崩溃。
所以这意味着32位操作系统必须为机器的所有硬件(例如键盘,屏幕,磁盘驱动器,鼠标等)提供自己的驱动程序。 实际上,32位保护模式操作系统有可能暂时切换回16位模式,因此它可能会利用BIOS。
我们将在切换到保护模式时遇到的第一个问题是知道如何在屏幕上打印消息,以便我们可以看到正在发生的事情。 以前我们要求BIOS在屏幕上打印ASCII字符,但是如何在计算机屏幕的适当位置突出显示相应的像素? 现在,只要知道显示设备可以在文本模式和图形模式两种模式之一中配置成几种分辨率之一就足够了; 并且屏幕上显示的内容是特定内存范围的直观表示。 因此,为了操作屏幕,我们必须操纵它在当前模式下使用的特定内存范围。 显示设备以存储器映射硬件的方式工作。
当大多数计算机启动时,尽管它们可能实际上具有更高级的图形硬件,但它们以简单的视频图形阵列(VGA)彩色文本模式开始,具有80x25字符面积。 在文本模式中,程序员不需要渲染单个像素来描述特定字符,因为已经在VGA显示设备的内部存储器中定义了简单字体。 相反,屏幕的每个字符单元格由内存中的两个字节表示:第一个字节是要显示的字符的ASCII代码,第二个字节编码字符属性,例如前景色和背景色以及字符是否应该闪烁。
因此,如果我们想在屏幕上显示一个字符,那么我们需要将其ASCII码和属性设置为当前VGA模式的正确内存地址,通常位于地址0xb8000。 如果我们略微修改我们的原始(16位实模式)打印字符串例程,使其不再使用BIOS例程,我们可以创建一个直接写入屏幕显示内存的32位保护模式例程,如图4.1所示
请注意,虽然屏幕显示为列和行,但屏幕显示内存只是顺序的。 例如,第3行第5列的地址可以按如下方式计算:0xb8000 + 2 (行 80 + 列)
我们的例程的缺点是它总是将字符串打印到屏幕的左上角,因此将覆盖以前的消息而不是滚动。 我们可以花时间增加这个程序集的复杂程度,但是不要让事情变得太难,因为在我们掌握切换到保护模式后,我们很快就会启动用更高级语言编写的代码,我们可以使用更高级语言让工作更轻松。