天天看点

linux启动从bios到bootleader

bios(basic input / output system)启动,首先系统会进行自检,检测cpu,内存,显卡,i/o是否正常,如发现异常这个阶段会报错。

上一个阶段成功后会读取mbr(master boot record)硬盘的0柱面,0磁盘,1扇区称为主引导扇区。它由三部分组成,主引导程序(bootleader),硬盘分区表dpt(disk partition table)和硬盘有效标志(55aa),如下图所示

linux启动从bios到bootleader

使用grub2启动系统时,启动顺序如下图

linux启动从bios到bootleader

grub2第一个需要安装的是boot.img,它由boot.s编译而成,一共512个字节,正是安装到启动盘的第一个扇区,这个扇区通常被称作mbr(master boot record / 主引导记录扇区)

bios完成任务后,会将boot.img从硬盘加载到内存的0x7c00中,为什么是0x7c00呢,0x7c00这个地址来自intel的第一代个人电脑芯片8088,以后的cpu为了保持兼容一直使用这个地址。1981年8月,ibm公司最早的个人电脑ibm pc 5150上市,就用了这个芯片。当时搭配的操作系统是86-dos, 这个操作系统需要的内存最少是32kb,我们知道,内存地址从0x0000开始编号,32kb的内存就是0x0000-0x7fff可以使用。8088芯片本身需要占用0x0000-0x03ff,用来保存各种中断处理程序的存储位置(主引导记录本身就是中断信号 int 19h的处理程序),所以内存只剩下0x0400-0x7fff可以使用,为了把尽量多的连续内存留给操作系统,主引导记录就被放到了内存地址的尾部,由于一个扇区是512个字节,主引导记录本身也会产生数据,需要另外留出512字节保存。所以他的预留位置就变成了0x7fff-512-512+1=0x7c00。

由于512个字节很有限,boot.imp做不了太多的事情,它能坐的最重要的一个事情就是加载grub2的另一个镜像core.img,它是由lzma_decompress.img、diskboot.img、kernel.img和一系列模块组成,功能比较丰富,能做很多事情。

boot.imp先加载的是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_decopress.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 tamdisk路径。于是grub_cmd_initrd()函数会被调用,将initramfs加载到内存中来。当这些事情做完之后,grub_command_execute("boot",0,0)才开始真正地启动内核。

继续阅读