使用GRUB Multiboot2引导自制操作系统
前言
之前花了一周时间,从头学习了传统 BIOS 的启动流程。惊讶于背后丰富的技术细节的同时,也感叹 x86 架构那厚重的历史包袱。毕竟,谁能想到,一个现代 CPU 竟然需要通过操作“键盘控制器寄存器”来启用一条地址线呢。
最终,出于兼容性和功能性的考虑,我还是决定投入 GRUB 的怀抱。况且,让自己写的操作系统和本机的 Linux 一同出现在 GRUB 菜单中并成功引导启动,也是一件非常有成就感的事。
开发环境
1 Multiboot2 规范
想让 GRUB 识别并引导我们自制的操作系统,就得先了解 Multiboot2 规范。
官方文档:Multiboot2 Specification version 2.0
1.1 Multiboot2 header
这段话的大致意思是:
在 Multiboot2 规范中,操作系统镜像的前 32768 字节内,必须包含一个名为 Multiboot2 header 的数据结构,并且起始地址必须 64-bit 对齐。
Multiboot2 header 的具体结构如下:
1.1.1 Magic fields
Multiboot2 header 的前四个字段,也就是0~15字节的内容,被称为 magic fields
magic
字段是 Multiboot2 header 的识别标志,它必须是十六进制值 0xE85250D6
architecture
字段指定 CPU 的架构。0
表示 i386 的 32 位保护模式,4
表示 32 位 MIPS 架构
header_length
字段记录了 Multiboot2 header 的长度(以字节为单位)
checksum
字段是一个 32 位无符号数,当它与 magic fields 其他字段(即 magic
、 architecture
和 header_length
)相加时,其 32 位无符号和必须为零。
1.1.2 Tags
Tags 由一个接一个的 tag 结构体组成,在必要时进行填充,以确保每个 tag 都从 8 字节对齐的地址开始。Tags 以一个 type = 0 且 size = 8 的 tag 作为结束标志(相当于字符串结尾的'\0')。
每个 tag 都有如下基本结构:
+-------------------+
u16 | type |
u16 | flags |
u32 | size |
+-------------------+
type
用于表示 tag 的类型,因为不同的 tag 在 size
之后可能还会有其他数据字段。
如果flags
的第 0 位(也称为 optional
)为 1,表示如果引导加载程序缺乏相关支持,它可以忽略这个 tag。
size
表示整个 tag 的长度。
例如,表示程序入口地址的 entry address tag 结构如下:
+-------------------+
u16 | type = 3 |
u16 | flags |
u32 | size |
u32 | entry_addr |
+-------------------+
GRUB 会根据 type = 3 判断它是 entry address tag,并在准备工作完成后,跳转到 entry_addr
字段中的地址运行操作系统。
除此之外,还有专门在 EFI 引导使用的 EFI i386 entry address tag:
+-------------------+
u16 | type = 8 |
u16 | flags |
u32 | size |
u32 | entry_addr |
+-------------------+
其结构与 entry address tag 相同,只是 type = 8 。既然都是指定程序入口,那么二者同时存在时会发生什么呢?
在使用 EFI 引导的情况下,entry address tag 将被忽略,引导加载程序会跳转到 EFI i386 entry address tag 提供的地址。
而使用传统 BIOS 引导时,引导加载程序会直接报错 error: unsupported tag: 0x08
。这是因为传统引导方式不支持 EFI 相关的 tag ,此时 flags
就派上用场了。只要把 flags
最低位设为 1 ,就可以让引导程序在不兼容时忽略这个 tag,而在 EFI 引导时又能正常使用它。
如此一来,我们的启动程序就能够兼容多种启动方式。
除此之外的 tag 类型,可以在 Multiboot2 Specification version 2.0 的 3.1.3 ~ 3.1.13 节找到详细说明。
1.2 机器状态
官方文档中的 3.3 I386 machine state 说明了引导加载程序调用 32 位操作系统时的机器状态。
除了开启保护模式和启用 A20 gate 这些常规操作以外,还有两条比较重要的内容:
EAX
必须包含标识值0x36d76289
,该值的存在向操作系统表明它是由兼容 Multiboot2 的引导加载程序加载的。EBX
必须包含引导加载程序提供的 Multiboot2 信息结构体的 32 位物理地址(请参阅 Boot information format)。
这两个寄存器中的数据,会在后续内核代码中用到。
2 代码实现
官方文档提供了对应的示例代码:4.4 Example OS code
但是!
前文有提过,文档给出的 boot.S 是有 BUG 的!因为 没有 给 tag 的地址进行 8 字节对齐,导致了 GRUB 读取 tag 会出现错位。
我将完整的代码放在了附录部分。 本章节主要内容是依据 Multiboot2 规范,对代码片段进行分析。
2.1 定义 Multiboot header
#include "multiboot2.h"
.text
multiboot_header:
/* Align 64 bits boundary. */
.align 8
/* magic */
.long MULTIBOOT2_HEADER_MAGIC
/* ISA: i386 */
.long MULTIBOOT_ARCHITECTURE_I386
/* Header length. */
.long multiboot_header_end - multiboot_header
/* checksum */
.long -(MULTIBOOT2_HEADER_MAGIC + MULTIBOOT_ARCHITECTURE_I386 + (multiboot_header_end - multiboot_header))
entry_address_tag_start:
/* 每个 tag 都要 8 字节对齐 */
.align 8
.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:
/* 终止 tag 表示 tags 部分结束 */
end_tag_start:
/* 每个 tag 都要 8 字节对齐 */
.align 8
.short MULTIBOOT_HEADER_TAG_END
.short 0
.long end_tag_end - end_tag_start
end_tag_end:
multiboot_header_end:
multiboot_entry:
/* ...... */
为了代码的可读性,使用宏定义来表示数值。具体内容参考: multiboot2.h
根据规范要求,我们将 Multiboot header 定义在了 text 段的开头,使数据尽量靠前。
这里使用了 entry address tag 表示程序入口的地址,也就是 GRUB 执行内核时要跳转的目标。不过,其实还有一个方法可以设置程序入口。
#include "multiboot2.h"
.text
jmp multiboot_entry
multiboot_header:
/* Align 64 bits boundary. */
.align 8
/* magic */
.long MULTIBOOT2_HEADER_MAGIC
/* ISA: i386 */
.long MULTIBOOT_ARCHITECTURE_I386
/* Header length. */
.long multiboot_header_end - multiboot_header
/* checksum */
.long -(MULTIBOOT2_HEADER_MAGIC + MULTIBOOT_ARCHITECTURE_I386 + (multiboot_header_end - multiboot_header))
/* 终止 tag 表示 tags 部分结束 */
end_tag_start:
/* 每个 tag 都要 8 字节对齐 */
.align 8
.short MULTIBOOT_HEADER_TAG_END
.short 0
.long end_tag_end - end_tag_start
end_tag_end:
multiboot_header_end:
multiboot_entry:
/* ...... */
相较于上一个代码,我们在 text 段的开头添加 jmp multiboot_entry
,并删除 entry address tag ,也能实现相同的功能。
这是因为,在没有使用 tag 指定程序入口时, GRUB 会直接执行我们的操作系统程序。此时,运行的第一条指令就是我们添加的 jmp multiboot_entry
2.2 启动入口
#include "multiboot2.h"
#ifdef HAVE_ASM_USCORE
# define EXT_C(sym) _ ## sym
#else
# define EXT_C(sym) sym
#endif
#define STACK_SIZE 0x4000
.text
multiboot_header:
/* ...... */
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
接下来,到了操作系统入口位置。
首先,需要做的事情是初始化栈指针:
#define STACK_SIZE 0x4000
:用宏定义表示栈的大小.comm stack, STACK_SIZE
:.comm
指令用来分配一块未初始化的数据段内存,这里分配了STACK_SIZE
字节的空间给stack
movl $(stack + STACK_SIZE), %esp
:因为栈从顶部向下生长,所以将栈的顶部地址放入栈指针寄存器%esp
中
接着清空 EFLAGS 寄存器:
pushl $0
:将立即数 0 压入栈popf
:将栈顶的值弹出,并加载到 EFLAGS 寄存器
初始化完成后,就可以调用内核入口的 C 语言函数。我们之前在 1.2 机器状态中提到过, EAX
和 EBX
寄存器保存着标识值和 Multiboot2 信息结构体地址,接下来内核会用到这些数据。由于内核代码是 C 函数,因此我们需要根据 C 语言的传参标准,按函数参数相反的顺序将数据压入栈中:
/* 内核入口函数定义: */
/* void cmain (unsigned long magic, unsigned long addr) */
pushl %ebx
pushl %eax
call EXT_C(cmain)
2.3 内核代码
这部分的代码和官方文档中的 kernel.c 一致。该内核的主要功能是在屏幕上打印出 Multiboot2 信息结构,主要用于测试 Multiboot2 引导加载程序,同时也可作为实现 Multiboot2 内核的参考示例。
2.4 代码构建
需要先在附录中获取 boot.S, kernel.c 和 multiboot2.h 代码
CC = gcc
LD = ld
CFLAGS = -m32 -fno-builtin -fno-stack-protector -nostartfiles
LDFLAGS = -Ttext 0x100000 -melf_i386 -nostdlib
KERNEL_NAME = kernel.bin
all: $(KERNEL_NAME)
$(KERNEL_NAME): boot.o kernel.o
$(LD) $(LDFLAGS) $^ -o $@
boot.o: boot.S
$(CC) -c $(CFLAGS) $< -o $@
kernel.o: kernel.c
$(CC) -c $(CFLAGS) $< -o $@
clean:
rm -f *.o $(KERNEL_NAME)
简单说明一下编译选项:
CFLAGS = -m32 -fno-builtin -fno-stack-protector -nostartfiles
-m32
:让编译器生成 32 位代码-fno-builtin
:禁用编译器对标准库函数(如 memcpy、strlen 等)的内建优化实现。确保这些函数不被替换为编译器优化版本-fno-stack-protector
:禁用栈保护功能,否则会编译报错-nostartfiles
:不使用标准启动文件,因为操作系统的启动方式与常规程序不同
LDFLAGS = -Ttext 0x100000 -melf_i386 -nostdlib
这些是传递给链接器(如 ld)的选项,用于控制链接行为:
-Ttext 0x100000
:表示程序的代码段(text segment)将被放置在内存地址 0x100000 处,这是加载操作系统常用的位置-melf_i386
:指定输出文件的目标格式是 32 位的 ELF 格式(适用于 i386 架构),这是 Multiboot2 规范建议的格式-nostdlib
:让链接器不要链接标准库。这是必要的,因为在内核或引导程序中,不会使用标准 C 库,而是会自己实现所需的功能或直接操作硬件
3 镜像制作
总所周知,现代 PC 有 Legacy
和 UEFI
两种启动方式,而接下来的镜像将会使用 Legacy + MBR
的启动方式。
3.1 创建镜像
使用 dd
命令创建一个 64M 的文件:
dd if=/dev/zero of=disk.img bs=1M count=64
使用 parted
为镜像文件分区:
parted -s disk.img mklabel msdos mkpart primary ext2 1MiB 100%
将镜像文件作为虚拟磁盘挂载,并创建 ext2 文件系统和挂载分区
sudo losetup -P /dev/loop0 disk.img
sudo mkfs.ext2 /dev/loop0p1
sudo mount /dev/loop0p1 /mnt
3.2 安装 GRUB
安装 Legacy
启动兼容的 GRUB ,并创建 grub.cfg
配置文件
sudo grub-install --target=i386-pc --boot-directory=/mnt/boot /dev/loop0
sudo mkdir -p /mnt/boot/grub
cat <<EOF > /mnt/boot/grub/grub.cfg
set timeout=20
set default=0
menuentry "MyOS" {
multiboot2 /boot/kernel.bin
boot
}
EOF
安装完成后,取消挂载
sudo umount /mnt
sudo losetup -d /dev/loop0
4 虚拟机运行
4.1 QEMU
运行目标平台为 i386 的 QEMU,内存要大于 128 M
qemu-system-i386 -m 1G -hda disk.img
4.2 VirtualBox
在 VirtualBox 中运行需要先将镜像文件转换为 vdi 格式
VBoxManage convertdd disk.img disk.vdi --format VDI
接着注册硬盘,并添加到虚拟机的 IDE 控制器中
在设置中关闭 EFI
这样就可以正常启动了
附录:完整代码
write_grub_cfg.sh
#!/bin/bash
KERNEL_NAME=$1
GRUB_CFG_PATH=$2
cat <<EOF > $GRUB_CFG_PATH
set timeout=20
set default=0
menuentry "MyOS" {
multiboot2 /boot/$KERNEL_NAME
boot
}
EOF
Makefile
CC = gcc
LD = ld
CFLAGS = -m32 -fno-builtin -fno-stack-protector -nostartfiles
LDFLAGS = -Ttext 0x100000 -melf_i386 -nostdlib
KERNEL_NAME = kernel.bin
IMG_NAME = disk.img
IMG_SIZE = 64
all: img
$(KERNEL_NAME): boot.o kernel.o
$(LD) $(LDFLAGS) $^ -o $@
boot.o: boot.S
$(CC) -c $(CFLAGS) $< -o $@
kernel.o: kernel.c
$(CC) -c $(CFLAGS) $< -o $@
.PHONY: img
img: $(IMG_NAME) $(KERNEL_NAME)
sudo losetup -P /dev/loop0 $(IMG_NAME)
sudo mount /dev/loop0p1 /mnt
sudo ./write_grub_cfg.sh $(KERNEL_NAME) /mnt/boot/grub/grub.cfg
sudo cp $(KERNEL_NAME) /mnt/boot/
sudo umount /mnt
sudo losetup -d /dev/loop0
$(IMG_NAME):
dd if=/dev/zero of=$(IMG_NAME) bs=1M count=$(IMG_SIZE)
parted -s $(IMG_NAME) mklabel msdos mkpart primary ext2 1MiB 100%
sudo losetup -P /dev/loop0 $(IMG_NAME)
sudo mkfs.ext2 /dev/loop0p1
sudo mount /dev/loop0p1 /mnt
sudo grub-install --target=i386-pc --boot-directory=/mnt/boot /dev/loop0
sudo mkdir -p /mnt/boot/grub
sudo umount /mnt
sudo losetup -d /dev/loop0
qemu: img
qemu-system-i386 -m 1G -hda $(IMG_NAME)
clean:
sudo umount /mnt || true
sudo losetup -d /dev/loop0 || true
rm -f *.o $(KERNEL_NAME) $(IMG_NAME)
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
.text
multiboot_header:
/* Align 64 bits boundary. */
.align 8
/* magic */
.long MULTIBOOT2_HEADER_MAGIC
/* ISA: i386 */
.long MULTIBOOT_ARCHITECTURE_I386
/* Header length. */
.long multiboot_header_end - multiboot_header
/* checksum */
.long -(MULTIBOOT2_HEADER_MAGIC + MULTIBOOT_ARCHITECTURE_I386 + (multiboot_header_end - multiboot_header))
entry_address_tag_start:
.align 8
.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:
end_tag_start:
.align 8
.short MULTIBOOT_HEADER_TAG_END
.short 0
.long end_tag_end - end_tag_start
end_tag_end:
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;
}
}
}
}
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 ANY
* 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 */
参考文献
Multiboot2 Specification version 2.0
写操作系统 之 GRUB 到 multiboot ——从 multiboot 开始对接内核(转载)
[操作系统原理与实现]Multiboot与GRUB_multiboot2-CSDN博客
grub2详解(翻译和整理官方手册) - 骏马金龙 - 博客园
用 GRUB 引导自己的操作系统_切换grub以实现系统的正常引导-CSDN博客
操作系统实现 - 055 multiboot2 头_哔哩哔哩_bilibili