如何用grub引导你的内核
以下内容以GNU GRUB 0.97为标准,并且是从U盘引导。
1. 安装grub到U盘。
安装grub到U盘最方便的方法就是在linux系统下使用grub安装程序。
具体步骤如下:
a) 首先找到U盘所在设备(这里假设为/dev/sdb1),然后挂载:
mkdir /mnt/sdb1 mount /dev/sdb1 /mnt/sdb1
b) 创建相关目录
mkdir /mnt/sdb1/boot mkdir /mnt/sdb1/boot/grub
找到文件stage1
, stage2
, 和 *_stage1_5
的位置
find /usr/ -name statge
d) 复制以上文件到grub 目录(假设上面的文件在/usr/share/grub/ 目录下):
cp /usr/share/grub /mnt/sdb1/boot/grub cp /usr/share/grub /mnt/sdb1/boot/grub
如果U盘是用FAT文件格式格式化的则复制fat_stage1_5,这里用的是FAT32:
cp /usr/share/grub /mnt/sdb1/boot/grub
e) 运行grub安装程序:
sudo grub
接着如下操作:(其中设备号要具体确定)
好了现在U盘上已经装好了grub,引导配置写在grub.conf中。
2. 用grub引导 内核
grub有两种不同的引导机制,一种是直接 载入系统内核,另一种是通过chainloader载入另一个bootloader来引导自己的系统。可以被bootloader原生支持的内核一般要符合多重引导规范(Multiboot Specification)。当然为了方便grub也支持直接引导linux,FreeBSD等。
我们要用grub来引导自己的内核,当然我们不希望自己写一个bootloader再让grub来chainload,所以我们得支持多重引导规范。
一个简单的内核实例(grub0.97源文件/docs/下面有三个文件multiboot.h,loader.s,kernel.c,下面作了简化):
loader.asm:这里使用nasm语法
global loader ; making entry point visible to linker
extern kmain ; kmain is defined elsewhere
; setting up the Multiboot header - see GRUB docs for details
MODULEALIGN equ 1<<0 ; align loaded modules on page boundaries
MEMINFO equ 1<<1 ; provide memory map
FLAGS equ MODULEALIGN | MEMINFO ; this is the Multiboot 'flag' field
MAGIC equ 0x1BADB002 ; 'magic number' lets bootloader find the header
CHECKSUM equ -(MAGIC + FLAGS) ; checksum required
section .text
align 4
MultiBootHeader:
dd MAGIC
dd FLAGS
dd CHECKSUM
; reserve initial kernel stack space
STACKSIZE equ 0x4000 ; that's 16k.
loader:
mov esp, stack+STACKSIZE ; set up the stack
finit ; initialize FPU
push eax ; pass Multiboot magic number
push ebx ; pass Multiboot info structure
call kmain ; call kernel proper
cli
hang:
hlt ; halt machine should kernel return
jmp hang
section .bss
align 32
stack:
resb STACKSIZE ; reserve 16k stack on a quadword boundary
编译方法:
nasm -f elf -o loader.o loader.asm
kernel.c
void kmain( void* mbd, unsigned int magic )
{
if ( magic != 0x2BADB002 )
{
/* Something went not according to specs. Print an error */
/* message and halt, but do *not* rely on the multiboot */
/* data structure. */
}
unsigned char *videoram = (unsigned char *) 0xb8000;
char str[]="hello world";
int i;
for (i=0;i<sizeof(str);i++) {
videoram[i*2] = str[i]; /* character 'A' */
videoram[1] = 0x07; /* forground, background color. */
}
/* Write your kernel here. */
}
编译方法
gcc -o kernel.o -c kernel.c
为了方便我们使用一个链接文件
linker.ld:
ENTRY (loader)
SECTIONS{
. = 0x00100000;
.text :{
*(.text)
}
.rodata ALIGN (0x1000) : {
*(.rodata)
}
.data ALIGN (0x1000) : {
*(.data)
}
.bss : {
sbss = .;
*(COMMON)
*(.bss)
ebss = .;
}
}
内核必须被加载到1M以上的内存。
最后链接:
ld –T linker.ld -o kernel.bin loader.o kernel.o
从U盘启动:
root (hd0,0) kernel /boot/kernel.bin boot
然后可以看到在屏幕的左上角打卬出了hello world。
3. grub引导完内核后的机器状态
1.EAX必须包含魔数;0X2BADB002,这个值告诉操作系 统它是由兼容multiboot的bootloader引导的。
2.EBX 必须包含bootloader提供的多重引导信息结构的32位物理地址。
我们可以在进入kmain之前把这两个参数通过压栈传过去, 像上面loader.asm中做的一样。
3.CS指向一个以0为基址,4GB-1为界限的代码段描述符
4.DS,SS,ES,FS和GS指向一个以0为基址,4GB-1为界限的数据段描述符。
5.A20地址线被打开
6.CR0的PE位置位,PG位清除,即保护模式开启,分页关闭。
7.EFLAGS的VM位必须清除,IF位必须清除。
8.GDTR这个时候可能不合法,所以在设置成指向正确的GDT之前,即使段寄存器像上面一样设置好了也不要用。
9.中断被关闭。
所以要让自己的内核可以被grub引导,其实只要添加一个multiboot header(参看loader.s开头部分);之后我们就可以根据加载后grub提供的环境,利用eax传过来的参数检测是否被正确加载,利用ebx传过来的参数得到内存以及加载等各种信息。由于A20地址线已经打开,保护模式也打开,我们 只需要正确设置GDTR和各种段寄存器就行了。我们甚至就可以在上面的kmain函数的基础上,根据我们的需要开始编写自己的内核了。因此grub使得编写操作系统的起步就变得相当容 易。
4. 参考资料
1.the grub manual
2.grub multiboot
3.wiki.osdev.org
5. http://wiki.osdev.org/Bare_bones#Booting_the_kernel
5. 其它
关于jos的grub引导:(MIT的一个6.828课程的实验操作系统)
其它jos是支持multiboot的,看看jos/kern/entry.S,发现开始已经定义了multiboot header。
再看看jos/kern/Makefrag,可以发现有一个grub的目标,正好是用来生成grub引导用的内核jos-grub。
关于Orange’s的grub引导:(<<Orange's 一个操作系统的实现>>书中的系统)
其实只要在oranges/kernel/kernel.asm中添加multiboot header和GDT的定义,把切换GDT改到加载刚定义的GDT。
2010年3月22日 05:52
nice work!