一个操作系统的设计与实现——第2章 主引导记录

2.1 BIOS

当按下开机键的那一刻,发生了什么呢?

这是一个百废待兴的时刻,所有的硬件设备都刚启动,并没有做好准备,甚至连CPU自己都是。此时,就需要一些外力帮助CPU工作起来。具体来说,在CPU刚启动时,其CS:IP被硬件电路设定为f000:fff0。这个地址并非指向内存,而是指向主板的一个非易失性ROM,其中存放的代码被称为基本输入/输出系统(Basic Input Output System,BIOS)。这段代码是CPU在加电后运行的第一段代码,其目的是初始化并检测各个硬件设备,如显示器、硬盘、键盘等。

BIOS的执行时间是十分短暂的,其做的最后一件事情是:将硬盘的第一扇区,共512字节,加载到内存地址0x7c00处,然后通过jmp 0:0x7c00指令跳转至该处执行。这个扇区中的内容被称为主引导记录(Main Boot Record,MBR)。

BIOS的故事讲完了,但上述描述中有一些细节需要进一步讨论。

2.1.1 实模式

此时的CPU工作在实模式下。所谓实模式,指的是8086这款CPU的工作模式。8086是一款16位的CPU,这意味着其寄存器都是16位的,不过,其地址线却不止16根,而是20根,20根地址线能够达到2的20次方,也就是1M的寻址能力。然而,地址线是多了,寄存器的位数却跟不上,16位的寄存器无法存放20位的地址。于是,8086使用了一个打补丁式的设计:在寻址时,先将段寄存器中存放的地址左移4位,再与偏移地址相加,这样就能强行凑出20位的地址了。

2.1.2 什么叫"硬盘的第一扇区"?

硬盘是由一个或多个圆形的盘堆叠而成的,每个盘上的所有同心圆环构成多个磁道,每个磁道又被分成多个扇区。所以,应该这样描述一个扇区:第x个盘面上的第y个磁道上的第z个扇区。即,用三个数字描述一个扇区。但这种描述方法的缺点显而易见:太麻烦了。于是,人们发明了"逻辑扇区"这一概念,所谓逻辑扇区,就是将整个硬盘视为一个超长数组,其由硬盘中的所有扇区排列形成。从而,只需要一个索引值,就能唯一确定一个扇区了。逻辑扇区由硬盘控制器负责转换,将其对应到某个实际的扇区上。

2.1.3 为什么一个扇区是512字节?

这完全是约定,硬盘的一个扇区就是512字节。

2.1.4 为什么是0x7c00这个地址

这个地址沿袭自IBM的一款计算机中的BIOS,属于一个约定俗成的地址。0x7c00这个数刚好是31K,接近32K这个整数,所以可能的原因是:这个BIOS是为有32K内存的计算机准备的,而主引导记录只是一段512字节的过渡代码,要尽量靠上加载,于是,就选用了31K这个位置。

综上,在计算机刚启动时,首先执行的是BIOS中的代码。BIOS在结束之前会加载并跳转到MBR。所以,MBR是操作系统的起点。

2.2 MBR

想要写一个MBR,就需要满足以下要求:

  1. MBR的大小必须是512字节,不能超过,也不能少,如果少了,就需要凑足512字节
  2. MBR的最后两字节必须依次是0x550xaa,这是MBR与BIOS的约定。只有这么做,BIOS才认为MBR有效,才会加载这个MBR

请看本章代码2/2.1/Mbr.s

第1行,声明一个段。这里的vstart=0x7c00是什么意思呢?这涉及到编译器对汇编代码的编址。在默认情况下,编译器会将段中的所有内容(包括指令和数据)从0开始编址,当使用段中的一个符号时,拿到的便是一个从0开始的偏移地址。但在MBR中,这样做是不对的,不应该从0,而应该从0x7c00开始编址。请看以下代码:

section mbr

A: db 0
B: db 1

mov ax, A  ; 0
mov bx, B  ; 1

在MBR中,上述地址是错误的,这两个字节的正确地址应该是0x7c000x7c01

最朴素的修正方法是这样:

section mbr

A: db 0
B: db 1

mov ax, 0x7c00 + A  ; 0x7c00
mov bx, 0x7c00 + B  ; 0x7c01

这样做虽然是对的,但是很麻烦,需要手动调整所有的地址。能从根本上解决问题的方案是:让编译器不要从0开始编址,而是从0x7c00开始,这就是vstart=0x7c00的作用:

section mbr vstart=0x7c00  ; 现在,从0x7c00开始编址

A: db 0
B: db 1

mov ax, A  ; 0x7c00
mov bx, B  ; 0x7c01

第3行,$是nasm编译器提供的一个特殊标号,用于指代当前行,所以,这一行代码等价于:

this: jmp this

这是一个无限循环,目的是让CPU保持在这一行,不要继续向下执行。现在的目标是让MBR先运行起来,并不做任何事情,所以使用了这一指令。

第5行,times是nasm提供的一个伪指令,其格式为:

times 一个数字 一段代码

times伪指令可将后面这段代码重复多次。这里使用times的目的是将MBR用0填充至510字节,为最后的0x550xaa做准备。填充的字节数使用510 - ($ - $$)进行计算。$已经介绍过了,其指代的是当前行的地址;$$是nasm提供的另一个特殊标号,其指代的是当前段的起始地址,故$ - $$表示当前段的大小,510 - ($ - $$)表示当前段距离510字节还差多少。

第7行,定义了0x550xaa,这是BIOS与MBR的约定。

接下来,请看本章代码2/2.1/Makefile

第2行,编译Mbr.s。可以发现,编译好的Mbr文件确实是512字节。

第3行,将编译好的Mbr文件写入虚拟硬盘。其中,if=Mbr用于指定输入文件;of=c.img用于指定输出文件;seek=0用于指定写入的起始逻辑扇区,由于MBR必须位于第0个逻辑扇区,所以这里需要使用seek=0count=1用于指定写入的扇区数,MBR的扇区数为1;conv=notrunc是固定用法。

现在,运行bochs命令,可以看到虚拟机开始正常运行。

2.3. 文本模式显存

实模式下的1M地址空间并不都是指向物理内存的。具体来说,只有0x0~0x9ffff指向物理内存,剩下的0xa0000~0xfffff指向外部设备。在我们的操作系统中,唯一需要关注的外部设备是文本模式显存。

文本模式显存有一个神奇的功能:这段显存里面有什么,屏幕上就显示什么,而且是实时更新的。具体来说,从0xb8000开始的4000个字节,决定了当前屏幕上显示的内容。bochs使用的是25行,80列的显示模式,每个字符使用两字节,所以一共是4000字节。这两字节中的第一字节为这个字符的ASCII码,第二字节的含义如下:

7 6 5 4 3 2 1 0
含义 是否闪烁 背景红色 背景绿色 背景蓝色 是否高亮 文字红色 文字绿色 文字蓝色

也就是说,如果想要显示一个最普通的白色字符,第二字节应为0x7

请看本章代码2/2.2/Mbr.s

第3~4行,将ds切换到0xb800。实模式下的段寄存器在寻址时会左移四位,这样就得到了0xb8000,这是显存的起始地址。

第6~7行,通过写显存,在屏幕的左上角打印一个6

编译这个新的MBR,并启动bochs,可以看到屏幕的左上角已经打印出了6

posted @ 2023-11-12 09:45  樱雨楼  阅读(20)  评论(0编辑  收藏  举报