从BIOS到bootloader:创业伊始,有活儿老板自己上
- 软件平台:运行于VMware Workstation 12 Player下UbuntuLTS16.04_x64 系统
- 开发环境:Linux-4.19-rc3内核
一、BIOS启动(实模式)
BIOS程序固化在ROM(Read Only Memory)
环境假设:1M内存空间,x86系统
ROM地址:0xF0000~0xFFFF
电脑刚刚加电时:
-
设置CS = 0xFFFF, IP = 0x00000, 第一条指令就会指向0xFFFF0
-
执行JMP指令跳到ROM中,执行BIOS,完成部分初始化功能
2.1 建立中断向量表和中断服务程序(鼠标、键盘)
2.2 建立内存映射显存空间(显示器)
二、bootloader启动(实模式–>保护模式)
在BIOS完成初始化工作之后,需要加载操作系统(放在硬盘),按照约定:一般来说在第一个扇区,占512字节,而且以0xAA55结束。
当满足这个条件的时候,就说明这是一个启动盘,在512字节以内会启动相关的代码。
在Linux中,通常使用Grub2这个工具来把操作系统放入到上述扇区中,步骤如下:
-
门卫boot.img
安装boot.img(由boot.S编译而成,大小为512字节),形成第一个扇区MBR(Master Boot Record,主引导扇区)。
主要工作内容:加载core.img。
-
管理处core.img
组成:diskboot.img、lzma_decompress.img、kernel.img和一系列的模块
-
1 由boot.img这个门卫引导到管理处core.img的第一个模块diskboot.img
工作内容:
diskboot.img获得控制权,把core.img的其他模块引导进来:
2.1.1 解压缩 lzma_decompress.img,由于此时所需要的内存空间太大,所以会调用real_to_prot,切换到保护模式,获取更大的寻址空间。
2.1.2 解压缩 kernel.img(Grub2的内核)
2.1.3 各个模块module对应的映像
-
三、实模式切换到保护模式的具体工作( lzma_decompress.img)
- 建立分段:建立段描述符表,把段寄存器设置为段选择子,建立对应关系
- 建立分页:获得更大的内存空间
- 打开地址线:由于实模式采用的是20位宽的地址线,所以保护模式此时要打开第21根地址线——Gate A20
- 有了更大的内存空间,这个时候就解压缩kernel.img
四、选择操作系统(kernel.img)
解压得到startup.S以及一堆c文件,在startup.S中会调用grub_main,这是grub kernel的主函数。
-
grub_load_config()——获取配置信息
static void grub_load_config (void) { struct grub_module_header *header; FOR_MODULES (header) { /* Not an embedded config, skip. */ if (header->type != OBJ_TYPE_CONFIG) continue; load_config = grub_malloc (header->size - sizeof (struct grub_module_header) + 1); if (!load_config) { grub_print_error (); break; } grub_memcpy (load_config, (char *) header + sizeof (struct grub_module_header), header->size - sizeof (struct grub_module_header)); load_config[header->size - sizeof (struct grub_module_header)] = 0; break; } }
-
进行一系列的初始化
-
正常启动后,则调用
grub_command_execute()
static inline grub_err_t grub_command_execute (const char *name, int argc, char **argv) { grub_command_t cmd; cmd = grub_command_find (name); return (cmd) ? cmd->func (cmd, argc, argv) : GRUB_ERR_FILE_NOT_FOUND; }
—>调用
grub_command_execute (“normal”, 0, 0)
—>最终会调用
grub_normal_execute()
函数:grub_show_menu()
会显示出让你选择的那个操作系统的列表。
五、启动操作系统
-
调用
grub_menu_execute_entry()
,开始解析并执行你选择的那一项 -
例如里面的linux16命令,表示装载指定的内核文件,并传递内核启动参数。
2.1
grub_cmd_linux()
函数会被调用- 首先读取Linux内核镜像头部的一些数据结构,放到内存中的数据结构来,进行检查
- 如果检查通过,则会读取整个Linux内核镜像到内存。
-
如果配置文件里面还有
initrd
命令,用于为即将启动的内核传递init ramdisk
路径。3.1
grub_cmd_initrd()
函数会被调用,将initramfs加载到内存中来。 -
grub_command_execute (“boot”, 0, 0)
才开始真正地启动内核。