写操作系统 之 GRUB 到 multiboot ——从 multiboot 开始对接内核(转载)

本文详细介绍了如何编写一个简单的操作系统内核,包括满足GRUB启动规范的MultibootHeader,以及使用汇编和C语言构建内核的步骤。通过创建汇编入口、内核入口函数,然后将两者链接生成内核,并将其复制到软盘镜像以供GRUB启动。
摘要由CSDN通过智能技术生成

跟我一起写操作系统(二)——史上最简单的内核、

CSDN 转载地址:https://blog.csdn.net/zdy0_2004/article/details/48920737

转载注明出处:http://www.cnblogs.com/lucasysfeng/p/4847662.html

上一讲地址:http://www.cnblogs.com/lucasysfeng/p/4846119.html

项目地址:https://github.com/lucasysfeng/lucasOS

上一讲我们介绍了计算机的启动流程,并给出了一份简单的主引导记录代码,此份代码仅仅是显示几个字符,并没有做它本应该做的事–启动内核。本讲我们首先看下内核是如何被启动的,然后写一个简单的内核,用已经实现的主引导记录配合GRUB启动它。

如何启动内核


前一讲我们说到,计算机读取"主引导记录"前面446字节的机器码之后,会运行事先安装的“启动管理器”bootloader,由用户选择启动哪个内核,之后就会载入内核,将控制权交给内核。GNU GRUB(GRand Unified Bootloader)就是一种bootloader,满足多重引导规范(The Multiboot Specification),GRUB可选择操作系统分区上的不同内核,下图就是GRUB的图形界面:
原图没有了,所以我按照我的理解放上我个人的图:
图 GRUB 界面

能够被GRUB启动的内核需要满足两个的条件:

(1) 内核的前8K字节内必须要包含多重引导规范的头信息(Multiboot Header);
(2) 内核要加载在内存地址的1MB以上。

那么Multiboot Header是什么样子的呢?它必须包含4字节对齐的3个域(还有其他非必须域,我们不讨论),如下:

魔数域(magic):标志头的魔数,必须等于 0x1BADB002。。
标志域(flag):是否需要引导程序支持某些特性,我们不关心这些特性,这个标志置为0。
校验域(checksum):校验等式是否成立(magic + flags + checksum = 0)

本文不讨论GRUB的实现,我们会用前人已经写好的GRUB(笔者会给出),我们要做的是完成符合GRUB启动规范的内核。为了完成这个内核,我们需要写少量的汇编用来在内核中加入Multiboot Header,然后用C语言写内核入口,最后将汇编目标代码和C语言目标代码链接起来生成真正的内核。下面就让我们一步步地完成这些吧!

第一步 汇编入口

1. 汇编代码如下:
; 文件名 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下一节实现。

2. 编译生成目标文件boot.o

# nasm -f elf boot.asm -o boot.o

运行上面命令后会生成目标文件boot.o,-f elf的意思是生成ELF格式的目标代码。

第二步 内核入口


1. 内核代码如下:
/****************************************************
# 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是显存开始的地址,读者可以看第一讲(http://www.cnblogs.com/lucasysfeng/p/4846119.html)“实模式内存地址空间分布”那张图,找到0xb8000h这个地址。从0xb8000h这个地址开始,每2个字节表示一个字符,前一个字节是字符的ASCII码,后一个字节是这个字符的颜色和属性,颜色和属性此处先不用关心。这段C代码的其余部分相信读者都能看得懂,我就不过多解释了。

2. 编译生成目标文件kernel.o

# gcc -m32 -c -o kernel.o kernel.c

运行上面命令后,目标文件kernel.o就生成了。

第三步 生成内核


上面讲到了能被GRUB启动的内核需要满足的条件:

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

/***************************
* 文件名: 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(大小端问题,反向存储)

在这里插入图片描述

第四步 将内核拷贝到软盘镜像

我们这里不制作软盘镜像,而是使用已经制作好的软盘镜像,镜像名称lucasOS.img,已经放在github上了。我们也无需制作GRUB,这个软盘镜像已经包含了GRUB.我们要做的是把内核文件kernel拷贝到软盘镜像lucasOS.img中。

1. 获取lucasOS.img软盘镜像。

lucasOS目录下的lucasOS.img就是我们要的软盘镜像。

# git clone https://github.com/lucasysfeng/lucasOS.git

2. 创建挂载点。

# sudo mkdir /mnt/lucasOS

3. 挂载软盘镜像。

注意把lucasOS.img改为你的lucasOS.img所在路径。

# sudo mount lucasOS.img /mnt/lucasOS

4. 把内核文件拷贝到软盘镜像中。

注意把kernel改为你的kernel所在路径。

# sudo cp kernel /mnt/lucasOS/kernel

5. 卸载软盘镜像。

# sudo umount /mnt/lucasOS

第五步 启动内核

上一讲我们用软盘镜像启动了一个空的虚拟机,下面用同样地操作启动虚拟机,要记得把软盘镜像lucasOS.img从ubuntu拷贝到windows下。这里我们使用VMware创建虚拟机,当然也可以使用其他软件创建虚拟机。

1. 创建空的虚拟机,去掉开机从CD/DVD启动选项。
2. 网络选择host-only模式。
3. 选择从软盘驱动,路径选择已经拷贝到windows下的镜像lucasOS.img.
4. 开启虚拟机电源,看到如下的画面(约停留2s),恭喜你,GRUB成功了!

由于原图失效,我放的是我转载后个人用bochs运行的图
图 GRUB 界面

5. GRUB启动成功后,会自动载入内核,出现下面画面,恭喜你,载入内核成功!

由于原图失效,我放的是我转载后个人用bochs运行的图
在这里插入图片描述
 好了,至此,我们完成了内核的启动流程,下一讲我们开始内核之旅!

代码获取

本系列GitHub地址 https://github.com/lucasysfeng/lucasOS,用下面命令获取代码:

# git clone https://github.com/lucasysfeng/lucasOS.git

本讲的代码是code/chapter2,笔者已经将上面的命令集成到Makefile中了,读者只需进入目录,按ReadMe.txt说明执行即可,有问题请留言

参考

  1. http://www.jamesmolloy.co.uk/tutorial_html/2.-Genesis.html
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值