当按下计算机的启动按钮时,主板就开始上电。
在主板上,有一个东西叫ROM(Read Only Memory,只读存储器)。这和咱们平常说的内存RAM(Random Access Memory,随机存取存储器)不同。
咱们平时买的内存条时可读可写的,这样才能保存计算结果。而ROM是只度的,上面早就固化了一些初始化的程序,也就是BIOS(Basic Input and Output System,基本输入输出系统)。
如果安装过操作系统,刚启动的时候,按某个组合键,显示器就会弹出一个蓝色的界面,能够调整启动顺序的系统,就是我说的BIOS,然后我们就可以先执行它。
在x86系统中,将1M空间最上面的0xF0000到0xFFFFF这64K映射给ROM,也就是说,到这部分地址访问的时候,会访问ROM。
当电脑刚加电的时候,会做一些重置的工作,将CS设置为0xFFFFF,将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来配置系统启动的选项。你可以看到里面有类似这样的配置。
menuentry 'CentOS Linux (3.10.0-862.el7.x86_64) 7 (Core)' --class centos --class gnu-linux --class gnu --class os --unrestricted $menuentry_id_option 'gnulinux-3.10.0-862.el7.x86_64-advanced-b1aceb95-6b9e-464a-a589-bed66220ebee' {
load_video
set gfxpayload=keep
insmod gzio
insmod part_msdos
insmod ext2
set root='hd0,msdos1'
if [ x$feature_platform_search_hint = xy ]; then
search --no-floppy --fs-uuid --set=root --hint='hd0,msdos1' b1aceb95-6b9e-464a-a589-bed66220ebee
else
search --no-floppy --fs-uuid --set=root b1aceb95-6b9e-464a-a589-bed66220ebee
fi
linux16 /boot/vmlinuz-3.10.0-862.el7.x86_64 root=UUID=b1aceb95-6b9e-464a-a589-bed66220ebee ro console=tty0 console=ttyS0,115200 crashkernel=auto net.ifnames=0 biosdevname=0 rhgb quiet
initrd16 /boot/initramfs-3.10.0-862.el7.x86_64.img
}
这里面的选项会在系统启动的时候,成为一个列表,让你选择从哪个系统启动。最终显示出来的结果就是下面这张图。
使用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,对应的代码是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,切换到保护模式,这样就能再更大的寻址空间里面,加载更多的东西。
切换到保护模式要干很多工作,大部分工作都和内存的访问方式有关。
第一项是启用分段,就是再内存里面建立段描述符表,将寄存器里面的段寄存器变成段选择器,指向某个段描述符,这样就能实现不同进程的切换了。第二项是启动分页。能够管理的内存变大了,就需要将内存分成相等大小的块。
切换保护模式的函数DATA32 call real_to_prot会打开Gate A20,也就是第21根地址线的控制线。现在好了,有的是空间了。接下来我哦们要对压缩过的kernel.img进行解压缩,然后跳转到kernel.img开始运行。
kernel.img对应的代码时staartup.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) 才开始真正地启动内核。