【操作系统专题】0.3 计算机的启动流程

0.3 计算机的启动流程

  1. 载入内存的概念

    • CPU的硬件电路被设计成只能运行处于内存中的程序,原因是速度和兼容性两方面的考虑,内存比外部存储速度快,且外部存储设备类型多,兼容复杂

    • 载入内存就是:程序被加载器从外部存储设备加载到内存中某个区域,CPU的cs:ip寄存器被指向这个程序的内存起始地址

        插入一张实模式下的内存分布图

        

  1. 计算机接通电源后运行的第一个软件BIOS

    按下计算机的电源键后从硬件启动到软件接管,对于第一个软件有几个问题需要思考:

    我们知道计算机运行的第一个软件是BIOS,BIOS的全称是Base Input & Output System,基本输入输出系统

    那么BIOS又是怎么启动的呢?因为BIOS是软件,它不可能自己加载自己,第一个运行的软件只能由硬件加载,那么这个硬件是谁?

    计算机中有一个寄存器ROM(只读寄存器),只读寄存器的内容不可擦除,不像DRAM(动态随机访问寄存器)掉电后数据就丢了,只要ROM中写入了数据,数据就会一直存在。BIOS代码所做的工作是一成不变的,正常其本身是不需要修改的(主板坏了刷BIOS例外),于是BIOS顺理成章的被写入ROM中。

    ROM也是一块内存,内存就需要被访问。ROM被映射到低端1MB内存的顶部,即地址0xF0000~0xFFFFF处。

    在硬件通电的一瞬间,CPU的cs:ip寄存器被强制初始化为0xF000:0xFFF0,这是CPU的设计机制。由于开机后系统处于实模式,段基址要左移4位,也就是乘以16,0xF000左移4位就是0xF0000,再加上偏移地址0xFFF0,就是0xFFFF0。这个地址就是BIOS的入口地址。

    根据上面描述ROM的地址空间是0xF0000~0xFFFFF,BIOS的入口地址0xFFFF0距离地址顶点1M只剩下16个字节,这么小的空间肯定是不够BIOS完成硬件检测及初始化工作的,所以这16个字节放置了一条跳转指令:

    jmp far f000:e05b

    这条指令将cs:ip指向了0xfe05b处,这个地址就是BIOS代码真正开始的地方。

    接下来BIOS便开始执行硬件检测、初始化、建立数据结构、添加中断等工作,BIOS的具体实现不是我们关注的重点,有兴趣的可以自行研究。

    • 它是由谁加载的?

    • 它被加载到哪里?

    • CPU的cs:ip是怎么指向这个程序加载后的起始地址的?

  2. BIOS交接给MBR

    BIOS完成的最后一项工作是校验启动盘中的位于0盘0道1扇区的内容。

    [说明:0盘0道1扇区其实就相当于0盘0道0扇区,就是磁盘上第一个扇区]  

    如果此扇区末尾的两个字节分别是魔数 0x55 和 0xaa, BIOS 便认为此扇区中确实存在可执行的程序(此程序便是久闻大名的主引导记录 MBR ),便将此扇区的内容加载到物理地址 0x7c00 ,随后跳转到此地址,继续执行 。 这里有个小细节, BIOS 跳转到 0x7c00 是用跳转实现的,

    jmp 0: 0x7c00 

    这是 jmp 指令的直接绝对远转移用法,段寄存器 cs 会被替换,这里的段基址是 0 ,即 cs 由之前的 oxf000变成了0x0。 如果此扇区的最后 2 个不是 0x55 和 0xaa ,即使里面有可执行代码也无济于事了, BIOS 不认。

    说明:为什么是物理地址 0x7c00 ,而不是个好记或好看的其他地址?其实就是一个魔数,至于魔数的来历,有兴趣的可以自行研究一下,这与理解操作系统的实现关系不大,不再深究。

  3. MBR的工作简述及后续交接

    MBR,全称是Master Boot Record,即硬盘的主引导记录,用汇编语言编写。

    一般将MBR分为广义和狭义两种:狭义上的MBR仅仅指引导程序,广义上包括整个扇区(512字节,包括:引导程序、分区表以及分隔标识魔数)

    主引导程序(boot loader)的主要功能:

    MBR到Bootloader的交接原理与BIOS交接到MBR的交接类似,将Bootloader的内容加载到一段内存,通过jmp指令跳转到程序入口。

    不同点在于这个交接更灵活了,因为MBR不是存在ROM中的只读属性,是可写入的,也就是可以自己修改,所以可以与Bootloader的设计进行对接,MBR可以根据Bootloader的大小和内容以及入口对应的定义Bootloader加载到内容中的空间以及跳转入口。

    • 分隔标识魔数2个字节,固定为0x55、0xaa,作用上一段落已说明。

    • 分区表(DPT)占用64个字节,含4个分区项,每个分区表项长16个字节。

    • 主引导程序(boot loader)占用446个字节

    1. 基本的硬件设备初始化(屏蔽所有的中断、关闭处理器内部指令/数据cache 等);

    2. 为加载操作系统的Bootloader(比如linux的grub)准备空间;

    3. 如果是从某个固态存储媒质中,则拷贝操作系统Bootloader到RAM 空间中;

    4. 设置好堆栈;

    5. 跳转到Bootloader的程序入口。

  4. Bootloader的工作简述及后续交接

    Bootloader一般用C语言编写,完成的工作主要有:

    1. 初始化系统引导阶段要使用到的硬件设备;
    1. 检测系统内存映射;
    1. 将操作系统内核及配置文件映像从flash上读到RAM 空间中;
    1. 为内核设置启动参数;
    1. 调用内核;
    1. 内核启动之后,操作系统就接管了计算机,进入了我们熟悉的领域。
  5. 简单的总结

        BOIS >>MBR (第一个磁盘的第一个分区的第一个扇区)>>在MBR中读取引导程序>>引导程序调用操作系统内核

  • 补充内容

        计算机运行起来后,一切都很正常,并且很多理论你都可以在很多资料上学到,然而,一切是如何开始的呢?开始意味着诞生,生活是乏味的,然而诞生一个生命却是需要十月怀胎的,一个生命总是在充满激情与畅想的十月之后诞生的,计算机的运行也不例外,在计算机中,对于运行而言最重要的东西其实不是操作系统,也不是应用程序,而是BIOS或者类似的东西,它里面记录着一张拓扑图,这张拓扑图描述了计算机的硬件是如何连接在一起的-比如网卡芯片在第几条pci总线上,以及各个硬件是如何配置的-比如时钟的值或者启动顺序,有了这张图,以后的操作系统才可以在这张图上谱写绚美的华章。在操作系统中,经常要处理与中断相关的信息, 每个设备的中断号从哪里来?这些都需要bios对信息的提供,计算机的启动程序存放在哪里?这些信息也需要bios的提供。比方说从ide的第一个磁盘启动操作系统的话,那么系统自然会把执行权力交给ide第一块磁盘中存放的某个程序。为了不同磁盘对不同操作系统的标准化,每个启动磁盘的前512字节被称作MBR。

        mbr中存放了启动程序和该磁盘的分区表,分区表一共描述了4个分区,其中一个分区为可启动分区,mbr中的启动程序的任务就是在分区表中找到这个启动分区,然后将执行权力交给启动分区的引导程序,至于这个引导程序如何做,那就交给操作系统的设计者来完成了,可以直接启动操作系统,也可以在启动操作系统前完成一些别的工作。以上就是标准化的操作系统自举的过程,然而为何必须这么做呢?因为第一,如果不是通过用户的设置,计算机不知道系统存储在哪个外部存储设备上,因此有了bios的启动顺序;第二,即使计算机知道了从第一块磁盘启动,由于磁盘可以有很多分区,它也不知道操作系统在哪个分区,因此就有了磁盘分区表中必须有一个启动分区。

        就是因为计算机太傻,有了上述的两个“不知道”,所以如果解决了这两个不知道,那么就不用在计算机内部设计这么多复杂的所谓的“智能化”了,bios已经解决了第一个计算机不知道的问题,而lilo/grub等bootloader解决了第二个不知道的问题,因此自从有了grub之后,启动操作系统也成了可配置的了,和在bios中配置从特定设备启动计算机一样,在grub中可以配置在哪个磁盘分区启动哪个操作系统,因此grub根本不需要磁盘分区的active标志,一切全部都由用户的配置搞定,之所以拥有分区的active标志,那纯粹是为了操作系统自举时的自动判断启动分区所在时使用。自从有了grub,大家可以放弃很多概念了,比如启动分区之类的。

        说实话,grub等bootlaoder就是一个mini OS,它的作用就是启动另一个real OS,grub的实现比简单的执行mbr的代码,判断哪个分区是启动分区,然后跳转到启动分区这种“自动化的配置”要复杂,这个复杂性主要表现在这个bootloader是可以配置的。如此复杂的代码不可能仅仅占据446字节(512-分区表)的空间,于是grub采用了“跳蛙”战术,从最简单的mbr中的代码加载其后的更大的一点的代码从而可以识别“文件系统”,然后跳转到具体的“文件系统”中,执行grub命令,加载存在于具体“文件系统”中的内核和内存盘,最终启动linux操作系统(启动windows等不直接支持grub的操作,需要显式跳转到windows的启动分区来完成启动)。

        以linux为例,如果启动到了initrd,那么此时肯定已经完成了内核的加载,可以说一个系统已经完全启动完成了,余下来的只是“根”的挂载问题了,对于liveCD之类的系统,到此步就完成了,因此我们的讨论也仅到此步。那么此时的linux还会“记得”它是从那块磁盘的哪个分区加载的吗?答案是不!因为grub的kernel和initrd命令将内核和内存盘从具体的“文件系统”加载到内存,只要完成了此步,内存中就已经有了系统运行的全部信息,此时将所有的磁盘拔下,内核还是会从解压开始到达initrd的执行的,毕竟数据和代码都在内存中,此时内核仅仅是内存中的内核,它是一个全新的执行体,并不知道grub的任何信息,也不知道是谁将它引导起来的(grub和linux内核的解耦合),内核将丢失所有grub所知道的硬件信息,当然包括磁盘信息,如想知道此时内存中的内核和initrd是从哪一块磁盘的哪一个分区被加载到内存的,不得不从sysfs的block目录中得到更详细的信息,然后遍历所有的磁盘,寻找特征从而加以判断。

        如果我事先做好了一个dom盘,cf卡或者磁盘,然后事后可能将之插入到不同的机器上,鉴于每台机器的ide口的硬连线可能顺序不同,比如在pc1上的ide口是hda,在pc2上或许就是hdb,如果我将grub的配置文件写成root=/dev/hda1那么在pc2上可能就无法启动,如何能做到自适应启动呢?在仅有一块块设备(磁盘)的情况下,我可以在initrd的/init脚本中挂载sysfs在/sys,然后再在/sys/block/下查找唯一的磁盘,然后直接挂载之,然而如果不仅仅有一块磁盘呢?那么一个可选的方法就是在启动磁盘的/下创建一个文件abc(确保唯一性),然后分别挂载所有的/sys/block下的磁盘,查找/下是否有一个abc的文件...

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值