Multiboot规范
Multiboot规范
本文定义了Multiboot规范——提议中的引导过程标准。本文是此规范的0.6.93版。
Multiboot规范简介
本章描述了一些关于Multiboot规范的粗略的信息。注意,这不是规范本身的一部分。
Multiboot规范诞生的背景
几乎每种现有的操作系统都拥有自己的引导程序。在机器上安装一个新的操作系统时通常意味着要引入一套全新的引导机制,每种的安装和运行界面都不相同。使多种操作系统相安无事地共存于一个机器上通常要借助于链式机制,这可是个恶梦。基本上你不能选择某个操作系统的引导程序——如果操作系统自带的引导程序不是你想要的,或者不能在你的机器上工作,你的麻烦可就大了。
尽管不太可能解决现有的商业操作系统所存在的问题,但是对于自由操作系统社区的人们来说,将他们的领袖集合在一起并为流行的自由操作系统解决这个问题并不是很难。这也正是这份规范的目的所在。基本上,它指出了引导程序和操作系统之间的接口,这样符合规范的引导程序就可以引导任何符合规范的操作系统。这份规范并不关心引导程序应该如何工作——只关心它们引导操作系统时的接口。
目标架构
这份规范主要面向PC,因为它们使用最广并且有最多的操作系统和引导程序。尽管如此,对于那些需要一份引导规范并且目前缺少一份的架构来说,剥去x86架构的相关细节得到版本也应该可以满足需要。
目标操作系统
这份规范的目标是自由的32位操作系统,因为应该可以比较容易获得修改这些操作系统以支持本规范的权力而不需要听满是官腔的喋喋不休。这份规范主要是面向Linux、FreeBSD、NetBSD、Mach和VSTa这些自由操作系统设计的。我们希望后来的自由操作系统能够从一开始就采用本规范,这样就可以立即使用现有的引导程序了。如果商业操作系统能够最终采用本规范当然很好,但是这很可能只是白日做梦。
引导源
实现一个可以从各种位置(软盘、硬盘或网络)载入OS映像的引导程序是现实可行的。
基于磁盘的引导程序可以使用各种技术查找位于磁盘上的OS映像和引导模块,例如解释某种文件系统(如BSD/Mach引导程序),使用预先计算好的block列表(如LILO),从特殊的引导分区载入(如OS/2),或者甚至从另一个操作系统载入(如VSTa引导代码,从DOS载入)。与此相似,基于网络的引导程序也可以使用各种网络硬件和协议。
我们希望引导程序可以支持多种载入机制,这样可以提供更好的可移植性、健壮性和易用性。
在引导时配置操作系统
总有这样或者那样的原因使得用户需要在启动时动态配置操作系统。尽管本规范不应该对引导程序如何获得这些配置信息指手划脚,我们还是应该为如何将这些信息传递给操作系统提供一种标准的方法。
如何使操作系统开发更容易
应尽量降低生成OS映象的难度。在理想情况下,OS映象应该是该操作系统通常使用的普通32位可执行文件格式。应该能够像对待普通可执行文件格式一样用nm
或者反汇编OS映象,而不应该用到特殊的工具来生成使用特殊文件格式的OS映象。如果这意味着将一部分的操作系统功能转移到引导程序中来的话,这很合适,因为任何引导程序用到的内存都应该可由它所创建的真正的系统自由使用,这样OS映象中的每一个比特都应该永远留在内存中。操作系统应该不必考虑如何进入32位地址模式,因为模式切换应该位于引导程序中,而这些程序通常需要将操作系统数据装入到1MB以上的内存,如果操作系统需要考虑这些问题的话创建OS映象的工作将变得更加困难。
不幸的是,仅在PC平台上的自由UNIX类系统中也有多得惊人的可执行文件格式——通常各种操作系统的格式都不相同。大多数的自由操作系统使用的是a.out格式的变种,但有一些已经改用了ELF格式。最好是引导程序不必为了载入OS映象而需要理解所有的可执行文件格式——否则的话引导程序又变成了某个操作系统专用的了。
这份规范采用了一种折衷的方案。符合Multiboot规范的OS映象总是包含一个magic Multiboot头(参见OS映像格式),这样引导程序就不必理解种类繁多的a.out变体或者其他什么可执行格式。magic头不必位于可执行文件的最前面,这样 OS 映象就可以在保持同a.out格式兼容的同时做到符合Multiboot规范。
引导模块
许多现代操作系统的内核,如VSTa和Mach,本身并不包括系统所有的功能:它们需要在引导时载入额外的软件模块以访问设备、挂载文件系统等。尽管这些额外的软件模块可以同内核一同嵌入到主OS映像中,并且在操作系统接管控制权时可以将映像分割为不同的部分,但如果引导程序能在一开始就分别的载入这些模块的话,对操作系统和用户来说就更灵活、更有空间效率并且很方便。
因此,本规范应该为引导程序提供一个标准的方法指示操作系统应该载入哪些辅助模块,以及应该将它们放在哪里。引导程序不一定非得支持多引导模块,但是我们强烈推荐它们这样,因为一些操作系统如果不这样就无法引导。
本Multiboot规范中所用术语的定义
-
必须
- 术语“必须 ”表示引导程序或OS映像需要服从某一规则,否则的话,引导程序或者OS映像就不能称为符合Multiboot规范的。 应该
- 术语“ 应该 ”表示引导程序或OS映像最好服从某一规则,但如果没有服从也是可以的。 可以
- 术语“ 可以”表示引导程序或OS映像服从某一规则是允许的。 引导程序
- 引导程序是负责载入最终的操作系统映像的一个或一组程序。引导程序本身可以由几个阶段组成,但是这属于实现细节而同本规范无关。只有引导程序最后的阶段——最终将控制权转交给操作系统的阶段——必须遵守本文中规定的规则,否则引导程序就不能称为符合Multiboot规范;在这之前的阶段可以怎么方便怎么设计。 OS 映像
-
OS映像是引导程序载入到内存的初始二进制映像,随后引导程序会将控制权转移给它,这样就启动了操作系统。典型的OS映像是包含了操作系统内核的可执行文件。
引导模块
-
引导模块是由引导程序同OS映像一同载入的其他起辅助作用的文件。引导程序并不理解这些文件,只会将它们的位置告知操作系统。
符合Multiboot规范
- 服从所有被标记为“必须”的引导程序或者OS映像被称为符合Multiboot规范。对于规范中那些被标记为“应该”或者“可以”的规则,符合Multiboot规范的引导程序或OS映像可以不必遵守。 u8
- 无符号8位数据。 u16
- 无符号16位数据。因为目标架构是高位优先(little-endian)的,所以u16按照高位优先编码。 u32
-
无符号32位数据。因为目标架构是高位优先(little-endian)的,所以u32按照高位优先编码。
u64
- 无符号64位数据。因为目标架构是高位优先(little-endian)的,所以u64按照高位优先编码。
Multiboot规范的精确定义
引导程序/OS映像接口主要包括三个方面:
- 引导程序看到的 OS 映像的格式。
- 当引导程序启动操作系统时机器的状态。
- 引导程序传递给操作系统的信息的格式。
OS映像格式
一个OS映像可以是一个普通的某种操作系统使用的标准格式的32位可执行文件,不同之处是它可能被连接到一个非默认的载入地址以避开PC的I/O区域或者其它的保留区域,当然它也不能使用共享库或其它这样可爱的东西。
除了OS映像所使用的格式需要的头之外,OS映像还必须额外包括一个Multiboot头。Multiboot头必须完整的包含在OS映像的前 8192 字节内,并且必须是longword(32位)对齐的。通常来说,它的位置越靠前越好,并且可以嵌入在text段的起始处,位于真正的可执行文件头之前。
Multiboot的头分布
Multiboot 头的分布必须如下表所示:
偏移量 | 类型 | 域名 | 备注 |
0 | u32 | magic | 必需 |
4 | u32 | flags | 必需 |
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域在头的magic域中定义,header_addr、load_addr、load_end_addr、bss_end_addr和entry_addr域在头的地址域中定义,mode_type
、width
、height
和depth
域则在头的图形域中定义。
Multiboot头的 magic 域
-
magic域是标志头的魔数,它必须等于十六进制值0x1BADB002。
-
flags域指出OS映像需要引导程序提供或支持的特性。0-15位指出需求:如果引导程序发现某些值被设置但出于某种原因不理解或不能不能满足相应的需求,它必须告知用户并宣告引导失败。16-31位指出可选的特性:如果引导程序不能支持某些位,它可以简单的忽略它们并正常引导。自然,所有flags字中尚未定义的位必须被置为0。这样,flags域既可以用于版本控制也可以用于简单的特性选择。
如果设置了flags字中的0位,所有的引导模块将按页(4KB)边界对齐。有些操作系统能够在启动时将包含引导模块的页直接映射到一个分页的地址空间,因此需要引导模块是页对齐的。
如果设置了flags字中的1位,则必须通过Multiboot信息结构(参见引导信息格式)的mem_*域包括可用内存的信息。如果引导程序能够传递内存分布(mmap_*域)并且它确实存在,则也包括它。
如果设置了flags字中的2位,有关视频模式表(参见引导信息格式)的信息必须对内核有效。
如果设置了flags字中的16位,则Multiboot头中偏移量8-24的域有效,引导程序应该使用它们而不是实际可执行头中的域来计算将OS映象载入到那里。如果内核映象为ELF格式则不必提供这样的信息,但是如果映象是a.out格式或者其他什么格式的话就必须提供这些信息。兼容的引导程序必须既能够载入ELF格式的映象也能载入将载入地址信息嵌入Multiboot头中的映象;它们也可以直接支持其他的可执行格式,例如一个a.out的特殊变体,但这不是必须的。
- 域checksum是一个32位的无符号值,当与其他的magic域(也就是magic和flags)相加时,结果必须是32位的无符号值0(即magic + flags + checksum = 0)。
magic
flags
checksum
Multiboot头的地址域
所有由flags的第16位开启的地址域都是物理地址。它们的意义如下:
-
包含对应于Multiboot头的开始处的地址——这也是magic值的物理地址。这个域用来
同步OS映象偏移量和物理内存之间的映射。
-
包含text段开始处的物理地址。从OS映象文件中的多大偏移开始载入由头位置的偏移量定义,相减(header_addr - load_addr)。load_addr必须小于等于header_addr。
-
包含data段结束处的物理地址。(load_end_addr - load_addr)指出了引导程序要载入多少数据。这暗示了text和data段必须在OS映象中连续;现有的a.out可执行格式满足这个条件。如果这个域为0,引导程序假定text和data段占据整个 OS 映象文件。
-
包含bss段结束处的物理地址。引导程序将这个区域初始化为0,并保留这个区域以免将引导模块和其他的于查系统相关的数据放到这里。如果这个域为0,引导程序假定没有bss段。
- 操作系统的入口点,引导程序最后将跳转到那里。
header_addr
load_addr
load_end_addr
bss_end_addr
entry_addr
Multiboot头的图形域
所有的图形域都通过flags的第2位开启。它们指出了推荐的图形模式。注意,这只是OS映象推荐的模式。如果该模式存在,引导程序将设定它,如果用户不明确指出另一个模式的话。否则,如果可能的话,引导程序将转入一个相似的模式。
他们的意义如下:
-
如果为0就代表线性图形模式,如果为1代表标准EGA文本模式。所有其他值保留以备将来扩展。注意即使这个域为0,引导程序也可能设置一个文本模式。
-
包含列数。在图形模式下它是象素数,在文本模式下它是字符数。0代表OS映象对此没有要求。
-
包含行数。在图形模式下它是象素数,在文本模式下它是字符数。0代表OS映象对此没有要求。
- 在图形模式下,包含每个象素的位数,在文本模式下为0。0代表OS映象对此没有要求。
mode_type
width
height
depth
机器状态
当引导程序调用32位操作系统时,机器状态必须如下:
-
必须包含魔数0x2BADB002;这个值指出操作系统是被一个符合Multiboot规范的引导程序载入的(这样就算是另一种引导程序也可以引导这个操作系统)。
-
必须包含由引导程序提供的Multiboot信息结构的物理地址(参见
引导信息格式)。
-
必须是一个偏移量位于0到0xFFFFFFFF之间的32位可读/可执行代码段。这里的精确值未定义。
-
必须是一个偏移量位于0到0xFFFFFFFF之间的32位可读/可执行代码段。这里的精确值未定义。
-
必须已经开启。
-
第31位(PG)必须为0。第0位(PE)必须为1。其他位未定义。
- 第17位(VM)必须为0。第9位(IF)必须为1 。其他位未定义。
EAX
EBX
CS
DS
ES
FS
GS
SS
A20 gate
CR0
EFLAGS
所有其他的处理器寄存器和标志位未定义。这包括:
-
当需要使用堆栈时,OS映象必须自己创建一个。
-
尽管段寄存器像上面那样定义了,GDTR也可能是无效的,所以OS映象决不能载入任何段寄存器(即使是载入相同的值也不行!)直到它设定了自己的GDT。
- OS映象必须在设置完它的IDT之后才能开中断。
ESP
GDTR
IDTR
尽管如此,其他的机器状态应该被引导程序留做正常的工作顺序,也就是同BIOS(或者DOS,如果引导程序是从那里启动的话)初始化的状态一样。换句话说,操作系统应该能够在载入后进行BIOS调用,直到它自己重写BIOS数据结构之前。还有,引导程序必须将PIC设定为正常的BIOS/DOS 状态,尽管它们有可能在进入32位模式时改变它们。
引导信息格式
FIXME:将这章像“OS映像格式”那样分解。
在进入操作系统时,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 | +-------------------+
第一个longword指出Multiboot信息结构中的其它域是否有效。所有目前未定义的位必须被引导程序设为0。操作系统应该忽略任何它不理解的位。因此,flags域也可以视作一个版本标志符,这样可以无破坏的扩展Multiboot信息结构。
如果设置了flags中的第0位,则mem_*域有效。mem_lower和mem_upper分别指出了低端和高端内存的大小,单位是K。低端内存的首地址是0,高端内存的首地址是1M。低端内存的最大可能值是640K。返回的高端内存的最大可能值是最大值减去1M。但并不保证是这个值。
如果设置了flags中的第1位,则boot_device域有效,并指出引导程序从哪个BIOS磁盘设备载入的OS映像。如果OS映像不是从一个BIOS磁盘载入的,这个域就决不能出现(第3位必须是0)。操作系统可以使用这个域来帮助确定它的root设备,但并不一定要这样做。boot_device域由四个单字节的子域组成:
+-------+-------+-------+-------+ | drive | part1 | part2 | part3 | +-------+-------+-------+-------+
第一个字节包含了BIOS驱动器号,它的格式与BIOS的INT0x13低级磁盘接口相同:例如,0x00代表第一个软盘驱动器,0x80代表第一个硬盘驱动器。
剩下的三个字节指出了引导分区。part1指出顶级分区号,part2指出一个顶级分区中的一个子分区,等等。分区号总是从0开始。不使用的分区字节必须被设为0xFF。例如,如果磁盘被简单的分为单一的一层DOS分区,则part1包含这个DOS分区号,part2和part3都是0xFF。另一个例子是,如果一个磁盘先被分为DOS分区,并且其中的一个DOS分区又被分为几个使用BSD磁盘标签策略的BSD分区,则part1包含DOS分区号,part2包含DOS分区内的BSD子分区,part3是0xFF。
DOS扩展分区的分区号从4开始,而不是像嵌套子分区一样,尽管扩展分区的底层分布就是分层嵌套的。例如,如果引导程序从传统的DOS风格磁盘的第二个分区启动,则part1是5,part2和part3都是0xFF。
如果设置了flags longword 的第2位,则cmdline域有效,并包含要传送给内核的命令行参数的物理地址。命令行参数是一个正常C风格的以0终止的字符串。
如果设置了flags的第3位,则mods域指出了同内核一同载入的有哪些引导模块,以及在哪里能找到它们。mods_count包含了载入的模块的个数;mods_addr包含了第一个模块结构的物理地址。mods_count可以是0,这表示没有载入任何模块,即使设置了flags的第1位时也有可能是这样。每个模块结构的格式如下:
+-------------------+ 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等于符号表的大小参数(位于符号section的头部),strsize等于符号表指向的字符串表的大小参数(位于string section的头部)。注意tabsize可以是0,这意味着没有符号,尽管已经设置了flags的第4位。
如果设置了flags的第5位,则下面从Multiboot信息结构的第28位开始的域是有效的:
+-------------------+ 28 | num | 32 | size | 36 | addr | 40 | shndx | +-------------------+
这指出在哪里可以找到 ELF 格式内核映像的section头表、每项的大小、一共有几项以及作为名字索引的字符串表。它们对应于可执行可连接格式(ELF)的program头中的shdr_*
项(shdr_num等)。所有的section都会被载入,ELF section头的物理地址域指向所有的section在内存中的位置(参见i386 ELF文档以得到如何读取section头的更多的细节)。注意,shdr_num可以是0,标志着没有符号,尽管已经设置了flags的第5位。
如果设置了flags的第 6 位,则mmap_*域是有效的,指出保存由BIOS提供的内存分布的缓冲区的地址和长度。mmap_addr是缓冲区的地址,mmap_length是缓冲区的总大小。缓冲区由一个或者多个下面的大小/结构对(size实际上是用来跳过下一个对的)组成的:
+-------------------+ -4 | size | +-------------------+ 0 | base_addr_low | 4 | base_addr_high | 8 | length_low | 12 | length_high | 16 | type | +-------------------+
size是相关结构的大小,单位是字节,它可能大于最小值20。base_addr_low是启动地址的低32位,base_addr_high是高32位,启动地址总共有64位。length_low是内存区域大小的低32位,length_high是内存区域大小的高32位,总共是64位。type是相应地址区间的类型,1代表可用RAM,所有其它的值代表保留区域。
可以保证所提供的内存分布列出了所有可供正常使用的标准内存。
如果设置了flags的第7位,则drives_*域是有效的,指出第一个驱动器结构的物理地址和这个结构的大小。drives_addr是地址,drives_length是驱动器结构的总大小。注意,drives_length可以是0。每个驱动器结构的格式如下:
+-------------------+ 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域代表了引导程序使用的访问模式。目前,模式定义如下:
-
CHS 模式(传统的“柱面/磁头/扇区”寻址模式)。
- LBA 模式(逻辑块寻址模式)。
0
1
这三个域,drive_cylinders、drive_heads和drive_sectors,指出了BIOS检测到的驱动器的参数。drive_cylinders包含柱面数,drive_heads包含磁头数,drive_sectors包含每磁道的扇区数。
drive_ports域包含了BIOS代码使用的I/O端口的数组。这个数组包含0个或者多个无符号两字节整数,并且以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位数据段、标志位、保护模式32位代码段的长度、保护模式16位代码段的长度和保护模式16位数据段的长度。只有offset域是4字节,其余的域都是2字节。参见高级电源管理(APM)BIOS接口规范。
域vbe_control_info和vbe_mode_info分别包含由VBE函数00h返回的VBE控制信息的物理地址和由VBE函数01h返回的VBE模式信息。
域vbe_mode指出了当前的显示模式,其中的信息符合VBE 3.0标准。
其余的域vbe_interface_seg、vbe_interface_off和vbe_interface_len包含了VBE 2.0+中定义的保护模式接口。如果没有这些信息,这些域都是0 。注意VBE 3.0定义了另一个保护模式接口,它与以前的版本是兼容的。如果你想要使用这些新的保护模式接口,你必须自己找到这个表。
graphics table中的域是按照VBE设计的,但是Multiboot引导程序可以在非VBE模式下模拟VBE模式。
节点: 示例, 前进: 历史, 后退: 规范, 向上: 顶层
示例
注意: 下面的内容不是规范文档的一部分,它们是给操作系统和引导程序编写者提供的示例。
PC机注记
在使用Multiboot信息结构中flags参数的第0位时,如果使用的引导程序使用较老的BIOS接口,或者还不被支持的最新的接口(参见有关第6位的描述),则返回的内存大小可能是15或者63M。因此强烈推荐引导程序进行彻底的内存检查。
在使用Multiboot信息结构中flags参数的第1位时,我们发现在最好的情况下,将哪个BIOS驱动器映射到哪个操作系统的设备驱动程序的决定也不容易做出。针对各种各样的操作系统提出了许多的笨拙的办法但都没有解决问题,大多数在很多情况下都会失败。为了鼓励使用通用的方法解决这个问题,我们提供了2种BIOS设备映射技术(参见BIOS设备映射技术)。
在使用 Multiboot信息结构中flags参数的第6位时,一定要注意这里用到的数据结构(自BaseAddrLow开始)时由INT 15h, AX=E820h——查询系统地址地图调用返回的数据。参见查询系统地址映射。这里的接口用来使一个引导程序可以不用修改的同进行过合理扩展的BIOS接口共同工作,如果这些扩展只是给予操作系统更多的信息的话。
BIOS设备映射技术
这两个技术应该可以用于任何的PC操作系统,并且也不需要驱动程序本身提供任何的特殊支持。本节将大量的讨论细节问题,尤其是I/O限制技术。
通用的规则是数据比较技术,它是快速但丑陋的解决方案。它在大多数情况下工作正常,但是并不总是这样,不过它相对简单。
I/O限制技术要复杂得多,但它更有可能在所有情况下解决问题,另外还允许在并非所有的BIOS设备拥有操作系统的驱动程序时访问有驱动程序的BIOS设备。
数据比较技术
在激活了设备驱动程序之后,使用操作系统驱动比较不同驱动器的数据。这样就可以为映射提供足够的信息。
问题:
- 一些BIOS设备上的数据可能是相同的(所以从BIOS读取设备信息的方法有可能失败)。
- 可能有一些BIOS不可访问的设备同BIOS用到的设备相同(所以这时这种方法也可能会宣告失败)。
节点: I/O限制技术, 后退: 数据比较技术, 向上: BIOS设备映射技术
I/O限制技术
对于每个设备驱动程序,决定哪个BIOS设备不可访问,方法是:
- 创建一个洁净的BIOS虚拟机。
- 将设备驱动程序要求的I/O区域在I/O允许位图中设置为无权限(既不能读也不能写)。
- 访问每个设备。
- 记录哪些设备访问成功,以及哪些试图访问受限I/O区域(这将可能是一个xor情景)。
对于每个设备驱动程序,假设已知其中有多少个BIOS设备(这个表中应该没有缝隙),应该很容易的确定哪些设备受这些控制器控制。
通常,每个拥有BIOS号的控制器上你至多有两个磁盘,它们总是从控制器逻辑号最低的设备数起。
节点: OS代码示例, 前进: 引导程序代码示例, 后退: BIOS设备映射技术, 向上: 示例
OS代码示例
在这个发行版中,包括了示例Multiboot内核kernel。这个内核只在屏幕上输出Multiboot信息结构,所以你可以利用这个内核检测一个Multiboot兼容的引导程序,或者作为如何实现一个Multiboot内核的参考。源文件可以在GRUB发行版的docs目录中找到。
内核kernel仅由三个文件组成:boot.S、kernel.c和multiboot.h。汇编源代码boot.S使用GAS汇编格式,包含符合本规范的Multiboot信息结构。当一个Multiboot兼容的引导程序载入并执行它时,它初始化堆栈指针和EFLAGS,然后调用kernel.c中定义的函数cmain。如果cmain返回,则它显示一条消息通知用户进入停机状态并停止知道你按下reset键。文件kernel.c包含函数cmain,它检查引导程序传递来的魔数是否有效等等,以及一些向屏幕输出消息的函数。文件multiboot.h定义了一些宏,如Multiboot头的魔数,Multiboot头结构和Multiboot信息结构等。
multiboot.h
这是 multiboot.h
文件中的源代码:
/* multiboot.h - Multiboot 的 header */ /* Copyright (C) 1999, 2001 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ /* 宏定义。 */ /* Multiboot header 的魔数。 */ #define MULTIBOOT_HEADER_MAGIC 0x1BADB002 /* Multiboot header 的标志。 */ #ifdef __ELF__ # define MULTIBOOT_HEADER_FLAGS 0x00000003 #else # define MULTIBOOT_HEADER_FLAGS 0x00010003 #endif /* Multiboot 兼容的引导程序传递来的魔数。 */ #define MULTIBOOT_BOOTLOADER_MAGIC 0x2BADB002 /* 堆栈大小 (16KB)。 */ #define STACK_SIZE 0x4000 /* C 符号格式。HAVE_ASM_USCORE 由 configure 定义。 */ #ifdef HAVE_ASM_USCORE # define EXT_C(sym) _ ## sym #else # define EXT_C(sym) sym #endif #ifndef ASM /* 不要在 boot.S 中 include 这里。 */ /* 类型定义。 */ /* Multiboot header。 */ typedef struct multiboot_header { unsigned long magic; unsigned long flags; unsigned long checksum; unsigned long header_addr; unsigned long load_addr; unsigned long load_end_addr; unsigned long bss_end_addr; unsigned long entry_addr; } multiboot_header_t; /* a.out 符号表。 */ typedef struct aout_symbol_table { unsigned long tabsize; unsigned long strsize; unsigned long addr; unsigned long reserved; } aout_symbol_table_t; /* ELF 的 section header table。 */ typedef struct elf_section_header_table { unsigned long num; unsigned long size; unsigned long addr; unsigned long shndx; } elf_section_header_table_t; /* Multiboot 信息。 */ typedef struct multiboot_info { unsigned long flags; unsigned long mem_lower; unsigned long mem_upper; unsigned long boot_device; unsigned long cmdline; unsigned long mods_count; unsigned long mods_addr; union { aout_symbol_table_t aout_sym; elf_section_header_table_t elf_sec; } u; unsigned long mmap_length; unsigned long mmap_addr; } multiboot_info_t; /* 模块结构。 */ typedef struct module { unsigned long mod_start; unsigned long mod_end; unsigned long string; unsigned long reserved; } module_t; /* 内存分布。小心,偏移量 0 是 base_addr_low 而不是 size 。 */ typedef struct memory_map { unsigned long size; unsigned long base_addr_low; unsigned long base_addr_high; unsigned long length_low; unsigned long length_high; unsigned long type; } memory_map_t; #endif /* ! ASM */
boot.S
文件 boot.S
的内容是:
/* boot.S - 引导内核 */ /* Copyright (C) 1999, 2001 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #define ASM 1 #include <multiboot.h> .text .globl start, _start start: _start: jmp multiboot_entry /* 32 位对齐。 */ .align 4 /* Multiboot header。 */ multiboot_header: /* magic */ .long MULTIBOOT_HEADER_MAGIC /* flags */ .long MULTIBOOT_HEADER_FLAGS /* checksum */ .long -(MULTIBOOT_HEADER_MAGIC + MULTIBOOT_HEADER_FLAGS) #ifndef __ELF__ /* header_addr */ .long multiboot_header /* load_addr */ .long _start /* load_end_addr */ .long _edata /* bss_end_addr */ .long _end /* entry_addr */ .long multiboot_entry #endif /* ! __ELF__ */ multiboot_entry: /* 初始化堆栈指针。 */ movl $(stack + STACK_SIZE), %esp /* 重置 EFLAGS。 */ pushl $0 popf /* 将指向 Multiboot 信息结构的指针入栈。 */ pushl %ebx /* 将魔数入栈。 */ pushl %eax /* 现在进入 C main 函数... */ call EXT_C(cmain) /* 停机。 */ pushl $halt_message call EXT_C(printf) loop: hlt jmp loop halt_message: .asciz "Halted." /* 我们的堆栈区。 */ .comm stack, STACK_SIZE
kernel.c
文件 kernel.c
中的内容:
/* kernel.c - 内核的 C 语言部分 */ /* Copyright (C) 1999 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include <multiboot.h> /* 宏定义。 */ /* 检测 FLAGS 中的位 BIT 是否被置位。 */ #define CHECK_FLAG(flags,bit) ((flags) & (1 << (bit))) /* 与显示相关的设置。 */ /* 列数。 */ #define COLUMNS 80 /* 行数。 */ #define LINES 24 /* 字符属性。 */ #define ATTRIBUTE 7 /* 显存地址。 */ #define VIDEO 0xB8000 /* 变量。 */ /* X 坐标。 */ static int xpos; /* Y 坐标。 */ static int ypos; /* 指向显存。 */ static volatile unsigned char *video; /* 前导声明。 */ void cmain (unsigned long magic, unsigned long addr); static void cls (void); static void itoa (char *buf, int base, int d); static void putchar (int c); void printf (const char *format, ...); /* 检查 MAGIC 是否有效并打印 ADDR 指向的 Multiboot 信息结构。 */ void cmain (unsigned long magic, unsigned long addr) { multiboot_info_t *mbi; /* 清屏。 */ cls (); /* 引导装载器是否符合 Multiboot 规范? */ if (magic != MULTIBOOT_BOOTLOADER_MAGIC) { printf ("Invalid magic number: 0x%x/n", (unsigned) magic); return; } /* 将 MBI 指向 Multiboot 信息结构。 */ mbi = (multiboot_info_t *) addr; /* 输出标志。 */ printf ("flags = 0x%x/n", (unsigned) mbi->flags); /* mem_* 是否有效? */ if (CHECK_FLAG (mbi->flags, 0)) printf ("mem_lower = %uKB, mem_upper = %uKB/n", (unsigned) mbi->mem_lower, (unsigned) mbi->mem_upper); /* boot_device 是否有效? */ if (CHECK_FLAG (mbi->flags, 1)) printf ("boot_device = 0x%x/n", (unsigned) mbi->boot_device); /* 是否有命令行参数? */ if (CHECK_FLAG (mbi->flags, 2)) printf ("cmdline = %s/n", (char *) mbi->cmdline); /* mods_* 是否有效? */ if (CHECK_FLAG (mbi->flags, 3)) { module_t *mod; int i; printf ("mods_count = %d, mods_addr = 0x%x/n", (int) mbi->mods_count, (int) mbi->mods_addr); for (i = 0, mod = (module_t *) mbi->mods_addr; i < mbi->mods_count; i++, mod += sizeof (module_t)) printf (" mod_start = 0x%x, mod_end = 0x%x, string = %s/n", (unsigned) mod->mod_start, (unsigned) mod->mod_end, (char *) mod->string); } /* 第 4 位和第 5 位是互斥的! */ if (CHECK_FLAG (mbi->flags, 4) && CHECK_FLAG (mbi->flags, 5)) { printf ("Both bits 4 and 5 are set./n"); return; } /* 是否有 a.out 符号表? */ if (CHECK_FLAG (mbi->flags, 4)) { aout_symbol_table_t *aout_sym = &(mbi->u.aout_sym); printf ("aout_symbol_table: tabsize = 0x%0x, " "strsize = 0x%x, addr = 0x%x/n", (unsigned) aout_sym->tabsize, (unsigned) aout_sym->strsize, (unsigned) aout_sym->addr); } /* 是否有 ELF section header table? */ if (CHECK_FLAG (mbi->flags, 5)) { elf_section_header_table_t *elf_sec = &(mbi->u.elf_sec); printf ("elf_sec: num = %u, size = 0x%x," " addr = 0x%x, shndx = 0x%x/n", (unsigned) elf_sec->num, (unsigned) elf_sec->size, (unsigned) elf_sec->addr, (unsigned) elf_sec->shndx); } /* mmap_* 是否有效? */ if (CHECK_FLAG (mbi->flags, 6)) { memory_map_t *mmap; printf ("mmap_addr = 0x%x, mmap_length = 0x%x/n", (unsigned) mbi->mmap_addr, (unsigned) mbi->mmap_length); for (mmap = (memory_map_t *) mbi->mmap_addr; (unsigned long) mmap < mbi->mmap_addr + mbi->mmap_length; mmap = (memory_map_t *) ((unsigned long) mmap + mmap->size + sizeof (mmap->size))) printf (" size = 0x%x, base_addr = 0x%x%x," " length = 0x%x%x, type = 0x%x/n", (unsigned) mmap->size, (unsigned) mmap->base_addr_high, (unsigned) mmap->base_addr_low, (unsigned) mmap->length_high, (unsigned) mmap->length_low, (unsigned) mmap->type); } } /* 清屏并初始化 VIDEO,XPOS 和 YPOS。 */ static void cls (void) { int i; video = (unsigned char *) VIDEO; for (i = 0; i < COLUMNS * LINES * 2; i++) *(video + i) = 0; xpos = 0; ypos = 0; } /* 将整数 D 转换为字符串并保存在 BUF 中。如果 BASE 为 'd',则 D 为十进制,如果 BASE 为 'x',则 D 为十六进制。 */ static void itoa (char *buf, int base, int d) { char *p = buf; char *p1, *p2; unsigned long ud = d; int divisor = 10; /* 如果指定了 %d 并且 D 是负数,在开始添上负号。 */ if (base == 'd' && d < 0) { *p++ = '-'; buf++; ud = -d; } else if (base == 'x') divisor = 16; /* 用 DIVISOR 去除 UD 直到 UD == 0。 */ do { int remainder = ud % divisor; *p++ = (remainder < 10) ? remainder + '0' : remainder + 'a' - 10; } while (ud /= divisor); /* 在字符串尾添上终结符。 */ *p = 0; /* 反转 BUF。 */ p1 = buf; p2 = p - 1; while (p1 < p2) { char tmp = *p1; *p1 = *p2; *p2 = tmp; p1++; p2--; } } /* 在屏幕上输出字符 C 。 */ static void putchar (int c) { if (c == '/n' || c == '/r') { newline: xpos = 0; ypos++; if (ypos >= LINES) ypos = 0; return; } *(video + (xpos + ypos * COLUMNS) * 2) = c & 0xFF; *(video + (xpos + ypos * COLUMNS) * 2 + 1) = ATTRIBUTE; xpos++; if (xpos >= COLUMNS) goto newline; } /* 格式化字符串并在屏幕上输出,就像 libc 函数 printf 一样。 */ void printf (const char *format, ...) { char **arg = (char **) &format; int c; char buf[20]; arg++; while ((c = *format++) != 0) { if (c != '%') putchar (c); else { char *p; c = *format++; switch (c) { case 'd': case 'u': case 'x': itoa (buf, c, *((int *) arg++)); p = buf; goto string; break; case 's': p = *arg++; if (! p) p = "(null)"; string: while (*p) putchar (*p++); break; default: putchar (*((int *) arg++)); break; } } } }
其他Multiboot内核
可以从Multiboot内核那里得到其它有用的信息,如GNU Mach和Fiasco <http://os.inf.tu-dresden.de/fiasco/>。最后,很有必要介绍一下OSKit <http://www.cs.utah.edu/projects/flux/oskit/>,它提供了一个支持本规范的库。
引导程序代码示例
<ftp://alpha.gnu.org/gnu/grub>
参见网页<http://www.gnu.org/software/grub/grub.html>,以得到更多信息。
节点: 历史, 前进: 索引, 后退: 示例, 向上: 顶层
本规范的修改日志
-
0.7
-
- Multiboot标准更名为Multiboot规范。
- 在Multiboot头加入了图形域。
- 在Multiboot信息中加入了BIOS驱动信息、BIOS配置表、引导程序名、APM信息及图形信息。
- 使用Texinfo格式重写了规范。
- 重写了规范,用于更严谨。
- 维护者由Bryan Ford和Erich Stefan Boleyn变更为GNU GRUB维护团队bug-grub@gnu.org。
0.6
-
- 修改了一些词汇。
- 头校验和。
- 对传递给操作系统的机器状态进行了分类。
0.5
-
- 名称变更。
0.4
-
- 添加了版本。