0%

计算机系统启动流程

远古时代

这个时期,计算机是处于电气化高度相关的环境下,几乎没有软件的概念(这里有点瑕疵,稍后补充)。

计算机这个时候会进行一系列针对硬件的自测试,尤其检测一些必要硬件是否可用,这个过程成为POST自检,如果检测通过就会进行下一个步骤:BIOS模式,或者UEFI模式。

一般计算机硬件出现故障,都会在这里的硬件自检阶段无法通过而被迫中断。此阶段的自检包括CPU的检测,内存,IO芯片模块,例如什么8255等等(现代的这些外设芯片性能更加优越,功能更加丰富,不一定是8255这些老古董)。

以上部分是纯粹硬件部分的自检,几乎没有软件掺入,到了下一步的BIOS或UEFI步骤,就会正式有软件程序的加入。

但是对于一些异构平台,例如嵌入式设备,这里的设计可能就会不一样了,它的自检可能是透明的,几乎不可察觉的,因为它都是在一块SOC芯片里面完成的,而且很大成分是基于软件的,这里不讨论嵌入式的流程。

传统BIOS方式

系统通过自检后,会启动一个ROM上的BIOS程序,BIOS是运行在实模式下的,程序从几百K到几M不等。它是最底层对硬件的控制层,可以在计算机启动的时候进入BIOS界面,可以对计算机系统进行各种设置,其可设置的信息因厂商不同也丰富多样。

BIOS程序被启动后,就会进行初始化操作,主要包含创建中断IRQ表,各种寄存器的设置,还有IO端口分配等等。

当上述操作都完成后,BIOS就会复制BIOS设置里面的启动分区到RAM中(启动MBR分区程序),并加载其中的引导程序(引导程序会被复制到RAM地址0x00007c00的开始处)。

现代UEFI方式

UEFI是现代计算机都采用的接口,作为替换BIOS的方案。UEFI的翻译就是统一可拓展固件接口,它采用了模块化的设计,而且,它可以运行在AMD64, IA32, IA64等多种架构平台上。

为什么要UEFI?

因为BIOS的发展缓慢,经过几十年,还是需要通过必须进入16位的实模式,然后初始化设置。而且对于各种多样的厂商,也没有统一的固件接口规范,所以就一直依赖于老的BIOS方案,但后来Intel设计的UEFI,确定了统一的接口规范,从设计上提高了软件操作性,并解决了BIOS的局限性,这些高级的功能拓展打破了BIOS下遇到的困局。

UEFI的方式和BIOS基本类似,只不过它具备了更加高级的特性,而且可以很方便地更新硬件固件。

UEFI的初始化完成后,也是在设置里面找到一个磁盘,不过不是启动分区上的MBR程序,而是会找到EFI的分区,从分区的对应目录下去启动EFI程序,而EFI启动程序就是计算机系统的引导程序了。

可见这种加载方式更加安全,以往有些病毒可以修改MBR分区程序,进而把自己深深地植入系统之中,而在UEFI下,如果启动程序是添加签名加载的话,UEFI对启动程序会进行验签,使得病毒无法通过修改启动程序来植入系统了。

古代文明

这一部分是启动的引导程序。

一些常见的引导程序

GRUB

GRUB是一款非常受欢迎的Linux启动引导程序,它是可以引导不同的操作系统来启动,比如:Linux,Windows,FreeBSD等等。

当GRUB被启动后,它会加载模块,并读取配置文件,展示可以启动的选项;或者也可以切换到它的命令交互模式,可以像使用Shell那样来发送命令,指定如何启动系统。

LILO

LILO是一个比GRUB更早的Linux启动引导程序,至今还有一些发行版本在延用它,它也可以加载不同的操作系统,不过在功能方面相比于GRUB更加简洁一些。

BOOTMGR

BOOTMGR是大概Windows Vista发行后,出现的启动引导,它是NTLDR的衍生版本,通过读取BCD文件配置来加载系统内核,同时,BOOTMGR也是为了更好地于64位系统兼容而设计的。

NTLDR

这个Windows XP以及之前的Windows版本采用的引导程序,它的职责就是解析BOOT.INI配置文件,并加载系统内核。

引导程序做什么

这里为了方便起见,就拿GRUB举例,在GRUB被启动后,它首先读取相应的配置,并向屏幕打印一个菜单,菜单列举了可以启动的系统选项,当用户选中一个菜单进入后,它就按照菜单项所设置的参数来启动系统内核,而且这些传递给操作系统的参数可以直接进行修改的,修改后的参数设置会被传递给内核。

GRUB的加载流程

这里的GRUB加载指的是以BIOS启动后的加载

  • 当GRUB程序在MGR分区被拷贝到RAM地址的0x00007c00后,GRUB就被接着运行
  • 这个小段的GRUB程序是实模式的,它会设置自己栈空间
  • 从磁盘读取配置,加载Linux内核文件的前面512字节,拷贝到系统的RAM地址为0x00090000处(这段内核按特定的约定设置好在相对偏移为0x0200处即为它的入口,即:0x00090200)
  • 再把Linux后面剩余部分拷贝到0x00010000处(用make zImage编译的内核),或者拷贝到0x00100000处(用make bzImage编译的内核)
  • 当上述拷贝完成后,直接跳到0x00090200处执行代码,就是上面所说的内核入口

对于UEFI启动的内核而言就不需要自己去拷贝再跳转代码了,因为UEFI加载的是PE文件,通过PE文件信息是可以指定让UEFI如何来加载PE内核的,例如:入口地址是多少等等

引导程序扮演的角色

GRUB本身不是操作系统,它只是一个操作系统的加载工具,通过这种方式,就可以很方便地维护和修复系统内核因错误无法启动而进行回滚到以前的版本的操作。

引导程序作为操作系统和BIOS/UEFI的中间层,它提供了一种衔接机制,避免了因操作系统内核出错原因而无法进行操作系统修复的尴尬。

在Windows中的NTLDR和BOOTMGR,都是类似的方式启动操作系统内核的,如果启动出现故障,也会展示一个启动出错的页面,那就是引导程序做的事。

近代时期

经过这么多流程,终于走到了操作系统的部分了,这部分各个操作系统不同而所进行的操作也各异,但是一般都会涉及到的操作可能有:

操作系统初始化

从实模式切换到保护模式,包括:

  • 初始化GDT, LDT寄存器
  • Paging需要设置PTE映射
  • 设置TSS任务管理机制
  • 设置IDT表来管理IRQ中断
  • 初始化文件系统
  • 初始化IO设备

至于操作系统这部分内容比较多,我可以选择下一篇博文叙述。

另外,我有一个未完成的demo在GitHub上,可以点击这里查看。

相关参考链接:

欢迎关注我的其它发布渠道