背景
- 每个操作系统(OS)在设计时都会考虑对boot loader的支持,OS的安装通常也涉及boot loader的预安装。在支持多OS的物理服务器使用场景中,通过链式机制让一个OS去引导另一个OS显然效率低下;定义一套boot loader和OS之间的接口,让遵循接口的所有boot loader都可以引导遵循相同接口的OS,才是更合理的解决方案。多OS兼容的场景我们称为Multiboot,boot loader和OS之间的接口规范我们称为Multiboot Specification。
- GRUB是遵循Multiboot Specification的boot loader之一。1995年,Erich Boleyn和Brian Ford在GNU Hurd项目的开发过程中设计了Multiboot规范,并将GNU Hurd的boot loader作为规范的实现引导并其微内核GNU Mach。之后Erich尝试修改FreeBSD的boot loader让其兼容Multiboot规范,发现与其修改FressBSD的boot loader,不如直接从零开始单独写一个软件,至此GRUB诞生。可以说,GRUB就是为兼容多OS场景而生的。
- GRUB在之后不断扩展以满足新的需求和场景,但很快,开发者发现GRUB遵循的Multiboot规范存在一些致命的缺陷导致对某些新功能的支持非常困难,并且设计导致的bug也不断增多,因此在2002年,Yoshinori K. Okuji开始重写GRUB的核心代码并将其重命名为GRUB2,原来的GRUB被重命名为GRUB Legacy,GRUB2遵循了Multiboot2规范。约2007年,GNU/Linux的发行版开始使用GRUB2,到2009年,大多数的Linux发行版已经将GRUB2作为默认版本安装。
目标
- 分析多OS使用场景,Multiboot规范需要考虑以下需求:
- 分析boot loader和OS的交互逻辑,可以将两者的接口定义从以下三个方面拆分:
- OS image format - 对OS镜像格式的定义,便于boot loader识别镜像并加载其内核到内存
- Machine state - 对boot loader状态的定义,便于OS判断其引导者boot loader的状态
- Boot information format - 对boot loader与OS之间传输信息的格式定义,便于控制OS的某些行为
- Multiboot与Multiboot2都基于以上的分析而设计,Multiboot2在具体定义上扩展性更强,标准化做的更好,下面我们基于以上三点对比介绍两个规范的具体内容。
OS image format
- OS image通常是一个普通的可执行文件(遵循ELF规范),其payload就是OS的section。通常boot loader将OS image加载到load address(ELF规范中section entry的sh_addr)对应的内存,之后跳转到entry address,最后将控制权交给OS。
- 如果所有的OS image都遵循ELF规范,那么boot loader总是可以根据规范找到OS image中包含的section并提取其load address,将其正确加载到load address指定的地址,但是,Mulitiboot规范需要考虑不支持ELF规范的OS image,也能被boot loader顺利地找到,就是说boot loader可以不需要理解ELF规范或者其它规范,也能正确地将OS image加载运行,要实现这个目标,需要将ELF规范中关于二进制程序加载相关的接口在Multiboot中也定义一遍,因此我们推测,OS image format必然包含了如何加载OS image的信息。
- 另外,某些OS image还会指定其倾向的图形模式,因此Multiboot提供了供OS image描述其推荐的图形信息可选字段。
除此之外还会有其它一些信息作为OS image format的内容,这里不赘述。
Multiboot
- Mulitboot设计的OS image format分为了三个部分,其格式如下:
- 每个字段的长度和偏移如下:
Magic fields
- Magic - 魔数,值为0x1BADB002,标示一个兼容Multiboot规范的OS image
- Flags - 标识域,用于OS images向boot loader展示加载其自身涉及的一些依赖特性和可选特性。其中bit 0 ~ bit 15是依赖特性,如果boot loader无法理解这些特性,boot loader会直接报错;bit 16 ~ bit 31是可选特性,boot loader无法理解可以忽略。我们简单介绍以下特性:
- bit 0,内存页对齐标记位,如果置位,OS image要求boot loader在加载启动模块(boot modules)时将其地址按照4k对齐。其原因是某些操作系统希望直接将启动模块映射到地址空间,4k对齐可以方便映射
- bit 1, 内存信息标记位,如果置位,OS image期望boot loader将系统的可用内存信息按照Boot information format中的定义,写入到mem_*字段,方便OS启动后解析,如果boot loader有能力获取整个内存映射的视图,写入mmap_*字段
- Bit 2,图形信息标记位,如果置位,OS image期望boot loader填入图形信息到Boot information format定义的对应字段
- Bit 16,加载信息标记位,可选,如果置位,标示地址字段(address fields)有效,boot loader不再根据ELF header中的地址计算加载地址,而是按照该字段包含的地址加载OS image。当OS image遵循ELF格式时,该字段不需要OS image提供,boot loader可以直接根据ELF头部解析OS image并正确加载;当OS image不是ELF格式时,该字段就是必填项了,boot loader会从这个字段提取加载地址并加载OS image到对应的内存位置。
Address fields
- Boot loader加载非ELF格式的OS image file示意图如下所示:
- Header_addr - OS image要求boot loader将Multiboot header加载到的内存物理地址。boot loader在加载OS images时,首先查找魔数0x1BADB002在OS image文件的偏移(后文用header_offset表示),然后从header_offset开始,加载header_addr - load_addr长度的内容到Header_addr指向物理内存,完成header的加载。
- Load_addr - OS image的代码段加载地址。boot loader计算Load_end_addr - Load_addr的长度,作为代码段的长度(text segment length),然后从header_offset + (header_addr - load_addr)开始(代码段在OS image文件的偏移text_seg_offset),拷贝text segment length长度的内容到Load_addr指向的物理内存,完成代码段的拷贝。
- Load_end_addr - 代码段结束地址。当该字段存在时,用于计算加载OS image代码段的长度;当该字段为零时,从text_seg_offset到文件结束都是代码段的内容,boot loader会全部加载。
- Bss_end_addr - bss段地址。如果该字段不为零,boot loader会将该地段指向的物理地址预留作OS的bss段使用
- Entry_addr - OS入口地址。完成整个OS image加载后,boot loader程序跳转的物理地址。
Graphics fields
- Graphics fields是OS image期望boot loader设置的显卡图形模式和具体的设置参数,即显卡的framebuffer参数,有4个字段:
- Mode_type - 期望boot loader设置的模式,0表示线性扫描模式,1表示EGA文本模式。
- Width - 期望boot loader设置的framebuffer行数
- Height - 期望boot loader设置的framebuffer列数
- Depth - 期望boot loader设置的framebuffer分辨率,即每个像素包含的bit
- 可以看到,Multiboot规范在设计OS image format这部分时,针对每个需求场景都考虑了对应的字段,但没有做扩展性设计,一旦有新的需求引入,基于现有的OS image format很难实现其功能。
Multiboot2
- Mulitboot2总结了Multiboot的缺点,设计OS image format时,在功能扩展性和架构扩展性等方面都做了对应考虑。Mulitboot2的OS image format格式设计如下:
- Mulitboot2设计了一个通用格式(tag)来表示OS image的需求信息,将每一类信息以tag的形式存放,用特殊的tag(end tag)标识结束。可以看到,Mulitboot中设计的address fields和graphics fields包含的信息被address tag、entry tag、framebuffer tag等tag提供了。同时,Mulitboot2在magic fields中还增加了架构相关的字段,对支持架构进行了扩展。
- 可以看到,Multiboot2不兼容Multiboot规范,因此基于Multiboot2实现的GRUB2也不兼容GRUB。
Machine state
- Boot loader在加载OS image到内存与跳转到entry address将控制权交给OS这个过程中,还有一些准备工作,包括向OS同步一些自己的状态,并向OS传递一些参数,从而实现对OS的动态配置。Boot loader利用CPU的寄存器完成这个工作。
Multiboot
Multiboot规范在设计时仅考虑了intel i386架构,因此它仅仅规定了如何使用i386CPU寄存器完成状态和参数传递。在交出控制权给OS之前,multiboot规范要求CPU具有如下状态:
- EAX
存放multiboot header的魔数,0x2BADB002,以此向OS表明它是被multiboot-compliant的boot loader引导的,而非其它boot loader。 - EBX
存放OS启动信息(Boot information)的物理地址,OS启动信息的格式参考下一小节 - CS
存放OS image代码段(code segment)物理地址 - DS
- ES
- SS
存放OS image数据段(data segment)物理地址 - A20 gate
设置为使能 - CR0
清零PG(Paging)位,禁止分页;使能PE(Protection Enable)位,开启页保护 - EFLAGS
清零VM(Virtual Real Mode)位,禁止进入虚拟实模式;清零IF(Interrupt Flag)位,屏蔽中断 - ESP
栈指针,由OS在启动后设置,boot loader不做设置 - GDTR
全局描述符表寄存器,当OS准备好全局描述描述表之后,设置该寄存器,boot loader不做设置 - IDTR
中断描述符表寄存器,当OS准备好中断描述符表之后,设置该寄存器,boot loader不做设置
Multiboot2
- Multiboot2规范在设计时考虑了对不同平台的支持,而不同平台的硬件寄存器不相同,因此Multiboot2分平台定义machine state,这里的平台包括但不限于MIPS,i386,EFI i386,EFI amd64等,其内容和Multiboot类似。如下:
MIPS machine state
i386 machine state
EFI i386 machine state
EFI amd64 machine state
Boot information format
- 上文提到EBX中存放的物理地址指向了boot loader传递给OS的参数信息,其格式即为Boot information format,OS按照该格式解析启动信息。
Multiboot
Multiboot2
- Multiboot定义的boot information format和OS image format相同,不具有扩展性,Multiboot2没有这一缺陷,它同样以tag为基本结构对boot information做描述,其格式如下: