本文定义了Multiboot规范——提议中的引导过程标准。本文是此规范的0.6.93版。
本章描述了一些关于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映像中,并且在操作系统接管控制权时可以将映像分割为不同的部分,但如果引导程序能在一开始就分别的载入这些模块的话,对操作系统和用户来说就更灵活、更有空间效率并且很方便。
因此,本规范应该为引导程序提供一个标准的方法指示操作系统应该载入哪些辅助模块,以及应该将它们放在哪里。引导程序不一定非得支持多引导模块,但是我们强烈推荐它们这样,因为一些操作系统如果不这样就无法引导。
引导程序/OS映像接口主要包括三个方面:
一个OS映像可以是一个普通的某种操作系统使用的标准格式的32位可执行文件,不同之处是它可能被连接到一个非默认的载入地址以避开PC的I/O区域或者其它的保留区域,当然它也不能使用共享库或其它这样可爱的东西。
除了OS映像所使用的格式需要的头之外,OS映像还必须额外包括一个Multiboot头。Multiboot头必须完整的包含在OS映像的前 8192 字节内,并且必须是longword(32位)对齐的。通常来说,它的位置越靠前越好,并且可以嵌入在text段的起始处,位于真正的可执行文件头之前。
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
域则在头的图形域中定义。
magic
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
所有由flags的第16位开启的地址域都是物理地址。它们的意义如下:
header_addr
load_addr
load_end_addr
bss_end_addr
entry_addr
所有的图形域都通过flags的第2位开启。它们指出了推荐的图形模式。注意,这只是OS映象推荐的模式。如果该模式存在,引导程序将设定它,如果用户不明确指出另一个模式的话。否则,如果可能的话,引导程序将转入一个相似的模式。
他们的意义如下:
mode_type
width
height
depth
当引导程序调用32位操作系统时,机器状态必须如下:
EAX
EBX
CS
DS
ES
FS
GS
SS
A20 gate
CR0
EFLAGS
所有其他的处理器寄存器和标志位未定义。这包括:
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域代表了引导程序使用的访问模式。目前,模式定义如下:
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模式。
注意: 下面的内容不是规范文档的一部分,它们是给操作系统和引导程序编写者提供的示例。
在使用Multiboot信息结构中flags参数的第0位时,如果使用的引导程序使用较老的BIOS接口,或者还不被支持的最新的接口(参见有关第6位的描述),则返回的内存大小可能是15或者63M。因此强烈推荐引导程序进行彻底的内存检查。
在使用Multiboot信息结构中flags参数的第1位时,我们发现在最好的情况下,将哪个BIOS驱动器映射到哪个操作系统的设备驱动程序的决定也不容易做出。针对各种各样的操作系统提出了许多的笨拙的办法但都没有解决问题,大多数在很多情况下都会失败。为了鼓励使用通用的方法解决这个问题,我们提供了2种BIOS设备映射技术(参见BIOS设备映射技术)。
在使用 Multiboot信息结构中flags参数的第6位时,一定要注意这里用到的数据结构(自BaseAddrLow开始)时由INT 15h, AX=E820h——查询系统地址地图调用返回的数据。参见查询系统地址映射。这里的接口用来使一个引导程序可以不用修改的同进行过合理扩展的BIOS接口共同工作,如果这些扩展只是给予操作系统更多的信息的话。
这两个技术应该可以用于任何的PC操作系统,并且也不需要驱动程序本身提供任何的特殊支持。本节将大量的讨论细节问题,尤其是I/O限制技术。
通用的规则是数据比较技术,它是快速但丑陋的解决方案。它在大多数情况下工作正常,但是并不总是这样,不过它相对简单。
I/O限制技术要复杂得多,但它更有可能在所有情况下解决问题,另外还允许在并非所有的BIOS设备拥有操作系统的驱动程序时访问有驱动程序的BIOS设备。
在激活了设备驱动程序之后,使用操作系统驱动比较不同驱动器的数据。这样就可以为映射提供足够的信息。
问题:
对于每个设备驱动程序,决定哪个BIOS设备不可访问,方法是:
对于每个设备驱动程序,假设已知其中有多少个BIOS设备(这个表中应该没有缝隙),应该很容易的确定哪些设备受这些控制器控制。
通常,每个拥有BIOS号的控制器上你至多有两个磁盘,它们总是从控制器逻辑号最低的设备数起。
在这个发行版中,包括了示例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_HEADER_MAGIC0x1BADB002 MULTIBOOT_HEADER_FLAGS0x00000003MULTIBOOT_HEADER_FLAGS0x00010003 MULTIBOOT_BOOTLOADER_MAGIC0x2BADB002 STACK_SIZE0x4000 HAVE_ASM_USCOREEXT_C(sym)_ ## sym EXT_C(sym)symASMtypedef 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; typedef struct aout_symbol_table { unsigned long tabsize; unsigned long strsize; unsigned long addr; unsigned long reserved; } aout_symbol_table_t; typedef struct elf_section_header_table { unsigned long num; unsigned long size; unsigned long addr; unsigned long shndx; } elf_section_header_table_t; 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; 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;
文件 boot.S
的内容是:
ASM 1.text .globl start, _start start: _start: jmp multiboot_entry .align 4 multiboot_header: .long MULTIBOOT_HEADER_MAGIC .long MULTIBOOT_HEADER_FLAGS .long -(MULTIBOOT_HEADER_MAGIC + MULTIBOOT_HEADER_FLAGS) __ELF__ .long multiboot_header .long _start .long _edata .long _end .long multiboot_entry multiboot_entry: movl $(stack + STACK_SIZE), %esp pushl $0 popf pushl %ebx pushl %eax call EXT_C(cmain) pushl $halt_message call EXT_C(printf) loop: hlt jmp loop halt_message: .asciz .comm stack, STACK_SIZE
文件 kernel.c
中的内容:
CHECK_FLAG(flags,bit) ((flags) & (1 << (bit))) COLUMNS 80 LINES 24 ATTRIBUTE 7 VIDEO 0xB8000 static int xpos; 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, ...); void cmain (unsigned long magic, unsigned long addr) { multiboot_info_t *mbi; cls (); if (magic != MULTIBOOT_BOOTLOADER_MAGIC) { printf (, (unsigned) magic); return; } mbi = (multiboot_info_t *) addr; printf (, (unsigned) mbi->flags); if (CHECK_FLAG (mbi->flags, 0)) printf (, (unsigned) mbi->mem_lower, (unsigned) mbi->mem_upper); if (CHECK_FLAG (mbi->flags, 1)) printf (, (unsigned) mbi->boot_device); if (CHECK_FLAG (mbi->flags, 2)) printf (, (char *) mbi->cmdline); if (CHECK_FLAG (mbi->flags, 3)) { module_t *mod; int i; printf (, (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 (, (unsigned) mod->mod_start, (unsigned) mod->mod_end, (char *) mod->string); } if (CHECK_FLAG (mbi->flags, 4) && CHECK_FLAG (mbi->flags, 5)) { printf (); return; } if (CHECK_FLAG (mbi->flags, 4)) { aout_symbol_table_t *aout_sym = &(mbi->u.aout_sym); printf ( , (unsigned) aout_sym->tabsize, (unsigned) aout_sym->strsize, (unsigned) aout_sym->addr); } if (CHECK_FLAG (mbi->flags, 5)) { elf_section_header_table_t *elf_sec = &(mbi->u.elf_sec); printf ( , (unsigned) elf_sec->num, (unsigned) elf_sec->size, (unsigned) elf_sec->addr, (unsigned) elf_sec->shndx); } if (CHECK_FLAG (mbi->flags, 6)) { memory_map_t *mmap; printf (, (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 ( , (unsigned) mmap->size, (unsigned) mmap->base_addr_high, (unsigned) mmap->base_addr_low, (unsigned) mmap->length_high, (unsigned) mmap->length_low, (unsigned) mmap->type); } } 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; } static void itoa (char *buf, int base, int d) { char *p = buf; char *p1, *p2; unsigned long ud = d; int divisor = 10; if (base == && d < 0) { *p++ = ; buf++; ud = -d; } else if (base == ) divisor = 16; do { int remainder = ud % divisor; *p++ = (remainder < 10) ? remainder + : remainder + - 10; } while (ud /= divisor); *p = 0; p1 = buf; p2 = p - 1; while (p1 < p2) { char tmp = *p1; *p1 = *p2; *p2 = tmp; p1++; p2--; } } static void putchar (int c) { if (c == || c == ) { 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; } 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 : case : case : itoa (buf, c, *((int *) arg++)); p = buf; goto string; break; case : p = *arg++; if (! p) p = ; string: while (*p) putchar (*p++); break; default: putchar (*((int *) arg++)); break; } } } }
可以从Multiboot内核那里得到其它有用的信息,如GNU Mach和Fiasco
参见网页