Multiboot规范

本文最后更新于:2022年6月8日 晚上

原英文 https://www.gnu.org/software/grub/manual/multiboot/multiboot.html

原文地址 blog.csdn.net

[TOC]

Multiboot 规范

Multiboot 规范

本文定义了 Multiboot 规范——提议中的引导过程标准。本文是此规范的 0.6.96 版。

  • 概览
  • 术语
  • 规范
  • 示例
  • 历史
  • 调用 mbchk:如何使用多重引导检查器
  • 索引

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 映像接口主要包括三个:

  1. 引导程序看到的 OS 映像的格式
  2. 引导程序启动操作系统时机器的状态
  3. 引导程序传递给操作系统的信息的格式
  • OS 映像格式

  • 机器状态

  • 引导信息格式

OS 映像格式

一个 OS 映像可以是一个普通的某种操作系统使用的标准格式的 32 位可执行文件,不同之处是它可能被连接到一个非默认的载入地址以避开 PC 的 I/O 区域或者其它的保留区域,当然它也不能使用共享库或其它这样可爱的东西。

除了 OS 映像所使用的格式需要的头之外,OS 映像还必须额外包括一个 Multiboot 头Multiboot 头必须完整的包含在 OS 映像的前 8192 字节内,并且必须是 longword(32 位)对齐的。通常来说,它的位置越靠前越好,并且可以嵌入在 text 段的起始处,位于真正的可执行文件头之前。

  • 头分布: Multiboot Header 分布

  • 头的幻数域: Multiboot Header magic 域

  • 头的地址域: Multiboot Header address域

  • 头的图形域: Multiboot Header graphics域

Multiboot 的 Header 分布

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] 被置位

magicflagschecksum 域在Header magic 域中定义,

header_addrload_addrload_end_addrbss_end_addrentry_addr 域在Header address域中定义,

mode_typewidthheightdepth域则在Header graphics 域中定义。

Multiboot Header magic 域

magic

​ magic 域是标志头的魔数,它必须等于十六进制值 0x1BADB002。

flags

​ 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

​ 域 checksum 是一个 32 位的无符号值,当与其他的 magic 域(也就是 magic 和 flags)相加时,结果必须是 32 位的无符号值 0(即 magic + flags + checksum = 0)。

Multiboot Header address 域

所有由 flags 的第 16 位开启的地址域都是物理地址。它们的意义如下:

header_addr

包含对应于 Multiboot 头的开始处的地址——这也是 magic 值的物理地址。这个域用来 同步 OS 映象偏移量和物理内存之间的映射。

load_addr

包含 text 段开始处的物理地址。从 OS 映象文件中的多大偏移开始载入由头位置的偏移量定义,相减(header_addr - load_addr)。load_addr 必须小于等于 header_addr。

load_end_addr

包含 data 段结束处的物理地址。(load_end_addr - load_addr)指出了引导程序要载入多少数据。这暗示了 text 和 data 段必须在 OS 映象中连续;现有的 a.out 可执行格式满足这个条件。如果这个域为 0,引导程序假定 text 和 data 段占据整个 OS 映象文件。

bss_end_addr

包含 bss 段结束处的物理地址。引导程序将这个区域初始化为 0,并保留这个区域以免将引导模块和其他的于查系统相关的数据放到这里。如果这个域为 0,引导程序假定没有 bss 段。

entry_addr

操作系统的入口点,引导程序最后将跳转到那里。

Multiboot 头的图形域

所有的图形域都通过 flags 的第 2 位开启。它们指出了推荐的图形模式。注意,这只是 OS 映象推荐的模式。如果该模式存在,引导程序将设定它,如果用户不明确指出另一个模式的话。否则,如果可能的话,引导程序将转入一个相似的模式。

他们的意义如下:

mode_type

如果为 0 就代表线性图形模式,如果为 1 代表标准 EGA 文本模式。所有其他值保留以备将来扩展。注意即使这个域为 0,引导程序也可能设置一个文本模式。

width

包含列数。在图形模式下它是象素数,在文本模式下它是字符数。0 代表 OS 映象对此没有要求。

height

包含行数。在图形模式下它是象素数,在文本模式下它是字符数。0 代表 OS 映象对此没有要求。

depth

在图形模式下,包含每个象素的位数,在文本模式下为 0。0 代表 OS 映象对此没有要求。

机器状态

当引导程序调用 32 位操作系统时,机器状态必须如下:

EAX

必须包含魔数 0x2BADB002;这个值指出操作系统是被一个符合 Multiboot 规范的引导程序载入的(这样就算是另一种引导程序也可以引导这个操作系统)。

EBX

必须包含由引导程序提供的 Multiboot 信息结构的物理地址(参见 引导信息格式)。

CS

必须是一个偏移量位于 0 到 0xFFFFFFFF 之间的 32 位可读 / 可执行代码段。这里的精确值未定义。

DS

ES

FS

GS

SS

必须是一个偏移量位于 0 到 0xFFFFFFFF 之间的 32 位可读 / 可执行代码段。这里的精确值未定义。

A20 gate

必须已经开启。

CR0

第 31 位(PG)必须为 0。第 0 位(PE)必须为 1。其他位未定义。

EFLAGS

第 17 位(VM)必须为 0。第 9 位(IF)必须为 1 。其他位未定义。

所有其他的处理器寄存器和标志位未定义。这包括:

ESP

当需要使用堆栈时,OS 映象必须自己创建一个。

GDTR

尽管段寄存器像上面那样定义了,GDTR 也可能是无效的,所以 OS 映象决不能载入任何段寄存器(即使是载入相同的值也不行!)直到它设定了自己的 GDT。

IDTR

OS 映象必须在设置完它的 IDT 之后才能开中断。

尽管如此,其他的机器状态应该被引导程序留做正常的工作顺序,也就是同 BIOS(或者 DOS,如果引导程序是从那里启动的话)初始化的状态一样。换句话说,操作系统应该能够在载入后进行 BIOS 调用,直到它自己重写 BIOS 数据结构之前。还有,引导程序必须将 PIC 设定为正常的 BIOS/DOS 状态,尽管它们有可能在进入 32 位模式时改变它们。

引导信息格式

FIXME:将这章像 “OS 映像格式” 那样分解。

在进入操作系统时,EBX 寄存器包含 Multiboot 信息数据结构的物理地址,引导程序通过它将重要的引导信息传递给操作系统。操作系统可以按自己的需要使用或者忽略任何部分;所有的引导程序传递的信息只是建议性的。

Multiboot 信息结构和它的相关的子结构可以由引导程序放在任何位置(当然,除了保留给内核和引导模块的区域)。如何在利用之前保护它是操作系统的责任。

Multiboot 信息结构(就目前为止定义的)的格式如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
        +-------------------+
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 域由四个单字节的子域组成:

1
2
3
4
+-------+-------+-------+-------+
| part3 | part2 | part1 | drive |
+-------+-------+-------+-------+
最低有效字节 最高有效字节

最高有效字节字节包含了 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 位时也有可能是这样。每个模块结构的格式如下:

1
2
3
4
5
6
7
8
        +-------------------+
0 | mod_start |
4 | mod_end |
+-------------------+
8 | string |
+-------------------+
12 | reserved (0) |
+-------------------+

前两个域包含了引导模块的开始和结束地址。string 域提供了一个自定义的与引导模块相关的字符串;它是以 0 中止的 ASCII 字符串,同内核命令行参数一样。如果没有什么与模块有关的字符串,string 域可以是 0。典型情况下,这个字符串也许是命令行参数(例如,如果操作系统将引导模块视作可执行程序的话),或者一个路径名(例如,如果操作系统将引导模块视作文件系统中的文件的话),它的意义取决于操作系统。reserved 域必须由引导程序设为 0 并被操作系统忽略。

注意:第 4 位和第 5 位是互斥的。

如果设置了 flags 的第 4 位,则下面从 Multiboot 信息结构的第 28 位开始的域是有效的:

1
2
3
4
5
6
        +-------------------+
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 位开始的域是有效的:

1
2
3
4
5
6
        +-------------------+
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 实际上是用来跳过下一个对的)组成的:

1
2
3
4
5
6
7
8
9
        +-------------------+
-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。每个驱动器结构的格式如下:  

1
2
3
4
5
6
7
8
9
10
11
12
13
        +-------------------+
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 模式(传统的 “柱面 / 磁头 / 扇区” 寻址模式)。  

1

​ LBA 模式(逻辑块寻址模式)。

这三个域,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 表的物理地址:

1
2
3
4
5
6
7
8
9
10
11
        +----------------------+
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 接口规范

如果设置了 flags 的第 11 位,则 graphics table 有效。前提是内核已经在 Multiboot 头中指定了一种图形模式。

域 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 机注记

  • BIOS 设备映射技术

  • OS 代码示例

  • 引导程序代码示例

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 设备。

  • 数据比较技术

  • I/O 限制技术

数据比较技术

在激活 _任何_设备驱动程序之前,要从每个磁盘上的相似扇区中收集足够的数据,这样就可以区分每个扇区。

在激活了设备驱动程序之后,使用操作系统驱动比较不同驱动器的数据。这样就可以为映射提供足够的信息。

问题:

  1. 一些 BIOS 设备上的数据可能是相同的(所以从 BIOS 读取设备信息的方法有可能失败)。
  2. 可能有一些 BIOS 不可访问的设备同 BIOS 用到的设备相同(所以这时这种方法也可能会宣告失败)。

I/O 限制技术

第一步并不是必要的,但首先为设备驱动程序创建 copy-on-write 映射。随后创建 洁净 BIOS 虚拟机的原始拷贝。

对于每个设备驱动程序,决定哪个 BIOS 设备不可访问,方法是:

  1. 创建一个洁净的 BIOS 虚拟机。
  2. 将设备驱动程序要求的 I/O 区域在 I/O 允许位图中设置为无权限(既不能读也不能写)。
  3. 访问每个设备。
  4. 记录哪些设备访问成功,以及哪些试图访问受限 I/O 区域(这将可能是一个 xor 情景)。

对于每个设备驱动程序,假设已知其中有多少个 BIOS 设备(这个表中应该没有缝隙),应该很容易的确定哪些设备受这些控制器控制。

通常,每个拥有 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

  • boot.S

  • kernel.c

  • 其他 Multiboot 内核

multiboot.h

这是 multiboot.h 文件中的源代码:  

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
/* 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 的内容是:  

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
/* 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 中的内容:  

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
/* 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/>,它提供了一个支持本规范的库。

引导程序代码示例

GNU GRUB 项目是一个完全支持的 Multiboot 的引导程序,支持本规范中所有必需的和可选的特性。目前并没有公开的发行版,但是可以从这里得到:

<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

  • 添加了版本。

索引

目录

  • Multiboot 规范
  • Multiboot 规范简介
    • Multiboot 规范诞生的背景
    • 目标架构
    • 目标操作系统
    • 引导源
    • 在引导时配置操作系统
    • 如何使操作系统开发更容易
    • 引导模块
  • 本 Multiboot 规范中所用术语的定义
  • Multiboot 规范的精确定义
    • OS 映像格式
      • Multiboot 头的分布
      • Multiboot 头的 magic 域
      • Multiboot 头的地址域
      • Multiboot 头的图形域
    • 机器状态
    • 引导信息格式
  • 示例
    • PC 机注记
    • BIOS 设备映射技术
      • 数据比较技术
      • I/O 限制技术
    • OS 代码示例
      • multiboot.h
      • boot.S
      • kernel.c
      • 其他 Multiboot 内核
    • 引导程序代码示例
  • 本规范的修改日志
  • 索引

Multiboot规范
https://www.glj0.top/posts/4e0f05fa/
作者
gong lj
发布于
2022年3月1日
更新于
2022年6月8日
许可协议