最后更新于2020年6月7日。
Multiboot与GRUB
- 一、Multiboot2规范 v2.0
- 1、介绍
- 2、措辞与术语
- 3、`Multiboot2`规范的确切定义
- 3.1、操作系统映像格式
- 3.1.1、`Multiboot2`头的定义
- 3.1.2、`Multiboot2`头中的字段
- 3.1.3、标签`tag`结构体
- 3.1.4、`Multiboot2` information request
- 3.1.5、`Multiboot2`头中的地址标签`tag`
- 3.1.6、`Multiboot2`头中的项目地址Tag
- 3.1.7、`Multiboot2`头中的EFI i386项目地址Tag
- 3.1.8、`Multiboot2`头中的EFI amd64项目地址Tag
- 3.1.9、标志Tag
- 3.1.10、帧缓冲Tag
- 3.1.11、模块对齐Tag
- 3.1.12、EFI启动服务Tag
- 3.1.13、重定位头Tag
- 3.2、MIPS机器状态
- 3.3、I386机器状态
- 3.4、具有可启动服务的EFI i386机器状态
- 3.5、具有可启动服务的EFI amd64机器状态
- 3.6、启动信息格式
- 3.6.1、启动信息格式
- 3.6.2、基本标签`tag`结构体
- 3.6.3、基本内存信息
- 3.6.4、BIOS启动设备
- 3.6.5、启动命令行
- 3.6.6、模块
- 3.6.7、ELF节头表
- 3.6.8、内存映射
- 3.6.9、引导加载器名称
- 3.6.10、APM表
- 3.6.11、VBE信息
- 3.6.12、帧缓冲信息
- 3.6.13、ELF32位系统表指针
- 3.6.14、ELF64位系统表指针
- 3.6.15、SMBIOS表
- 3.6.16、旧RSDP(ACPI 1.0)
- 3.6.17、新RSDP(ACPI 2.0及以上)
- 3.6.18、网络信息
- 3.6.19、EFI内存映射
- 3.6.20、EFI启动服务未被终止
- 3.6.21、EFI32位映像处理指针
- 3.6.22、EFI64位映像处理指针
- 3.6.23、映像加载物理基地址
- 4、示例
- 5、本规范更新日志
- 6、索引
- 二、GRUB
- 三、代码实例
一、Multiboot2规范 v2.0
- 本规范是汉化版本,最后更新于2020年6月1日。由于能力有限,翻译可能存在错误。现附上规范原文The Multiboot2 Specification version 2.0
,如果错误之处,请指正。 - 本翻译先通过金山词霸在线翻译,然后对机器翻译的结果进行调整。译者尽可能从原始的角度翻译原规范,有一部分内容是从理解的角度翻译的,如果翻译有误,请遵照原文。
1、介绍
本章粗略地描述了关于Multiboot2
规范的一些信息。 请注意,这不是规范本身的一部分。
1.1、背景内容
当前设计的每个操作系统可能都有其自己的引导程序。在机器上安装一个新的操作系统通常需要安装一套全新的引导机制,这些引导机制在安装过程中及引导过程都有一个相互完全不同用户界面。在一台机器上安装有多个操作系统,如果用典型的链式引导机制让这些操作系统可靠地共存,这是不现实的。对于一个特定的操作系统来说,可选的引导加载器不是很多,甚至根本没有可选的解决方案。如果不能按照您的要求随心所欲地使用操作系统随附的引导加载器,或者引导加载器无法在您的机器上工作,这个时候您会感到非常困惑甚至无所适从。
因为版权问题,我们可能无法在私有操作系统中解决这个问题。但对于自由操作系统社区的一些人来说,他们可以通过各种方式聚在一起为流行的自由操作系统解决此类问题不是很困难。这就是制定这个规范的目的所在。基本上,该规范指定了引导加载程序与操作系统之间的接口,这样任何符合规范的引导加载程序都应该能够加载任何符合的操作系统。本规范没有指定引导加载程序应该如何工作,只是规定了它们必须如何与正在加载的操作系统一起使用该接口。
1.2、目标架构
此规范主要针对PC,因为它们是最常见的,同时在其上运行着大量的操作系统和引导程序。 但是,如果需要在其他体系结构上引导内核,而在该架构上还没有合适的引导规范,则可以将本规范中特定于x86平台的细节去掉并做出适当的调整,即可将本规范适应于特定的体系架构。
1.3、目标系统
本规范主要针对的是自由的32为操作系统。这些操作系统不经过大幅度调整,就可以很容易地进行修改以支持规范。本规范主要是为Linux、Free BSD、Net BSD、Mach以及VSTa等操作系统或内核设计,同时也希望其他新兴的自由操作系统从一开始就采用它,从而能使用现有的引导加载器。很高兴能有私有操作系统供应商最终也采用这个规范,但这可能不太现实。
1.4、启动方式
应该编写可以兼容的引导加载程序,这样操作系统镜像就可以从诸如软盘、硬盘,甚至网络等不同来源被加载。
基于磁盘的引导加载器可以使用各种技术来查找磁盘上的操作系统映像和引导模块。例如,可以通过特定的文件系统加载系统(BSD/Mach 引导加载器);也可以使用预先规定好的磁盘块列表加载系统(LILO);还可以从特定的启动分区加载系统(OS/2);甚至,还可以另一个操作系统中加载系统(例如,可以从DOS中加载VSTa引导代码);类似地,基于网络的引导加载器可以使用各种不同的网络硬件和协议。
同时,我们都希望创建的引导加载器可以支持多种不同的加载机制,这样可以提高其可移植性、健壮性和用户友好性。
1.5、在引导阶段配置操作系统
处于某种原因,用户往往希望在加载启动时能够动态地向操作系统提供一些配置信息。虽然本规范不应该规定引导加载程序如何获取此配置信息,但它应该为此提供一个标准化的途径。
1.6、如何更容易地开发操作系统
操作系统映像应该是很容易被生成的。理想情况下,操作系统映像应该是一个普通的32位可执行文件,这个可执行文件可以是操作系统通常使用的任何一种文件格式。操作系统映像应该是可以被nm工具或反汇编的,看起来就像是一个正常可执行文件。不应该要求使用专门的工具以一种特殊的文件格式来创建系统映像。因为,引导加载程序所使用的内存空间通常可在引导结束后再次被使用,而通常系统映像中的所有代码都必须永远保持在内存中。所以,将一些工作从操作系统转移到引导加载程序是合适的。如果要将操作系统数据加载到1MB以上的内存空间,则必须提前将CPU工作模式从16位实模式切换到32位保护模式。操作系统不应该承担这一过程的责任,因为这一部分的代码通常是在引导加载程序中的。如果在设计操作系统时不遵守这种约定,那么将会导致创建操作系统映像会变得更加困难。
不幸的是,即便是在自由的类Unix的PC操作系统中,也有各种各样形式不同的可执行文件格式。通常,不同的操作系统都会采用不同的可执行文件格式。虽然,大多数自由操作系统都在使用一种基于a.out的变体格式,但好在其中的一些正在向ELF可执行文件格式迁移。不需要解释所有已存在的不同类型的可执行文件格式来加载操作系统映像,这是一件对于引导加载器来说很期望的事情。否则,引导加载器将不再是通用型的了,从而变成只针对于某些特定操作系统的“专属加载器”了。
为了解决这个问题,本规范采用了一种折衷的解决方案。这个解决方案要求符合Multiboot2
规范的操作系统映像必须包含一个特殊的Multiboot2
头(查看3.1节 [操作系统镜像格式])。操作系统映像包含Multiboot2
头后,引导加载程序在加载映像时不再需要理解各种不同的a.out的变体或其他可执行格式。这个特殊的结构体(Multiboot2
头)不需要出现在可执行文件最开始的地方。这样,内核映像就可以同时符合特定的可执行文件格式和Multiboot2
规范了。
1.7、启动模块
许多现代操作系统内核,诸如Mach和VSTa中的微内核,它们自身并不包含足够的机制来使系统独立运行。相反,它们需要在系统启动时加载额外的软件模块,以便能够访问设备、安装文件系统等。虽然这些附加的模块可以与内核本身一起嵌入到磁盘映像中,但是,如果引导加载程序能够首先独立地加载这些附加模块,然后操作系统映像在控制序列的操控下分割磁盘映像并逐一加载,这样会让操作系统和用户都感觉更加灵活,更加节省空间,也更加方便。
因此,本规范为引导加载程序提供了一种标准方法,这种方法使得引导加载程序向操作系统告知了这些辅助引导模块如何被加载。虽然引导程序不是必须要加载这些辅助引导模块,但是对于那些特定的操作系统这是必须的。因为没有这些辅助引导模块,那些操作系统将无法完成引导启动。
2、措辞与术语
【在将措辞与术语的解释进行翻译的过程中,会不可避免地引进一些误差或错误,而这些措辞属于比较号理解。为了表述的严谨性,这一节只列出措辞术语的名词,相关解释请查阅本规范原文】
- must
- should
- may
- boot loader
- OS image, kernel
- boot module
- Multiboot2-compliant
- u8
- u16
- u32
- u64
3、Multiboot2
规范的确切定义
引导加载器与操作系统映像之间的接口主要有三个方面:
- 引导加载程序所看到的操作系统映像的格式。
- 引导加载程序启动操作系统时机器的状态。
- 引导加载程序传递给操作系统的信息格式。
3.1、操作系统映像格式
操作系统映像可以是某一特定操作系统采用的标准的普通的32可执行文件,但它也可以被链接在一个非默认的加载地址,这样可以避免加载在PC顶部的I/O地址空间或其他保留的地址空间。并且,磁盘映像文件不允许使用共享库或其他有趣的特性功能。
OS映像自身使用某一种格式的文件头,但除此之外,它还必须包含一个称为Multiboot2
的头部。Multiboot2
头必须包含在OS映像的前32768字节内,而且不许是64位对齐,也就是4字节对齐。通常情况下,应该将Multiboot
头放在尽可能前的地方。它通常是嵌入到可执行文件头部之后代码段的起始部位。
3.1.1、Multiboot2
头的定义
以下为Multiboot2
头的格式:
Offset | Type | Field Name | Note |
---|---|---|---|
0 | u32 | magic | required |
4 | u32 | architecture | required |
8 | u32 | header_length | required |
12 | u32 | checksum | required |
16-XX | tags | required |
- 字段
magic
、architecture
、header_length
和checksum
在3.1.2 [Multiboot2
头中的字段] 中定义; - 字段
tags
在3.1.3 [标签tag
结构体] 中定义; - 所有的字段的存储顺序依据当前系统是大端还是小端。
3.1.2、Multiboot2
头中的字段
3.1.2.1、 magic
字段magic
是指示Multiboot
头的魔数,该字段必须为16进制值0xE85250D6。
3.1.2.2、 architecture
字段architecture
指定CPU指令集体系结构。由于magic
字段不是回文,Multiboot2规范具体实现可以据此来确定当前平台的大小端情况。architecture
字段的值为0时,则表示是i386的32位(受保护)模式;如果是4则表示是32位MIPS。
3.1.2.3、 header_length
字段header_length
以字节为单位指定Multiboot2
头的长度,其中包括magic
字段。
3.1.2.4、 checksum
字段checksum
是一个32位无符号值,使得magic
、architecture
、header_length
、checksum
这四个字段的值的和必须是32位无符号0。
3.1.3、标签tag
结构体
标签tag
是结构体缓冲区,这个结构体缓冲区在必要的时候进行填充,以便每个标签tag
会从8字节对齐的地址处开始。所有的标签tag
排列组成一个标签列表,作为终止标签的最后一个标签,其类型为0、大小为8。每一个标签tag
结构体都具有以下结构:
大小 | 名称 |
---|---|
u16 | type |
u16 | flags |
u32 | size |
type
分为两部分。type
类型的低字节部分包含了标签tag
的类型。size
字段包含了该标签tag
中所有字段的大小总和。flags
字段的位0指示了该标签tag
是可选的。如果flags
字段的位0被置位,那么当缺少相关支持时,引导加载程序会忽略此标签tag
。
3.1.4、Multiboot2
information request
大小 | 名称 |
---|---|
u16 | type = 1 |
u16 | flags |
u32 | size |
u32 | mbi_tag_types |
字段mbi_tag_types
是一个32位无符号整型数组,每一个元素表示一个请求信息。
对于这个标签tag
,如果设置字段flags
中可选标志位位0,则引导加载程序必须支持这个请求的标签tag
,并且如果图形模式可用,则必须为操作系统提供相关的图形模式信息。如果引导加载程序不支持所请求的标记的含义,则必须以错误失败结束。然而,如果引导加载程序支持给定的标记,但为其所传递的信息不可用,则引导加载程序就不会在Multiboot2
信息结构中提供所请求的标记,并且将控制权交给被加载的系统映像。
注:以上说明意味着不能保证任何类型的mbi_tag_types
标签实际上都是存在的。例如,在视频系统中,即便您请求标签类型为8的标签tag
,同时引导加载程序也支持它,但Multiboot2
信息结构中也不会出现对应的标签tag
。
3.1.5、Multiboot2
头中的地址标签tag
大小 | 名称 |
---|---|
u16 | type = 2 |
u16 | flags |
u32 | size |
u32 | header_addr |
u32 | load_addr |
u32 | load_end_addr |
u32 | bss_end_addr |
该标签tag
中的所有的地址字段均时物理地址。每个字段的含义如下:
3.1.5.1、header_addr
该字段包含将操作系统映像加载到内存后,Multiboot2
在内存中的物理内存地址。该字段用于同步操作系统内核与物理内存地址之间的映射。
3.1.5.2、load_addr
该字段包含Multiboot2头所在代码段的起始物理地址。在操作系统内核映像中,引导加载器确定文本段相对于Multiboot2
头的相对偏移是通过计算得到的:header_addr - load_addr
。这样就可以正确地确定映像中文本段的起始位置。load_addr
字段必须小于或等于header_addr
,这样所得到的相对偏移数值为正。另外,也可以赋值为特殊值-1,表明必须从磁盘映像文件的起始处开始加载。
3.1.5.3、load_end_addr
该字段包含数据段末尾的物理地址。load_end_addr - load_addr
表示有多少字节的数据需要被加载进内存。这也意味着文本段和数据段在操作系统映像中必须是连续的。现有的a.out
可执行文件格式是否和此要求的。对于特殊值0,表示代码段和数据段占用了整个操作系统磁盘映像文件。
3.1.5.4、bss_end_addr
3.1.6、Multiboot2
头中的项目地址Tag
3.1.7、Multiboot2
头中的EFI i386项目地址Tag
3.1.8、Multiboot2
头中的EFI amd64项目地址Tag
3.1.9、标志Tag
3.1.10、帧缓冲Tag
3.1.11、模块对齐Tag
3.1.12、EFI启动服务Tag
3.1.13、重定位头Tag
3.2、MIPS机器状态
3.3、I386机器状态
3.4、具有可启动服务的EFI i386机器状态
3.5、具有可启动服务的EFI amd64机器状态
3.6、启动信息格式
3.6.1、启动信息格式
引导加载程序将控制权交给操作系统后,EBX
寄存器包含Multiboot2
信息数据结构的物理地址,引导加载程序通过该结构将重要信息传递给操作系统。操作系统可以选择或忽略Multiboot2
信息数据结构中的任何部分。引导加载程序传递的所有信息仅仅只能作为查询使用。
Multiboot2
信息结构及其相关子结构可以由引导加载程序放置在内存的任何地方。当然,这些数据不会被放置在内核加载以及引导模块保留的内存区域中。当引导加载程序将控制权交给操作系统后,操作系统在使用完这些信息之前,必须确保这块区域不能被覆盖。
3.6.2、基本标签tag
结构体
启动引导信息是由固定部分和一系列标签组成。这些标签的起始地址是以8字节对齐的。固定部分如下:
大小 | 名称 |
---|---|
u32 | total_size |
u32 | reserved |
- 字段
total_size
包含引导信息的大小总和,这其中也包括了该字段本身以及以字节为单位的终止标记。 - 字段
reserved
总是被设定为0,操作系统应该总是忽略这个字段。
每一个标签tag
都以以下字段开始:
大小 | 名称 |
---|---|
u32 | type |
u32 | size |
- 字段
type
包含标签tag
其余部分内容的标识符,也就是标签tag
的类型。 - 字段
size
包含标签tag
的大小,其中包括头字段,但不包括填充部分。
这些标签必须相互跟随(原文在此处的描述是:“Tags follow one another padded when necessary in order for each tag to start at 8-bytes aligned address.”),也就是说在内存空间中紧密排列,以便每个标签都是从8字节对齐的地址开始。这些标签组成了一个标签列表,这个列表以type
为0并且size
为8的标签结束。
3.6.3、基本内存信息
大小 | 名称 |
---|---|
u32 | type = 4 |
u32 | size = 16 |
u32 | mem_lower |
u32 | mem_upper |
字段mem_lower
和mem_upper
指示低端内存和高端内存的大小,其值是以KB为单位。低端内存从是从地址0处开始的,高端内存是从1MB地址处开始的。低端内存的最大值可能是640KB。高端内存的值是存储器最大存储值减去1MB的数值。但是,引导加载程序不能保证这些值的有效性。
在一些EFI平台上,如果启用了EFI启动服务并且可以加载映像,引导加载器可能不会支持这个标签。在Multiboot2
信息结构中存在未终止的标签tag
(原文在此的描述是:“EFI boot services not terminated tag exists in Multiboot2 information structure”)。
3.6.4、BIOS启动设备
大小 | 名称 |
---|---|
u32 | type = 5 |
u32 | size = 20 |
u32 | biosdev |
u32 | partition |
u32 | sub_parition |
这个标签tag
指明了引导加载程序是从哪一个BIOS磁盘设备加载这个操作系统映像的。如果操作系统映像不是从一个BIOS磁盘设备引导的,那么这个标签tag
不会出现。操作系统可以使用这个字段来确定其根设备,但并不是必须这么作。
剩余的三个字段指明了引导分区。(原文在此的描述是:“The three remaining bytes specify the boot partition.”,并且位置也不在此处。如果有疑问,请阅读本节的原文描述。)
- 字段
biosdev
包含了BIOS设备号,这个设备号是由底层磁盘接口BIOS INT 0x13 中断提供的。例如:第一个软盘的设备号是0x00,第二个硬盘的设备号是0x80。 - 字段
partition
指明了引导分区的主分区号。 - 字段
sub_partition
指明了引导分区在所在主分区中的次分区号。
分区号总是从0开始的。未使用的字段必须被设置为0xFFFFFFFF,例如,如果一个磁盘被分区为一个简单的单级DOS分区格式,那么字段partition
中保存着DOS分区号,并且字段sub_partition
被设置为0xFFFFFFFF。在另一个例子中,如果把DOS主分区进一步划分为几个BSD分区,那么字段partition
中保存着DOS主分区号,同时字段sub_partition
保存着在BSD分区子级分区在DOS主分区中的子分区号。
DOS扩展分区的分区号是从4开始的,并且递增。这一点和嵌套的子分区不同,尽管扩展分区的底层磁盘布局具有层次性。(原文在此的描述是:“DOS extended partitions are indicated as partition numbers starting from 4 and increasing, rather than as nested sub-partitions, even though the underlying disk layout of extended partitions is hierarchical in nature.”)举个例子,在一个DOS分区格式的磁盘上,如果引导加载程序是从磁盘第二个扩展分区引导系统的,那么字段partition
将是5,而字段sub_partiton
将是0xFFFFFFFF。
3.6.5、启动命令行
大小 | 名称 |
---|---|
u32 | type = 1 |
u32 | size |
u8[n] | string |
- 字段
string
中包含了启动命令行。这个命令行是一个UTF-8编码的以0结束的普通的C风格字符串。
3.6.6、模块
大小 | 名称 |
---|---|
u32 | type = 3 |
u32 | size |
u32 | mod_start |
u32 | mod_end |
u32 | string |
这个标签tag
向内核指示了有哪些引导模块是与内核映像一同被加载进内存的,以及在内存何处可以被找到这些模块。
- 字段
mod_start
包含了引导模块的起始物理地址。 - 字段
mod_end
包含了引导模块的结束物理地址。 - 字段
string
包含了与特定引导模块相关的一个描述性字符串。与内核命令行一样,这是一个UTF-8编码的以0结束的字符串。
通常,字符串可能是命令行(如果操作系统将引导模块视为可执行程序),也可能是路径名(如果操作系统将引导模块视为文件系统中的文件),但其确切用途是依据特定操作系统而定的。
每一个模块会对应一个标签tag
。这个标签tag
可能会出现多次。
3.6.7、ELF节头表
大小 | 名称 |
---|---|
u32 | type = 9 |
u32 | size |
u16 | num |
u16 | entsize |
u16 | shndx |
u16 | reserved |
u16 | section headers |
(依据给出的头文件multiboot2.h
判断,字段size
、entsize
、shndx
、reserved
应该是u32
类型的,字段section headers
应该是u8
的数组。这一点与规范中此处的描述不符。)
如果被加载的内核映像是一个ELF文件内核,则此标签tag
包含了其节头表(section header table),节头表中的每一个条目的大小,以及字符串表中用来引用节名的偏移量(原文使用“index”)。它们相当于ELF规范中在ELF头(原文使用“the program header”,但ELF中另有程序头表)的“shdr_*”这样的条目(例如“shdr_num”)。所有的节都会被加载。
- 字段
num
中保存ELF节头表中的项数。 - 字段
entsize
中保存ELF节头表中每一项的大小。 - 字段
shndx
中保存节名表在节表中的的序号。 - 字段
section headers
中保存节头表内容。此内容和ELF中的节头表内容完全一致。
ELF节头表条目结构如下:
#define EI_NIDENT (16)
typedef struct
{
unsigned char e_ident[EI_NIDENT]; /* Magic number and other info */
Elf32_Half e_type; /* Object file type */
Elf32_Half e_machine; /* Architecture */
Elf32_Word e_version; /* Object file version */
Elf32_Addr e_entry; /* Entry point virtual address */
Elf32_Off e_phoff; /* Program header table file offset */
Elf32_Off e_shoff; /* Section header table file offset */
Elf32_Word e_flags; /* Processor-specific flags */
Elf32_Half e_ehsize; /* ELF header size in bytes */
Elf32_Half e_phentsize; /* Program header table entry size */
Elf32_Half e_phnum; /* Program header table entry count */
Elf32_Half e_shentsize; /* Section header table entry size */
Elf32_Half e_shnum; /* Section header table entry count */
Elf32_Half e_shstrndx; /* Section header string table index */
} Elf32_Ehdr;
3.6.8、内存映射
这个标签tag
提供内存映射。
大小 | 名称 |
---|---|
u32 | type = 6 |
u32 | size |
u32 | entry_size |
u32 | entry_version |
varies | entries |
- 字段
size
包含了这个标签tag
的大小,且包括字段entries
。(原文在此的描述是:“‘size’ contains the size of current entry including this field itself.”,且位置也不再此处。) - 字段
entry_size
包含每一个条目的大小,以便将来可以在其中添加新字段。这个字段的值必须是8的倍数。 - 字段
entry_version
当前必须被设置为0。在未来的版本中会递增这个字段,当然未来的版本是向后兼容旧版本的。
每一个条目有下面的结构:
大小 | 名称 |
---|---|
u64 | base_addr |
u64 | length |
u32 | type |
u32 | reserved |
- 字段
base_addr
是所表示内存区域的起始地址。 - 字段
length
是所表示内存区域的大小。 - 字段
type
是所表示内存区域的类型。其中,数值1表示可用RAM;数值3表示保存ACPI信息的可用内存;数值4表示需要在休眠时保存数据用的保留内存(原文在此的描述是:“value of 4 indicates reserved memory which needs to be preserved on hibernation“);数值5表示有缺陷的RAM模块(原文在此的描述是:“value of 5 indicates a memory which is occupied by defective RAM modules“);所有其他的值当前都表示保留的内存区域。 - 字段
reserved
被引导加载程序设置为0,操作系统内核应该忽略这个字段。
type值 | 助记符 | 含义 |
---|---|---|
1 | AddressRangeMemory | 这一段内存是可以供操作系统使用的 |
2 | AddressRangeReserved | 这一段内存已经被用了,或者保留起来的,不被OS的memory manager去分配 |
3 | AddressRangeACPI | 当OS 读过ACPI 表之后,就可使用的一段范围 |
4 | AddressRangeNVS | ACPI NVS 内存,这一段内存已经被用了或者保留起来,是不能被OS去用的 |
5 | AddressRangeUnusuable | 这是一段不能有的地址,因为检测到了错误 |
6 | AddressRangeDisabled | 一段没有被enabled的地址, 也是ospm 不可以用的 |
其他 | Undefined | 未定义,保留或者将来用。 |
提供的内存映射都是可被正常使用的,这些区域中可能包含内核、mbi数据、段和模块。内核不应该覆盖这些区域。(原文在此的描述是:“This type however includes the regions occupied by kernel, mbi, segments
and modules. Kernel must take care not to overwrite these regions.”)
在一些EFI平台上,如果启用了EFI启动服务并且可以加载映像,引导加载器可能不会支持这个标签。在Multiboot2
信息结构中存在未终止的标签tag
(原文在此的描述是:“EFI boot services not terminated tag exists in Multiboot2 information structure”)。
3.6.9、引导加载器名称
大小 | 名称 |
---|---|
u32 | type = 2 |
u32 | size |
u8[n] | string |
- 字段
string
包含了加载内核的引导加载器的名称。这个名称是一个UTF-8编码的以0结束的C风格字符串。
3.6.10、APM表
3.6.11、VBE信息
3.6.12、帧缓冲信息
3.6.13、ELF32位系统表指针
3.6.14、ELF64位系统表指针
3.6.15、SMBIOS表
3.6.16、旧RSDP(ACPI 1.0)
3.6.17、新RSDP(ACPI 2.0及以上)
3.6.18、网络信息
3.6.19、EFI内存映射
3.6.20、EFI启动服务未被终止
3.6.21、EFI32位映像处理指针
3.6.22、EFI64位映像处理指针
3.6.23、映像加载物理基地址
4、示例
4.1、C结构体成员对齐与填充
4.2、在PC上需要注意的问题
4.3、BIOS设备映射技术
4.3.1、数据比较技术
4.3.2、I/O限制技术
4.4、操作系统示例代码
4.4.1、multiboot2.h
4.4.2、boot.S
4.4.3、kernel.c
4.4.4、Makefile
4.4.5、其他Multiboot2
内核
4.5、引导程序示例代码
5、本规范更新日志
6、索引
二、GRUB
三、代码实例
multiboot2.h
/* multiboot2.h - Multiboot 2 header file. */
/* Copyright (C) 1999,2003,2007,2008,2009,2010 Free Software Foundation, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL A
* DEVELOPER OR DISTRIBUTOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
* IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef MULTIBOOT_HEADER
#define MULTIBOOT_HEADER 1
/* How many bytes from the start of the file we search for the header. */
#define MULTIBOOT_SEARCH 32768
#define MULTIBOOT_HEADER_ALIGN 8
/* The magic field should contain this. */
#define MULTIBOOT2_HEADER_MAGIC 0xe85250d6
/* This should be in %eax. */
#define MULTIBOOT2_BOOTLOADER_MAGIC 0x36d76289
/* Alignment of multiboot modules. */
#define MULTIBOOT_MOD_ALIGN 0x00001000
/* Alignment of the multiboot info structure. */
#define MULTIBOOT_INFO_ALIGN 0x00000008
/* Flags set in the ’flags’ member of the multiboot header. */
#define MULTIBOOT_TAG_ALIGN 8
#define MULTIBOOT_TAG_TYPE_END 0
#define MULTIBOOT_TAG_TYPE_CMDLINE 1
#define MULTIBOOT_TAG_TYPE_BOOT_LOADER_NAME 2
#define MULTIBOOT_TAG_TYPE_MODULE 3
#define MULTIBOOT_TAG_TYPE_BASIC_MEMINFO 4
#define MULTIBOOT_TAG_TYPE_BOOTDEV 5
#define MULTIBOOT_TAG_TYPE_MMAP 6
#define MULTIBOOT_TAG_TYPE_VBE 7
#define MULTIBOOT_TAG_TYPE_FRAMEBUFFER 8
#define MULTIBOOT_TAG_TYPE_ELF_SECTIONS 9
#define MULTIBOOT_TAG_TYPE_APM 10
#define MULTIBOOT_TAG_TYPE_EFI32 11
#define MULTIBOOT_TAG_TYPE_EFI64 12
#define MULTIBOOT_TAG_TYPE_SMBIOS 13
#define MULTIBOOT_TAG_TYPE_ACPI_OLD 14
#define MULTIBOOT_TAG_TYPE_ACPI_NEW 15
#define MULTIBOOT_TAG_TYPE_NETWORK 16
#define MULTIBOOT_TAG_TYPE_EFI_MMAP 17
#define MULTIBOOT_TAG_TYPE_EFI_BS 18
#define MULTIBOOT_TAG_TYPE_EFI32_IH 19
#define MULTIBOOT_TAG_TYPE_EFI64_IH 20
#define MULTIBOOT_TAG_TYPE_LOAD_BASE_ADDR 21
#define MULTIBOOT_HEADER_TAG_END 0
#define MULTIBOOT_HEADER_TAG_INFORMATION_REQUEST 1
#define MULTIBOOT_HEADER_TAG_ADDRESS 2
#define MULTIBOOT_HEADER_TAG_ENTRY_ADDRESS 3
#define MULTIBOOT_HEADER_TAG_CONSOLE_FLAGS 4
#define MULTIBOOT_HEADER_TAG_FRAMEBUFFER 5
#define MULTIBOOT_HEADER_TAG_MODULE_ALIGN 6
#define MULTIBOOT_HEADER_TAG_EFI_BS 7
#define MULTIBOOT_HEADER_TAG_ENTRY_ADDRESS_EFI32 8
#define MULTIBOOT_HEADER_TAG_ENTRY_ADDRESS_EFI64 9
#define MULTIBOOT_HEADER_TAG_RELOCATABLE 10
#define MULTIBOOT_ARCHITECTURE_I386 0
#define MULTIBOOT_ARCHITECTURE_MIPS32 4
#define MULTIBOOT_HEADER_TAG_OPTIONAL 1
#define MULTIBOOT_LOAD_PREFERENCE_NONE 0
#define MULTIBOOT_LOAD_PREFERENCE_LOW 1
#define MULTIBOOT_LOAD_PREFERENCE_HIGH 2
#define MULTIBOOT_CONSOLE_FLAGS_CONSOLE_REQUIRED 1
#define MULTIBOOT_CONSOLE_FLAGS_EGA_TEXT_SUPPORTED 2
#ifndef ASM_FILE
typedef unsigned char multiboot_uint8_t;
typedef unsigned short multiboot_uint16_t;
typedef unsigned int multiboot_uint32_t;
typedef unsigned long long multiboot_uint64_t;
struct multiboot_header
{
/* Must be MULTIBOOT MAGIC - see above. */
multiboot_uint32_t magic;
/* ISA */
multiboot_uint32_t architecture;
/* Total header length. */
multiboot_uint32_t header_length;
/* The above fields plus this one must equal 0 mod 2^32. */
multiboot_uint32_t checksum;
};
struct multiboot_header_tag
{
multiboot_uint16_t type;
multiboot_uint16_t flags;
multiboot_uint32_t size;
};
struct multiboot_header_tag_information_request
{
multiboot_uint16_t type;
multiboot_uint16_t flags;
multiboot_uint32_t size;
multiboot_uint32_t requests[0];
};
struct multiboot_header_tag_address
{
multiboot_uint16_t type;
multiboot_uint16_t flags;
multiboot_uint32_t size;
multiboot_uint32_t header_addr;
multiboot_uint32_t load_addr;
multiboot_uint32_t load_end_addr;
multiboot_uint32_t bss_end_addr;
};
struct multiboot_header_tag_entry_address
{
multiboot_uint16_t type;
multiboot_uint16_t flags;
multiboot_uint32_t size;
multiboot_uint32_t entry_addr;
};
struct multiboot_header_tag_console_flags
{
multiboot_uint16_t type;
multiboot_uint16_t flags;
multiboot_uint32_t size;
multiboot_uint32_t console_flags;
};
struct multiboot_header_tag_framebuffer
{
multiboot_uint16_t type;
multiboot_uint16_t flags;
multiboot_uint32_t size;
multiboot_uint32_t width;
multiboot_uint32_t height;
multiboot_uint32_t depth;
};
struct multiboot_header_tag_module_align
{
multiboot_uint16_t type;
multiboot_uint16_t flags;
multiboot_uint32_t size;
};
struct multiboot_header_tag_relocatable
{
multiboot_uint16_t type;
multiboot_uint16_t flags;
multiboot_uint32_t size;
multiboot_uint32_t min_addr;
multiboot_uint32_t max_addr;
multiboot_uint32_t align;
multiboot_uint32_t preference;
};
struct multiboot_color
{
multiboot_uint8_t red;
multiboot_uint8_t green;
multiboot_uint8_t blue;
};
struct multiboot_mmap_entry
{
multiboot_uint64_t addr;
multiboot_uint64_t len;
#define MULTIBOOT_MEMORY_AVAILABLE 1
#define MULTIBOOT_MEMORY_RESERVED 2
#define MULTIBOOT_MEMORY_ACPI_RECLAIMABLE 3
#define MULTIBOOT_MEMORY_NVS 4
#define MULTIBOOT_MEMORY_BADRAM 5
multiboot_uint32_t type;
multiboot_uint32_t zero;
};
typedef struct multiboot_mmap_entry multiboot_memory_map_t;
struct multiboot_tag
{
multiboot_uint32_t type;
multiboot_uint32_t size;
};
struct multiboot_tag_string
{
multiboot_uint32_t type;
multiboot_uint32_t size;
char string[0];
};
struct multiboot_tag_module
{
multiboot_uint32_t type;
multiboot_uint32_t size;
multiboot_uint32_t mod_start;
multiboot_uint32_t mod_end;
char cmdline[0];
};
struct multiboot_tag_basic_meminfo
{
multiboot_uint32_t type;
multiboot_uint32_t size;
multiboot_uint32_t mem_lower;
multiboot_uint32_t mem_upper;
};
struct multiboot_tag_bootdev
{
multiboot_uint32_t type;
multiboot_uint32_t size;
multiboot_uint32_t biosdev;
multiboot_uint32_t slice;
multiboot_uint32_t part;
};
struct multiboot_tag_mmap
{
multiboot_uint32_t type;
multiboot_uint32_t size;
multiboot_uint32_t entry_size;
multiboot_uint32_t entry_version;
struct multiboot_mmap_entry entries[0];
};
struct multiboot_vbe_info_block
{
multiboot_uint8_t external_specification[512];
};
struct multiboot_vbe_mode_info_block
{
multiboot_uint8_t external_specification[256];
};
struct multiboot_tag_vbe
{
multiboot_uint32_t type;
multiboot_uint32_t size;
multiboot_uint16_t vbe_mode;
multiboot_uint16_t vbe_interface_seg;
multiboot_uint16_t vbe_interface_off;
multiboot_uint16_t vbe_interface_len;
struct multiboot_vbe_info_block vbe_control_info;
struct multiboot_vbe_mode_info_block vbe_mode_info;
};
struct multiboot_tag_framebuffer_common
{
multiboot_uint32_t type;
multiboot_uint32_t size;
multiboot_uint64_t framebuffer_addr;
multiboot_uint32_t framebuffer_pitch;
multiboot_uint32_t framebuffer_width;
multiboot_uint32_t framebuffer_height;
multiboot_uint8_t framebuffer_bpp;
#define MULTIBOOT_FRAMEBUFFER_TYPE_INDEXED 0
#define MULTIBOOT_FRAMEBUFFER_TYPE_RGB 1
#define MULTIBOOT_FRAMEBUFFER_TYPE_EGA_TEXT 2
multiboot_uint8_t framebuffer_type;
multiboot_uint16_t reserved;
};
struct multiboot_tag_framebuffer
{
struct multiboot_tag_framebuffer_common common;
union {
struct
{
multiboot_uint16_t framebuffer_palette_num_colors;
struct multiboot_color framebuffer_palette[0];
};
struct
{
multiboot_uint8_t framebuffer_red_field_position;
multiboot_uint8_t framebuffer_red_mask_size;
multiboot_uint8_t framebuffer_green_field_position;
multiboot_uint8_t framebuffer_green_mask_size;
multiboot_uint8_t framebuffer_blue_field_position;
multiboot_uint8_t framebuffer_blue_mask_size;
};
};
};
struct multiboot_tag_elf_sections
{
multiboot_uint32_t type;
multiboot_uint32_t size;
multiboot_uint32_t num;
multiboot_uint32_t entsize;
multiboot_uint32_t shndx;
char sections[0];
};
struct multiboot_tag_apm
{
multiboot_uint32_t type;
multiboot_uint32_t size;
multiboot_uint16_t version;
multiboot_uint16_t cseg;
multiboot_uint32_t offset;
multiboot_uint16_t cseg_16;
multiboot_uint16_t dseg;
multiboot_uint16_t flags;
multiboot_uint16_t cseg_len;
multiboot_uint16_t cseg_16_len;
multiboot_uint16_t dseg_len;
};
struct multiboot_tag_efi32
{
multiboot_uint32_t type;
multiboot_uint32_t size;
multiboot_uint32_t pointer;
};
struct multiboot_tag_efi64
{
multiboot_uint32_t type;
multiboot_uint32_t size;
multiboot_uint64_t pointer;
};
struct multiboot_tag_smbios
{
multiboot_uint32_t type;
multiboot_uint32_t size;
multiboot_uint8_t major;
multiboot_uint8_t minor;
multiboot_uint8_t reserved[6];
multiboot_uint8_t tables[0];
};
struct multiboot_tag_old_acpi
{
multiboot_uint32_t type;
multiboot_uint32_t size;
multiboot_uint8_t rsdp[0];
};
struct multiboot_tag_new_acpi
{
multiboot_uint32_t type;
multiboot_uint32_t size;
multiboot_uint8_t rsdp[0];
};
struct multiboot_tag_network
{
multiboot_uint32_t type;
multiboot_uint32_t size;
multiboot_uint8_t dhcpack[0];
};
struct multiboot_tag_efi_mmap
{
multiboot_uint32_t type;
multiboot_uint32_t size;
multiboot_uint32_t descr_size;
multiboot_uint32_t descr_vers;
multiboot_uint8_t efi_mmap[0];
};
struct multiboot_tag_efi32_ih
{
multiboot_uint32_t type;
multiboot_uint32_t size;
multiboot_uint32_t pointer;
};
struct multiboot_tag_efi64_ih
{
multiboot_uint32_t type;
multiboot_uint32_t size;
multiboot_uint64_t pointer;
};
struct multiboot_tag_load_base_addr
{
multiboot_uint32_t type;
multiboot_uint32_t size;
multiboot_uint32_t load_base_addr;
};
#endif /* ! ASM FILE */
#endif /* ! MULTIBOOT HEADER */
boot.S
/* boot.S - bootstrap the kernel */
/* Copyright (C) 1999, 2001, 2010 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 3 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, see <http://www.gnu.org/licenses/>.
*/
#define ASM_FILE 1
#include "multiboot2.h"
/* C symbol format. HAVE ASM USCORE is defined by configure. */
#ifdef HAVE_ASM_USCORE
# define EXT_C(sym) _ ## sym
#else
# define EXT_C(sym) sym
#endif
/* The size of our stack (16KB). */
#define STACK_SIZE 0x4000
/* The flags for the Multiboot header. */
#ifdef __ELF__
# define AOUT_KLUDGE 0
#else
# define AOUT_KLUDGE MULTIBOOT_AOUT_KLUDGE
#endif
#define GRUB_MULTIBOOT_ARCHITECTURE_I386 MULTIBOOT_ARCHITECTURE_I386
.text
.globl start, _start
start:
_start:
jmp multiboot_entry
/* Align 64 bits boundary. */
.align 8
/* Multiboot header. */
multiboot_header:
/* magic */
.long MULTIBOOT2_HEADER_MAGIC
/* ISA: i386 */
.long GRUB_MULTIBOOT_ARCHITECTURE_I386
/* Header length. */
.long multiboot_header_end - multiboot_header
/* checksum */
.long -(MULTIBOOT2_HEADER_MAGIC + GRUB_MULTIBOOT_ARCHITECTURE_I386 + (multiboot_header_end - multiboot_header))
#ifndef __ELF__
.align 8
address_tag_start:
.short MULTIBOOT_HEADER_TAG_ADDRESS
.short MULTIBOOT_HEADER_TAG_OPTIONAL
.long address_tag_end - address_tag_start
/* header_addr */
.long multiboot_header
/* load_addr */
.long _start
/* load_end_addr */
.long _edata
/* bss end addr */
.long _end
address_tag_end:
.align 8
entry_address_tag_start:
.short MULTIBOOT_HEADER_TAG_ENTRY_ADDRESS
.short MULTIBOOT_HEADER_TAG_OPTIONAL
.long entry_address_tag_end - entry_address_tag_start
/* entry addr */
.long multiboot_entry
entry_address_tag_end:
#endif /* ELF */
.align 8
framebuffer_tag_start:
.short MULTIBOOT_HEADER_TAG_FRAMEBUFFER
.short MULTIBOOT_HEADER_TAG_OPTIONAL
.long framebuffer_tag_end - framebuffer_tag_start
.long 1024
.long 768
.long 32
framebuffer_tag_end:
.align 8
.short MULTIBOOT_HEADER_TAG_END
.short 0
.long 8
multiboot_header_end:
multiboot_entry:
/* Initialize the stack pointer. */
movl $(stack + STACK_SIZE), %esp
/* Reset EFLAGS. */
pushl $0
popf
/* Push the pointer to the Multiboot information structure. */
pushl %ebx
/* Push the magic value. */
pushl %eax
/* Now enter the C main function... */
call EXT_C(cmain)
/* Halt. */
pushl $halt_message
call EXT_C(printf)
loop:
hlt
jmp loop
halt_message:
.asciz "Halted."
/* Our stack area. */
.comm stack, STACK_SIZE
kernel.c
/* kernel.c - the C part of the kernel */
/* Copyright (C) 1999, 2010 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 3 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, see <http://www.gnu.org/licenses/>.
*/
#include "multiboot2.h"
/* Macros. */
/* Some screen stuff. */
/* The number of columns. */
#define COLUMNS 80
/* The number of lines. */
#define LINES 24
/* The attribute of an character. */
#define ATTRIBUTE 7
/* The video memory address. */
#define VIDEO 0xB8000
/* Variables. */
/* Save the X position. */
static int xpos;
/* Save the Y position. */
static int ypos;
/* Point to the video memory. */
static volatile unsigned char *video;
/* Forward declarations. */
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, ...);
/* Check if MAGIC is valid and print the Multiboot information structure pointed by ADDR. */
void cmain(unsigned long magic, unsigned long addr)
{
struct multiboot_tag *tag;
unsigned size;
/* Clear the screen. */
cls();
/* Am I booted by a Multiboot-compliant boot loader? */
if (magic != MULTIBOOT2_BOOTLOADER_MAGIC)
{
printf("Invalid magic number: 0x%x\n", (unsigned)magic);
return;
}
if (addr & 7)
{
printf("Unaligned mbi: 0x%x\n", addr);
return;
}
size = *(unsigned *)addr;
printf("Announced mbi size 0x%x\n", size);
for (tag = (struct multiboot_tag *)(addr + 8);
tag->type != MULTIBOOT_TAG_TYPE_END;
tag = (struct multiboot_tag *)((multiboot_uint8_t *)tag + ((tag->size + 7) & ~7)))
{
printf("Tag 0x%x, Size 0x%x\n", tag->type, tag->size);
switch (tag->type)
{
case MULTIBOOT_TAG_TYPE_CMDLINE:
printf("Command line = %s\n", ((struct multiboot_tag_string *)tag)->string);
break;
case MULTIBOOT_TAG_TYPE_BOOT_LOADER_NAME:
printf("Boot loader name = %s\n", ((struct multiboot_tag_string *)tag)->string);
break;
case MULTIBOOT_TAG_TYPE_MODULE:
printf("Module at 0x%x-0x%x. Command line %s\n",
((struct multiboot_tag_module *)tag)->mod_start,
((struct multiboot_tag_module *)tag)->mod_end,
((struct multiboot_tag_module *)tag)->cmdline);
break;
case MULTIBOOT_TAG_TYPE_BASIC_MEMINFO:
printf("mem_lower = %uKB, mem_upper = %uKB\n",
((struct multiboot_tag_basic_meminfo *)tag)->mem_lower,
((struct multiboot_tag_basic_meminfo *)tag)->mem_upper);
break;
case MULTIBOOT_TAG_TYPE_BOOTDEV:
printf("Boot device 0x%x,%u,%u\n",
((struct multiboot_tag_bootdev *)tag)->biosdev,
((struct multiboot_tag_bootdev *)tag)->slice,
((struct multiboot_tag_bootdev *)tag)->part);
break;
case MULTIBOOT_TAG_TYPE_MMAP:
{
multiboot_memory_map_t *mmap;
printf("mmap\n");
for (mmap = ((struct multiboot_tag_mmap *)tag)->entries;
(multiboot_uint8_t *)mmap < (multiboot_uint8_t *)tag + tag->size;
mmap = (multiboot_memory_map_t *)((unsigned long)mmap + ((struct multiboot_tag_mmap *)tag)->entry_size))
printf(" base_addr = 0x%x%x,"
" length = 0x%x%x, type = 0x%x\n",
(unsigned)(mmap->addr >> 32),
(unsigned)(mmap->addr & 0xffffffff),
(unsigned)(mmap->len >> 32),
(unsigned)(mmap->len & 0xffffffff),
(unsigned)mmap->type);
}
break;
case MULTIBOOT_TAG_TYPE_FRAMEBUFFER:
{
multiboot_uint32_t color;
unsigned i;
struct multiboot_tag_framebuffer *tagfb = (struct multiboot_tag_framebuffer *)tag;
void *fb = (void *)(unsigned long)tagfb->common.framebuffer_addr;
switch (tagfb->common.framebuffer_type)
{
case MULTIBOOT_FRAMEBUFFER_TYPE_INDEXED:
{
unsigned best_distance, distance;
struct multiboot_color *palette;
palette = tagfb->framebuffer_palette;
color = 0;
best_distance = 4 * 256 * 256;
for (i = 0; i < tagfb->framebuffer_palette_num_colors; i++)
{
distance = (0xff - palette[i].blue) * (0xff - palette[i].blue) + palette[i].red * palette[i].red + palette[i].green * palette[i].green;
if (distance < best_distance)
{
color = i;
best_distance = distance;
}
}
}
break;
case MULTIBOOT_FRAMEBUFFER_TYPE_RGB:
color = ((1 << tagfb->framebuffer_blue_mask_size) - 1) << tagfb->framebuffer_blue_field_position;
break;
case MULTIBOOT_FRAMEBUFFER_TYPE_EGA_TEXT:
color = '\\' | 0x0100;
break;
default:
color = 0xffffffff;
break;
}
for (i = 0; i < tagfb->common.framebuffer_width && i < tagfb->common.framebuffer_height; i++)
{
switch (tagfb->common.framebuffer_bpp)
{
case 8:
{
multiboot_uint8_t *pixel = fb + tagfb->common.framebuffer_pitch * i + i;
*pixel = color;
}
break;
case 15:
case 16:
{
multiboot_uint16_t *pixel = fb + tagfb->common.framebuffer_pitch * i + 2 * i;
*pixel = color;
}
break;
case 24:
{
multiboot_uint32_t *pixel = fb + tagfb->common.framebuffer_pitch * i + 3 * i;
*pixel = (color & 0xffffff) | (*pixel & 0xff000000);
}
break;
case 32:
{
multiboot_uint32_t *pixel = fb + tagfb->common.framebuffer_pitch * i + 4 * i;
*pixel = color;
}
break;
}
}
break;
}
}
}
tag = (struct multiboot_tag *)((multiboot_uint8_t *)tag + ((tag->size + 7) & ~7));
printf("Total mbi size 0x%x\n", (unsigned)tag - addr);
}
/* Clear the screen and initialize VIDEO, XPOS and 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;
}
/* Convert the integer D to a string and save the string in BUF. If
BASE is equal to ’d’, interpret that D is decimal, and if BASE is
equal to ’x’, interpret that D is hexadecimal. */
static void itoa(char *buf, int base, int d)
{
char *p = buf;
char *p1, *p2;
unsigned long ud = d;
int divisor = 10;
/* If %d is specified and D is minus, put ‘-’ in the head. */
if (base == 'd' && d < 0)
{
*p++ = '-';
buf++;
ud = -d;
}
else if (base == 'x')
divisor = 16;
/* Divide UD by DIVISOR until UD == 0. */
do
{
int remainder = ud % divisor;
*p++ = (remainder < 10) ? remainder + '0' : remainder + 'a' - 10;
} while (ud /= divisor);
/* Terminate BUF. */
*p = 0;
/* Reverse BUF. */
p1 = buf;
p2 = p - 1;
while (p1 < p2)
{
char tmp = *p1;
*p1 = *p2;
*p2 = tmp;
p1++;
p2--;
}
}
/* Put the character C on the screen. */
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;
}
/* Format a string and print it on the screen, just like the libc
function 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, *p2;
int pad0 = 0, pad = 0;
c = *format++;
if (c == '0')
{
pad0 = 1;
c = *format++;
}
if (c >= '0' && c <= '9')
{
pad = c - '0';
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:
for (p2 = p; *p2; p2++)
;
for (; p2 < p + pad; p2++)
putchar(pad0 ? '0' : ' ');
while (*p)
putchar(*p++);
break;
default:
putchar(*((int *)arg++));
break;
}
}
}
}
Makefile
CC = gcc
LD = ld
# -g -- 生成符号表以供调试使用
# -m32 -- 编译成32位目标文件
# -Wall -- 打开绝大多数警告
# -Wno-implicit-function-declaration --
# -std=c11 -- 遵循C11标准
# -std=gnu11 -- 遵循C11标准并且打开GNU的相关扩展
# -ffreestanding -- 采用独立环境
# -fno-pic -- 解决“对‘_GLOBAL_OFFSET_TABLE_’未定义的引用”问题
# -nostdinc -- 项目中的头文件可能会和开发环境中的系统头文件文件名称发生冲突,采用这个编译选项时不检索系统默认的头文件目录,但之后需要手动添加需检索的提供独立环境头文件的目录。
# -Iinclude -- 指定包含文件目录
# -fomit-frame-pointer -- 函数操作时不保存栈帧到寄存器(%ebp)(如果使用这个编译参数,则在gdb中调试会出错,因为gdb调试依赖栈帧)
CFLAGS = -g -m32 -Wall -Wno-implicit-function-declaration -std=gnu11 -ffreestanding -fno-pic -nostdinc #-fomit-frame-pointer
# -Ttext -- 设置 .text 节的地址
# -m -- 设置仿真
LDFLAGS = -Ttext 0x100000 -m elf_i386
%.o: %.S
$(CC) $(CFLAGS) -c $< -o $@
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
%: %.o
$(LD) $^ -o $@ $(LDFLAGS)
LOOP_DEVICE_NUMBER = 12
LOOP_DEVICE = /dev/loop$(LOOP_DEVICE_NUMBER)
LOOP_DEVICE_MOUNT_ROOT_DIR = /mnt/vdisk
KERNEL_NAME = kernel
DISK_IMAGE = hd.img
DISK_IMAGE_SECTORS = 20160
all: mkimage
boot.o: boot.S
kernel.o: kernel.c
$(KERNEL_NAME): boot.o kernel.o
mkimage: kernel $(DISK_IMAGE)
make mount
sudo cp $(KERNEL_NAME) $(LOOP_DEVICE_MOUNT_ROOT_DIR)/p1
make umount
$(DISK_IMAGE):
dd if=/dev/zero of=$(DISK_IMAGE) bs=512 count=$(DISK_IMAGE_SECTORS)
parted $(DISK_IMAGE) 'mklabel msdos mkpart primary fat16 1MB -1 set 1 boot on'
sudo losetup -P $(LOOP_DEVICE) $(DISK_IMAGE)
sudo mkfs.msdos $(LOOP_DEVICE)p1
sudo mount $(LOOP_DEVICE)p1 $(LOOP_DEVICE_MOUNT_ROOT_DIR)/p1
sudo grub-install --boot-directory=$(LOOP_DEVICE_MOUNT_ROOT_DIR)/p1 --target=i386-pc $(LOOP_DEVICE)
echo "echo \"multiboot (hd0,msdos1)/kernel\nboot\" > $(LOOP_DEVICE_MOUNT_ROOT_DIR)/p1/grub/grub.cfg" | sudo sh
sync
make umount
mount:
sudo losetup -P $(LOOP_DEVICE) $(DISK_IMAGE)
sudo mount $(LOOP_DEVICE)p1 $(LOOP_DEVICE_MOUNT_ROOT_DIR)/p1
umount:
sudo umount $(LOOP_DEVICE_MOUNT_ROOT_DIR)/p1
sudo losetup -d $(LOOP_DEVICE)
clean:
rm -f *.o kernel $(DISK_IMAGE)