计算机上电之后bios的启动过程:

    在 x86 系统中,将 1M 空间最上面的 0xF00000xFFFFF 这 64K 映射给 ROM,也就是说,到这部分地址访问的时候,会访问 ROM。
当电脑刚加电的时候,会做一些重置的工作,将 CS 设置为 0xFFFF,将 IP 设置为 0x0000,所以第一条指令就会指向 0xFFFF0,正是在 ROM 的范围内。
在这里,有一个 JMP 命令会跳到 ROM 中做初始化工作的代码,于是,BIOS 开始进行初始化的工作。
现在,BIOS开始打听操作系统的下落,在 BIOS 的界面上。你会看到一个启动盘的选项。启动盘有什么特点呢?它一般在第一个扇区,占
512 字节,
而且以 0xAA55 结束。这是一个约定,当满足这个条件的时候,就说明这是一个启动盘,在 512 字节以内会启动相关的代码。这些代码是谁放在这里的呢?
在 Linux 里面有一个工具,叫 Grub2,全称 Grand Unified Bootloader Version 2。顾名思义,就是搞系统启动的。
可以通过 grub2
-mkconfig -o /boot/grub2/grub.cfg 来配置系统启动的选项。这里面的选项会在系统启动的时候,成为一个列表,让你选择从哪个系统启动。
使用 grub2-install /dev/sda,可以将启动程序安装到相应的位置。grub2 第一个要安装的就是 boot.img。它由 boot.S 编译而成,一共 512 字节,正式安装到启动盘的第一个扇区。
这个扇区通常称为 MBR(Master Boot Record,主引导记录 / 扇区)。BIOS 完成任务后,会将 boot.img 从硬盘加载到内存中的 0x7c00 来运行。
由于 512 个字节实在有限,boot.img 做不了太多的事情。它能做的最重要的一个事情就是加载 grub2 的另一个镜像 core.img。core.img 知道的和能做的事情就多了一些。
core.img 由 lzma_decompress.img、diskboot.img、kernel.img 和一系列的模块组成,功能比较丰富,能做很多事情。 boot.img 先加载的是 core.img 的第一个扇区。如果从硬盘启动的话,这个扇区里面是 diskboot.img,对应的代码是 diskboot.S。
boot.img 将控制权交给 diskboot.img 后,diskboot.img 的任务就是将 core.img 的其他部分加载进来,先是解压缩程序 lzma_decompress.img,
再往下是 kernel.img,最后是各个模块 module 对应的映像。这里需要注意,它不是 Linux 的内核,而是 grub 的内核。lzma_decompress.img 对应的代码是 startup_raw.S,
本来 kernel.img 是压缩过的,现在执行的时候,需要解压缩。在这之前,我们所有遇到过的程序都非常非常小,完全可以在实模式下运行,但是随着我们加载的东西越来越大,
实模式这 1M 的地址空间实在放不下了,所以在真正的解压缩之前,lzma_decompress.img 做了一个重要的决定,就是调用 real_to_prot,
切换到保护模式,这样就能在更大的寻址空间里面,加载更多的东西。
切换到保护模式要干很多工作,大部分工作都与内存的访问方式有关。第一项是启用分段,就是在内存里面建立段描述符表,将寄存器里面的段寄存器变成段选择子,
指向某个段描述符,这样就能实现不同进程的切换了。第二项是启动分页。能够管理的内存变大了,就需要将内存分成相等大小的块。 保护模式需要做一项工作,那就是打开 Gate A20,也就是第
21 根地址线的控制线。在实模式 8086 下面,一共就 20 个地址线,可访问 1M 的地址空间。
如果超过了这个限度怎么办呢?当然是绕回来了。在保护模式下,第 21 根要起作用了,于是我们就需要打开 Gate A20。
切换保护模式的函数 DATA32 call real_to_prot 会打开 Gate A20,也就是第 21 根地址线的控制线。
现在好了,有的是空间了。接下来我们要对压缩过的 kernel.img 进行解压缩,然后跳转到 kernel.img 开始运行。
kernel.img 对应的代码是 startup.S 以及一堆 c 文件,在 startup.S 中会调用 grub_main,这是 grub kernel 的主函数。
在这个函数里面,grub_load_config() 开始解析,我们上面写的那个 grub.conf 文件里的配置信息。
如果是正常启动,grub_main 最后会调用 grub_command_execute (“normal”,
0, 0),最终会调用 grub_normal_execute() 函数。
在这个函数里面,grub_show_menu() 会显示出让你选择的那个操作系统的列表。一旦,你选定了启动某个操作系统,
就要开始调用 grub_menu_execute_entry() ,开始解析并执行你选择的那一项。例如里面的 linux16 命令,表示装载指定的内核文件,
并传递内核启动参数。于是 grub_cmd_linux() 函数会被调用,它会首先读取 Linux 内核镜像头部的一些数据结构,放到内存中的数据结构来,
进行检查。如果检查通过,则会读取整个 Linux 内核镜像到内存。
如果配置文件里面还有 initrd 命令,用于为即将启动的内核传递 init ramdisk 路径。于是 grub_cmd_initrd() 函数会被调用,
将 initramfs 加载到内存中来。当这些事情做完之后,grub_command_execute (“boot”,
0, 0) 才开始真正地启动内核。

 

bios使用MBR分区表
1、Bios + windows:
Bios 读取 MBR 前446字节的引导程序到内存并执行,MBR里面也有分区表,引导程序根据这个分区表找到引导分区,再加载引导分区的第一个扇区(PBR)的二级引导程序,二级引导加载下一步的bootmgr (bootloader)。

2、bios + linux
Bios 读取 MBR 前446字节(GRUB的boot.img)的引导程序到内存并执行,MBR里面也有分区表,引导程序读取分区表,并加载core.img,MBR和第一个分区中间的空隙放的是GRUB的core.img,从/boot/grub中读取配置和其他功能代码从而加载系统。

uefi使用gpt分区表
3、uefi + windows
Uefi自身可以识别fat32文件系统,uefi启动时会自动扫描启动设备,只要是带、fat32分区的都可以是启动设备,这些启动设备会显示在uefi的启动项里面,比如插上了一个带fat32分区的U盘,就会被uefi识别到,下一步就是去第一个启动设备的fat32分区的/boot/efi/目录下找osloader,也就是bootloader,64位系统叫做bootx64.efi,32位系统叫做boot.efi。一般这个efi是根据EDK2工程编译出来的,它的作用就是加载操作系统内核。

4、uefi + linux
Uefi自身可以识别fat32文件系统,uefi启动时会自动扫描启动设备,只要是带、fat32分区的都可以是启动设备,这些启动设备会显示在uefi的启动项里面,比如插上了一个带fat32分区的U盘,就会被uefi识别到,下一步就是去第一个启动设备的fat32分区的/boot/efi/目录下找osloader,也就是bootloader,64位系统叫做bootx64.efi,32位系统叫做boot.efi。一般这个efi是根据EDK2工程编译出来的,它的作用就是加载操作系统内核。 linux 系统上可能就是 core.img 的功能,只不过做成了efi文件。

 

参考:

1、

极客时间,趣谈linux操作系统第7节

2、

grub2 - 聊聊BIOS、UEFI、MBR、GPT、GRUB……_个人文章 - SegmentFault 思否

3、StackOverflow 问答

 

 

4、b站谭玉刚视频

 

posted on 2022-10-29 20:01  周伯通789  阅读(984)  评论(0编辑  收藏  举报