操作系统启动之谜

Published: 29 Jun 2012 Category: Linux

在我小的时候一直纳闷一台电脑是怎么从给它个电就能自动跑出一个五颜六色的界面的,相信很多人也有和我一样的疑问吧。长大之后总算多少了解一些了,试着把我的了解和疑问写一下。PS:可能今后会有一段不方便更新博客了,不过也只是可能,不过既然有可能了,这次就多写一点。

先考虑一个普通的程序运行,会有各种通用寄存器记录一些变量,一些状态寄存器记录程序状态,eip寄存器记录运行的指令地址;内存中会有代码段,数据断,堆和栈;然后就按照当前的eip地址从内存中取指令,指令再根据各自的寻址方式选择从寄存器还是从内存中读取数据进行运算,如果开启虚拟内存机制,数据页面可能在硬盘上,此时会有磁盘IO。计算机的启动实际上也是一个程序,执行一系列的指令,修改一系列的数据,那么计算机的启动和普通程序有什么不同呢?

史前时代

正如我们所知寄存器和内存这种存储器都是易失性存储器,一掉电他们就清白了,一开机是有作坏事的能力了,问题是谁能给出第一个eip?这第一条指令的eip又该是多少?好吧,就算eip有了,eip去内存取指令了,可内存此时应该也是空的呀。我们知道程序要在内存中才能运行,其他程序可以靠上一个在内存中的程序把自己加在到内存,那么第一个程序怎么把自己加载到内存呢?

从现有的计算机体系角度来看,软件是无法完成这件事情的,只能靠硬件来实现。由硬件把代码复制到内存的一个区域,再把eip置到那个区域就可以执行了,那么我们又该把什么代码硬件复制过去呢?如果你认识 BIOS 的话,这个时候他就登场了,它包括一系列的开机自检程序,对硬件进行初始化,设置中断向量,中断处理程序之类的工作。这部分代码是写在一个非易失的flash上的,硬件会自动把它加载到1M内存的顶部,为什么现在是1M内存就自己想去吧。不过我一直纳闷的是为什么要加载到顶部,加载到顶部的结果就是启动的第一条指令从地址到内容都是个十分tricky的设计。

其实写到这机器启动的第一条指令还没运行呢。因为尽管代码我们放到内存了,eip还没定下来。实际情况是BIOS代码的大小是不固定的,而硬件又把这部分代码加载到了内存的顶端,这样BIOS中地址最低一条代码的位置就不固定了,按照一般的编程思维,第一条指令就应该是地址最低的那条指令。而第一个eip的值又是由硬件写死的,怎么才能由一个固定的eip定位到一个不确定的地址呢?

先看一下实际中第一条eip是多少吧————0xFFFF0。对应于1M的地址空间,这个地址已经到了最后16字节,如果考虑到最长的指令有15字节和对齐因素的话,这个地方就只能放一条指令了,那应该放一条什么指令呢?事实上这个地方是一条跳转指令,跳转到BIOS 的低地址的第一条可执行代码。BIOS代码过把自己最高16字节的代码设为一条跳向低地址的跳转指令,这样就可以灵活的控制开机第二条指令的地址,来解决BIOS代码变化的问题。通过一个跳转指令,硬件和软件完成了交接,进入了一个我们相对熟悉的程序运行过程,BIOS的代码开始欢乐的跑起来了。不过我一直在想如果BIOS代码加载到内存从0开始的位置,第一条指令定位就没那么麻烦了吧。

远古时代

走到了这里所有的代码都是在BIOS的flash中代码,硬盘还没干活呢,也就是说操作系统还在睡觉呢。我们要把操作系统的代码弄到内存里来执行,然后就又碰到一个问题,装过操作系统的人都知道,操作系统可以装在不同的盘上,位置也是不固定的,也可能有多个系统,又该到哪去找操作系统呢?还是看一下BIOS在跑趴下之前干了什么吧,BIOS代码在内存中设置了中断向量表和中断处理程序,如下图,倒下前它发了个0x19号中断,这个中断的处理程序会把磁盘的第一个扇区加载到内存。

事情到这里慢慢有了头绪,尽管操作系统可能有很多位置也不固定,但是他们如果在第一扇区放个引导指向他们就可以了。实际上在第一扇区会存两个东西,一个 MBR(Master Boot Record)和一个加载程序。MBR中包括一段检查磁盘启动信息的程序和传说中的分区表。在分区表中就会记录每一个磁盘分区是否有可启动的操作系统,分区的大小,以及操作系统的位置。而那一小段加载程序就通过分区表来加载操作系统,grub就是这一小段程序的一个代表,它可以通过和用户的交互界面让用户选择进入的操作系统。这时候它会加载操作系统的第一部分代码,这第一部分的代码在最后又会加载第二部分代码,然后依次类推,一个链式反应就进入正常运行了,也进入了我们越来越熟悉的时代。

中世纪

操作系统现在其实基本就可以用了,很久很久以前最为简陋的操作系统,没有复杂的寻址模式,没有保护模式,没有操作系统自己的中断处理,差不多也就这样了。为了能兼容这种很就很久以前的设计,计算机启动还是保留着之前的步骤,但是时代总是在进步,所以启动过程也开始进一步演化。

首先要从实模式到保护模式的转化,在之时候要考虑一下原来什么东西是和保护模式不兼容的呢?其实主要还是地址相关的一些东西,可能在32位地址模式下就和原来不一样了。之前主要是有一个中断向量表是和地址相关的,需要把它进化成32位的,此时操作系统也想接管中断处理程序创建自己的中断向量表了,所以BIOS老前辈就要享受兔死狗烹的待遇了。但是在这时候如果有个中断请求正好过来,而中断向量表又在修改中,改了一个错误的地址那么机子刚起来就被打回到史前文明了,BIOS这个老油条还是留了一手的。所以在修改前操作系统会有一个关中断的操作。这样操作系统就可以毫无后顾之忧的修改BIOS的遗产了。

搞定中断向量后,操作系统只需要把相关的寄存器再初始化一次弄成32位模式的,初始化保护模式下GDT和IDT就可以开始保护模式时代了。然后地址线20位到31位在这一刻被激活了,我们终于可以利用4G的寻址空间了。

现代

其实没什么好多说的了,操作系统可以撒欢跑了。我也要准备考虑跑路了。

参考资料

  • MIT 6.828
  • 深入理解Linux内核
  • Linux内核设计的艺术

  • 本博客已经全文RSS输出,可通过订阅 oilbeater.com/atom.xml 订阅更新。或者关注我的微博@oilbeater ,公众号『我的观点』