通过grub启动自己的内核

★ 原理
https://blog.csdn.net/liyuanbhu/article/details/7583595

在 PC 机上捣鼓自己的操作系统遇到的第一个难题就是如何将内核加载到内存中执行。读过于渊写的《自己动手写操作系统》,发现这部分的工作非常繁琐。而且实际上这部分工作和操作系统没太大的关系。好在开源的引导加载程序也已经发展的很成熟了,如grub和uboot。我们可以利用前人的成果,将自己的操作系统改造成可以用现有引导加载程序引导的内核。

这里采用GRUB作为引导加载程序。uboot主要用于arm芯片的flash启动。

GRUB 引导加载程序广泛应用于 Linux、各种 BSD 系统的引导,具有极高的可靠性。满足多重引导规范(The Multiboot Specification),可以引导各种满足多重引导规范的操作系统内核。

多重引导规范
多重引导规范并不强制要求内核的格式,但是如果采用 ELF 格式,将会带来许多方便。本文下面的介绍都是基于内核采用 ELF 格式。如果您的内核碰巧不能采用 ELF 格式,请您参考多重引导规范的官方文本中 3.1 节关于 Multiboot Header 的介绍。

能够被 GRUB 引导的内核有两个条件:
(1) 需要有一个 Multiboot Header ,这个 Multiboot Header 必须在内核镜像的前 8192 个字节内,并且是首地址是 4 字节对齐的。
(2) 内核的加载地址在 1MB 以上的内存中,这个要求是 GRUB 附加的,并非多重引导规范的规定。

Multiboot Header的分布必须如下所示:


偏移量 类型 域名 备注
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]被置位

最后,最重要的,需要在grub的menu.lst中添加:
find –set-root /yourkernel
kernel /yourkernel

★ 例子:

史上最简单的内核
https://blog.csdn.net/zdy0_2004/article/details/48920737

1、写代码boot.asm
; 文件名 boot.asm
; Copyright: www.cnblogs.com/lucasysfeng

MBOOT_MAGIC equ 0x1BADB002 ; multiboot magic域,必须为此值
MBOOT_FLAGS equ 0x00 ; multiboot flag域, GRUB启动时是否要做一些特殊操作
MBOOT_CHECKSUM equ -(MBOOT_MAGIC + MBOOT_FLAGS) ; multiboot checksum域,校验上面两个域是否正确

[BITS 32] ; 以32位编译

section .text
dd MBOOT_MAGIC
dd MBOOT_FLAGS
dd MBOOT_CHECKSUM
dd start

[GLOBAL start]
[EXTERN kernel_main] ; 内核入口函数, EXTERN表明此符号在外部定义

start:
cli ; 禁用中断
call kernel_main ; 调用内核入口函数
jmp $ ; 无限循环

在上面汇编中,我们定义了GRUB启动需要的域MBOOT_MAGIC、MBOOT_FLAGS和MBOOT_CHECKSUM,并调用了内核入口函数kernel_main, kernel_main下一节实现。
编译:nasm -f elf boot.asm -o boot.o

2、写代码kernel.c
/****************************************************
# Copyright(c) www.cnblogs.com/lucasysfeng, all rights reserved
# File : kernel.c
# Author : lucasysfeng
# Description : 内核入口函数
****************************************************/

int kernel_main()
{
// 显存开始地址
char *display_buf = (char*)0xb8000;

// 清屏
unsigned int i = 0;
const unsigned int total = 80 * 25 * 2; // 一屏25行,每行80个字符,每个字符2个字节
while(i < total)
{
display_buf[i++] = ' ';
display_buf[i++] = 0x04; // 颜色
}

// 显示字符
const char *str = "Hello World, welcome to kernel!";
for (i = 0; '\0' != *str;)
{
display_buf[i++] = *(str++);
display_buf[i++] = 0x04;
}

return 0;
}

0xb8000h是显存开始的地址,从这个地址开始,每2个字节表示一个字符,前一个字节是字符的ASCII码,后一个字节是这个字符的颜色和属性。
编译:gcc -m32 -c -o kernel.o kernel.c

3、生成内核
能被GRUB启动的内核需要满足的条件:
(1) 内核的前8K字节内必须要包含多重引导规范的头信息(Multiboot Header);
(2) 内核要加载在内存地址的1MB以上。
头信息放在了汇编生成的目标文件boot.o中,因此需要将boot.o和kernel.o链接到一起生成真正的kernel,并且这个真正的内核要加载到1MB内存上。
为此,编写下面的链接脚本和命令(关于链接脚本的使用自行google):
写代码link.ld
/***************************
* 文件名: link.ld
***************************/

ENTRY(start)
SECTIONS
{
. = 0x100000;

.text :
{
*(.text)
. = ALIGN(4096);
}
.data :
{
*(.data)
*(.rodata)
. = ALIGN(4096);
}
}

用ld命令链接目标文件boot.o和kernel.o,指明使用链接脚本link.ld:
ld -T link.ld -m elf_i386 -nostdlib boot.o kernel.o -o kernel

运行上面命令后,会生成我们要启动的真正的内核kernel,那么这个kernel是否满足GRUB启动规范呢?我们可以通过反汇编来看一下:
objdump -d kernel | head -n30
看到地址100000了吗,这个就是.text段起始的地址即1M,看到02 b0 ad 1b 00 00了吗,这个就是GRUB魔数域1b ad b0 02(大小端问题,反向存储)

4、将kernel添加到虚拟机的根目录,并在menu.lst中添加:
find --set-root /kernel
kernel /kernel

Leave a comment

Your email address will not be published. Required fields are marked *