一个启动载入器/OS映像接口主要是三个方面:
1 由启动载入器看到的一个OS映像的格式。
2 当启动载入器启动一个操作系统时,机器的状态。
3 由启动载入器传递给操作系统的信息的格式。
3.1. OS映像的格式
一个OS映像可能是一个,对于这个特定的操作系统而言,标准格式的普通32位可执行文件,除了它可能被链到一个非缺省的载入地址,以避免载入到PC的I/O区域的顶部或其它的保留区域,同样它不应该使用共享库或其它花哨的特性。
一个OS映像,除了该OS映像所使用文件格式的头部外,必须包含一个称为Multiboot头的额外的头部。Multiboot头部必须被完整包含 在OS映像的前8192字节内,而必须与长字(32位)对齐。一般而言,它应该尽早出现,并可能嵌入在可执行文件头部后,代码段的开头。
3.1.1. Multiboot头的布局
Multiboot头的布局必须如下:
偏移 类型 域名 注意
0 u32 magic 要求
4 u32 lags 要求
8 u32 checksum 要求
12 u32 header_addr 如果设置了flags[16]
16 u32 load_addr 如果设置了flags[16]
20 u32 load_end_addr 如果设置了flags[16]
24 u32 bss_end_addr 如果设置了flags[16]
28 u32 entry_addr 如果设置了flags[16]
32 u32 mode_type 如果设置了flags[2]
36 u32 width 如果设置了flags[2]
40 u32 height 如果设置了flags[2]
44 u32 depth 如果设置了flags[2]
域‘magic’,‘flags’及‘checksum’定义在3.1.2节【头部魔数域】,4页,域 ‘header_addr’,‘load_addr’,‘load_end_addr’,‘bss_end_addr’及‘entry_addr’定义在 3.1.3节【头部地址域】,5页,而域‘mode_type’,‘width’,‘height’及 ‘depth’定义在3.1.4节【头部图形化域】,5页。
3.1.2. Multiboot头部的魔数域
‘magic’ 域‘magic’是标识这个头的魔数,它必须是十六进制数0x1BADB002。
‘flags’ 域‘flags’指定OS映像所要求的特性,或对启动载入器的要求。0-15位显示要求;如果启动载入器看到这些比特中有比特被设置,但不能理解这个标 志,或出于某些原因不能满足其代表的要求,它必须通知用户,并且这个OS映像载入失败。16-31位显示可选的特性;如果这个区域内有比特被设置,但启动 载入器不能理解,它可能只是忽略它们,并如常处理。理所当然,所有‘flags’中尚未定义的比特,必须在OS映像中设置为0。这样,‘flags’域, 除了选择简单特性外,还可用于版本控制。
如果‘flags’字中的第0位被设置,那么所有与操作系统一起载入的启动模块必须在页(4KB)边界上对齐。某些操作系统在启动期间,能够直接把包含启动模块的页面映射到一个页对齐的地址,因此要求启动模块页对齐。
如果‘flags’字中的第1位被设置,那么可用内存的信息,至少是Multiboot 消息结构(参考3.3节【启动信息格式】,7页)的 ‘mem_*’ 域,被包括。 如果启动载入器能够传递一个内存映射图(‘mmap_*’域),而且这样的域存在,它也可能被包括。
如果‘flags’字中的第2位被设置,关于视频模式表(参考3.3节【启动信息格式】,7页)的信息必须对内核可用。如果‘flags’字中的第 6位被设置,那么在Multiboot头偏移为12-28处的域有效,启动载入器应该使用它们,而不是在实际可执行头中的域,来计算何处载入OS映像。如 果内核映像是elf格式,这个信息不需要提供,但如果内核是a.out格式或其它格式,则必须提供。兼容的启动载入器必须能载入elf格式的映像,或在 Multiboot头中嵌入载入地址信息的映像;它们可能也直接支持其它可执行格式,比如特定的a.out变体,不过这不要求。
‘checksum’
域‘checksum’是一个32位无符号值,当加上其它魔数域(即,‘magic’及‘flags’)时,其和必须是无符号32位数0。
3.1.3. Multiboot头的地址域
所有被flag第16位激活的地址域都是物理地址。每个的含义如下:
header_addr
包含对应于Multiboot头开始的地址——magic值被期望载入的物理内存位置。这个域用于同步OS映像偏移与物理内存地址间的映射。
load_addr
包含代码段开头的物理地址。在OS映像中,开始载入的偏移,由Multiboot头部所在偏移减去(header_addr – load_addr)来定义。Load_addr必须小于等于header_addr。
load_end_addr
包含数据段末尾的物理地址。(load_end_addr – load_addr)指出了要载入多少数据。这暗示在OS映像中代码段及数据段必须连续;对于现存的a.out可执行格式,这是成立的。如果这个域是0, 启动载入器就假定代码段与数据段占据了整个OS映像文件。
bss_end_addr
包含bss段末尾的物理地址。启动载入器初始化这个区域为0,并保留它所占据的内存,以避免在这个区域放入启动模块及与操作系统相关的其它数据。如果这个域是0,启动载入器就假定bss段没有出现。
entry_addr
为了开始运行操作系统,启动载入器应该跳转到的物理地址。
3.1.4. Multiboot头的图形化域
所有的图形化域被flag的第2位所激活。它们指定了首选的图形化模式。注意到这仅是OS映像的一个建议的模式。如果该模式存在,当用户没有显式指定一个模式时,启动载入器应该设置它。否则,如果可能,启动载入器应该进入一个类似的模式。
每个域的含义如下:
mode_type
对于线性图形模式,包含‘0’,或‘1’对于EGA-标准文本模式。余下为未来扩展保留。注意到启动载入器可能设置一个文本模式,即便这个域包含‘0’。
width 包含列的个数。在一个图形化模式中,这以像素为单位,在一个文本模式中,以字符为单位。值0表示OS映像没有偏好。
height 包含行的个数。在一个图形化模式中,这以像素为单位,在一个文本模式中,以字符为单位。值0表示OS映像没有偏好。
depth 包含,在一个图形化模式下,每个像素的比特数;而在一个文本模式中,这为0。值0表示OS映像没有偏好。
3.2. 机器状态
当启动载入器调用32位操作系统时,机器必须具有如下状态:
‘EAX’ 必须包含魔数‘0x2BADB002’;这个值的出现,向操作系统表示,它由一个Multiboot兼容的启动载入器载入(即,相对于可以载入该操作系统的,其它类型启动载入器)。
‘EBX’ 必须包含由启动载入器提供的,Multiboot信息结构的32位物理地址(参考3.3节【启动信息格式】,7页)。
‘CS 必须是一个偏移为‘0’,限长为‘0xFFFFFFFF’的32位读/执行代码段。具体值没有定义。
‘DS’
‘ES’
‘FS’
‘GS’
‘SS’ 必须是一个偏移为‘0’,限长为‘0xFFFFFFFF’的32位读/写数据段。具体值没有定义。
‘A20 gate’必须激活。
‘CR0’ 第31位(PG)必须被清除。第0位(PE)必须被设置。其它位未定义。
‘EFLAGS’
第17位(VM)必须被清除。第9位(IF)必须被清除。其它位未定义。
所有其它处理器寄存器及标记位都未定义。这包括,特别是:
‘ESP’ 一旦需要,OS映像必须构建自己的栈。
‘GDTR’ 即便段寄存器如上所示那样设置,‘GDTR’可能是无效的,因此OS映像必须不加载任何段寄存器(就算只是重载相同的值!),直到它设立自己的‘GDT’。
‘IDTR’ OS映像必须保持禁止中断,直到它设立自己的IDT。
然而,在正常的工作次序中,启动载入器不应该改动其它机器状态,即,如BIOS初始化的那样(或DOS,如果那是启动载入器运行的地方)。换而言 之,操作系统在载入后,应该能够进行诸如BIOS调用的操作,只要在这样做之前,它不改写BIOS数据结构。同样,启动载入器必须不改动以正常 BIOS/DOS值编写的PIC,即便在切换到32位模式过程中,它改变了它们。
3.3. 启动信息格式
进入到操作系统,EBX寄存器包含Multiboot信息数据结构的物理地址,通过它,启动载入器向操作系统传递重要信息。操作系统可以使用或忽略,所选择的该结构体的任意部分;由启动载入器传入的所有信息仅供参考。
Multiboot信息结构体及其相关的子结构体,可能被启动载入器放在内存的任一处(当然,为内核及启动模块所保留的内存是例外)。避免改写这个内存,直到它没有使用价值,这是操作系统的责任。
Multiboot信息结构(就目前的定义)的格式如下:
+----------------------+
0 | flags | (要求)
+----------------------+
4 | mem_lower | (如果设置了flags[0],出现)
8 | mem_upper | (如果设置了flags[0],出现)
+----------------------+
12 | boot_device |(如果设置了flags[1],出现)
+----------------------+
16 | cmdline | (如果设置了flags[2],出现)
+----------------------+
20 | mods_count |(如果设置了flags[3],出现)
24 | mods_addr | (如果设置了flags[3],出现)
+----------------------+
28 - 40 | syms | (如果设置了flags[4]或flags[5],出现)
+----------------------+
44 | mmap_length |(如果设置了flags[6],出现)
48 | mmap_addr |(如果设置了flags[6],出现)
+----------------------+
52 | drives_length |(如果设置了flags[7],出现)
56 | drives_addr |(如果设置了flags[7],出现)
+----------------------+
60 | config_table |(如果设置了flags[8],出现)
+----------------------+
64 | boot_loader_name|(如果设置了flags[9],出现)
+----------------------+
68 | apm_table |(如果设置了flags[10],出现)
+----------------------+
72 | vbe_control_info |(如果设置了flags[11],出现)
76 | vbe_mode_info |
80 | vbe_mode |
82 | vbe_interface_seg |
84 | vbe_interface_off |
86 | vbe_interface_len |
+----------------------+
第一个长字显示在Multiboot信息结构体中其它域存在及有效性。所有尚未定义的比特位必须由启动载入器设为0。操作系统不能理解的任意比特组应该被忽略。这样,域‘flags’还起到版本指示器的作用,允许Multiboot信息结构体在将来进行无损害的扩展。
如果在‘flags’中设置了第0位,那么域‘mem_*’是有效的。‘mem_lower’及‘mem_upper’分别表示低位内存和高位内存 的数量,以KB(kilobyte)为单位。低位内存从地址0开始,而高位内存从地址1M开始。低位内存最大的可能值是640KB。为高位内存所返回的值 最大是第一个高位内存空洞的地址减去1M。但不保证就是这个值。
如果在‘flags’中设置了第1位,那么域‘boot_device’是有效的,并显示从哪个BIOS硬盘设备上启动载入器载入OS映像。如果 OS映像不从一个BIOS硬盘上载入,那么这个域必须不出现(第3位必须被清除)。操作系统可能使用这个域作为确定其自己根设备的一个暗示,但它不要求这 样做。域‘boot_device’在4个1字节子域中布置如下:
+-------+-------+-------+-------+
| part3 | part2 | part1 | drive |
+-------+-------+-------+-------+
第一个字节包含BIOS设备号,它可被BIOS的INT 0x13底层接口所理解:即,0x00对应软盘,0x80对应第一个硬盘。
余下3个字节指定了启动分区。‘part1’指定最上层的分区号,‘part2’指出在最上层分区的一个子分区等。分区号总是从0开始。没有设置的 分区字节必须被设置为0xFF。例如,如果硬盘使用单层DOS分区规划,那么‘part1’包含DOS分区号,而‘part2’及 ‘part3’都是0xFF。另一个例子,如果一个硬盘首先划分为DOS分区,然后其中一个DOS分区被进一步使用BSD硬盘标签(disklabel) 策略划分为几个BSD分区,那么‘part1’包含这个DOS分区号,‘part2’包含在这个DOS分区中的BSD子分区,而‘part3’是 0xFF。
DOS扩展分区由从4开始的分区号表示,而不是作为嵌套子分区,即便扩展分区所立足的硬盘布局本质上是分层的。例如,如果启动载入器从,一个以传统DOS形式分区的硬盘的第二个扩展分区启动,那么‘part1’将是5,而‘part2’及‘part3’都将是0xFF。
如果在‘flags’中设置了第2位,域‘cmdline’是有效的,它包含了要传递给内核的命令行的物理地址。这个命令行是一个普通C样式的,以0结尾的字符串。
如果在‘flags’中设置了第3位,那么域‘mods’向内核显示,什么启动模块会伴随内核映像载入,以及可以在何处找到它们。 ‘mods_count’包含了载入模块的个数;‘mods_addr’包含了第一个模块结构的物理地址。‘mods_count’可能是0,表示不载入 启动模块,就是设置了‘flags’的第一位比特。每个模块结构体的格式如下:
+-------------------+
0 | mod_start |
4 | mod_end |
+-------------------+
8 | string |
+-------------------+
12 | reserved (0) |
+-------------------+
头两个域包含了启动模块本身开头与结尾的地址。域‘string’提供了一个任意的字符串来关联这个特定的启动模块;它是一个0结尾的ASCII字 符串,就像内核命令行。域‘string’可能是0,如果没有字符串可关联这个模块。通常,这个字符串可能是一个命令行(比如,如果操作系统把启动模块处 理作可执行程序),或一个路径名(比如,如果操作系统把启动模块处理作在一个文件系统中的文件),不过它确切的使用,取决于操作系统。域 ‘reserved’必须被启动载入器设置为0,并且为操作系统所忽略。
警告:第4与第5比特位是互斥的。
如果在‘flags’中设置了第4位,那么在Multiboot信息结构体中,从第28个字节开始的以下域是有效的:
+-------------------+
28 | tabsize |
32 | strsize |
36 | addr |
40 | reserved (0) |
+-------------------+
这些域表示何处可以找到,来自一个a.out内核映像的符号表。‘addr’是一个a.out格式的nlist结构体数组大小(4字节无符号长整 形)的物理地址,其后紧跟着这个数组本身,然后是一组以0结尾的ASCII字符串的大小(4字节无符号长整形,并加上sizeof(unsigned long)),最后是这组字符串本身。‘tabsize’是符号表大小的参数(在符号段开头找到),而‘strsize’是随后的符号表所引用的字符串表 大小的参数(在字符串段开头找到)。注意到‘tabsize’可能是0,表示没有符号,即便设置了‘flags’中的第4位。
如果在‘flags’中设置了第5位,那么在Multiboot信息结构体中,从第28个字节开始的以下域是有效的:
+-------------------+
28 | num |
32 | size |
36 | addr |
40 | shndx |
+-------------------+
这些域表示何处是来自一个ELF内核的段头表(section header table),每个项的大小,项的个数,及用作名字索引的字符串表。它们对应于在可执行及可链接格式(ELF)规范的程序头(program header)中的‘shdr_*’项(‘shdr_num’,等)。所有的段被载入,然后ELF段头的物理地址域指向内存中的段(如何读出段头的细节, 参考i386 ELF 文档)。注意到‘shdr_num’可能是0,表示没有符号,即便设置了‘flags’中的第5位。
如果在‘flags’中设置了第5位,那么域‘mmap_*’是有效的,表示包含由BIOS提供的一个机器内存映射的缓存的地址及长度。其 中,‘mmap_addr’是这个地址,而‘mmap_length’是这个缓存的总体大小。这个缓存由以下一个或多个size/structure对组 成(‘size’实际上用于跳到下一对):
+-------------------+
-4 | size |
+------------------+
0 | base_addr |
8 | length |
16 | type |
+-------------------+
其中‘size’是关联结构体的字节大小,它至少是20。‘base_addr’是起始地址。‘length’是该内存区域的字节大小。‘type’是所代表的地址区域的种类,其中值1表示为可用的ram,而当前其它值表示一个保留区域。
该映射保证列出所有可以正常使用的标准ram。
如果在‘flags’中设置了第7位,那么域‘drives_*’是有效的,表示第一个drive结构体的物理地址,及drive结构体的大小。 ‘drives_addr’是这个地址,而‘drives_length’是drive结构体的总大小。注意到‘drives_length’可以是0。 每个drive结构体的格式如下:
+-------------------+
0 | size |
+-------------------+
4 | drive_number |
+-------------------+
5 | drive_mode |
+-------------------+
6 | drive_cylinders |
8 | drive_heads |
9 |drive_sectors |
+--------------------+
10 - xx | drive_ports |
+--------------------+
域‘size’指定这个结构体的大小。这个大小,依赖于端口的数目,是可变的。注意到因为对齐的原因,这个大小可能不等于(10 + 2 * 端口的数目)。
域‘drive_number’包含了BIOS设备号。域‘drive_mode’代表由启动载入器使用的访问模式。当前,以下模式被定义:
‘0’ CHS 模式(传统的柱面/头/扇区(cylinder/head/sector)取址模式)。
‘1’ LBA 模式(逻辑块取址模式(Logical Block Addressing mode))。
‘drive_cylinders’,‘drive_heads’及‘drive_sectors’这三个域表示由BIOS检测出来的驱动的几何数 据。‘drive_cylinders’包含柱面数目。‘drive_heads’包含柱头个数。‘drive_sectors’包含每个磁道的扇区数。
域‘drive_ports’包含在BIOS代码中使用的I/O端口数组。这个数组由0个或以上的无符号2字节整数构成,以0结尾。注意到这个数组可能包含任意数目的,不与该驱动真正相关的I/O端口 (比如DMA控制器端口)。
如果在‘flags’中设置了第8位,那么域‘config_table’是有效的,它表示由GET CONFIGURATION BIOS调用返回的rom配置表的地址。如果这个BIOS调用失败,这个表的大小必须为0。
如果在‘flags’中设置了第9位,域‘boot_loader_name’是有效的,它包含了一个启动该内核的启动载入器名字的地址。这个名字是一个普通C-样式以0结尾的字符串。
如果在‘flags’中设置了第10位,域‘apm_table’是有效的,它包含了一个定义如下的APM表的物理地址:
+----------------------+
0 | version |
2 | cseg |
4 | offset |
8 | cseg_16 |
10 | dseg |
12 | flags |
14 | cseg_len |
16 | cseg_16_len |
18 | dseg_len |
+----------------------+
域 ‘version’,‘cseg’,‘offset’,‘cseg_16’,‘dseg’,‘flags’,‘cseg_len’,‘cseg_16_len’,‘dseg_len’ 分别表示版本号,32位保护模式代码段,入口点的偏移,16位保护模式代码段,16位保护模式数据段,flags,32位保护模式代码段的长度,16位保 护模式代码段的长度,16位保护模式数据段的长度。只有域‘offset’是4个字节,其它的都是2个字节。更多信息,参考先进电源管理 (APM)BIOS接口规范。
如果在‘flags’中设置了第11位,表示图形化表可用。如果内核已经在‘Multiboot Header’中表示,它接受一个图形模式,它才能被设置。
域‘vbe_control_info’及‘vbe_mode_info’分别包含了由VBE Function 00h返回的VNE控制信息的物理地址,及由VBE Function 01h返回的VBE模式信息。
域‘vbe_mode’表示在VBE 3.0中所规定格式中的当前视频模式。
余下的域‘vbe_interface_seg’,‘vbe_interface_off’,及‘vbe_interface_len’包含了定义 在VBE 2.0+中的一个保护模式接口表。如果这个信息不可用,这些域包含0。注意VBE 3.0定义了另一个保护模式接口,它与旧的不兼容。如果你希望使用新的保护模式接口,你将不得不自己查找这个表。
用于图形化表的域是为VBE设计的,但Multiboot启动载入器可能在非-VBE模式上模拟 VBE,仿佛它们就是VBE 模式。