bootloader如何加载bzImage

在进入start_kernel之前,那真的是一片黑暗的路程。因为好多都是用汇编写的,对我来说简直就是抓瞎。

bzImage的全貌中我们看过了make install时安装的bzImage的组成部分。而start_kernel在这几个组成部分的最后一点。

                                     *
                                     | <-- vmlinux.lds.S
                                     |
                                   vmlinux
                                     |
                                     | <-- objdump
                                     |
                             arch/x86/boot/compressed/vmlinux.bin
                                     |
                                     | <-- compress
                                     |
                             arch/x86/boot/compressed/vmlinux.bin.zst
                                     |
                                     | <-- mkpiggy
                                     |
                             arch/x86/boot/compressed/piggy.S
                                     |
                                     |
                                     |  arch/x86/boot/compressed/*
       arch/x86/boot/*                \  /
              |                        \/
              | <-- setup.ld            | <-- vmlinux.lds
              |                         |
              |                         v
              |              arch/x86/boot/compressed/vmlinux
              |                         |
              |                         | <-- objcopy
              |                         |
              v                         v
    arch/x86/boot/setup.bin  arch/x86/boot/vmlinux.bin  
                   \         /
                    \       /
               arch/x86/boot/bzImage

这次我们就来看看被安装的内核是通过哪些步骤走到start_kernel的。

从代码上看,内核加载后到start_kernel前经历了下面的步骤。

setup.bin:

vmlinux.bin:

vmlinux:

但是真的是这样吗?如何可以确认呢?经过一番研究,发现第一步要确认的是bzImage被bootloader加载到了哪里。

幸好我们知道bochs模拟器,那就请出他来确认一下整个流程吧。

安装bochs

bochs是一个x86的模拟器,据说还能运行win98。而且调试友好,在《自己动手写操作系统》一书中就是用bochs来运行手写的操作系统的。这里我们就要再次请出它来帮助我们了解start_kernel之前的黑暗世界。

在ubuntu上运行这两个命令就能安装bochs了。记得安装bochs-x,否则会报错。

准备启动镜像

其实x86内核编译里有制作启动盘的目标,包括了软盘、光盘、硬盘。这里我们只用光盘。

另外记得安装个依赖

执行这条命令就可以生成arch/x86/boot/image.iso启动光盘,其中包含了当前目录编译出的最新kernel。 但是这个命令有个问题,不确定是不是内核开发遗漏了,一定要加上这个改动才能制作成功。

就是一定要指定根文件才能制作。等有空了我问问内核社区这是几个意思。

bochs配置文件

有了镜像,bochs也装好了,接下来我们就可以启动了。

可以参考下面的配置,

我是把image.iso放在配置文件同一个目录的,大家可以根据自己习惯调整。

然后,启动,运行!

内核加载到了哪里?

一切的都很顺利?但是说好的调试呢?断点在哪里设置?什么时候进入的保护模式?

好像我们什么都不知道。我们想要的是内核究竟被加载到哪里了。这样我们才能设置断点,然后调试查看。

这时候我突然想到了内核文档,说不定文档里会有写呢?别说,我还真找到一个文档boot.rst。人是这么说的:

实模式的内核加载地址是个X,这有点头大。那究竟是哪里呢?

回忆一下《自己动手写操作系统》,在开机上电到内核运行经历了这么几个步骤:

  • 系统先运行BIOS

  • 由BIOS找到boot sector

  • boot sector加载loader

  • loader加载内核,并跳转

所以在内核运行前,还有几个步骤要执行。在我们制作出的启动镜像里,这个loader是syslinux完成的。还记得我们制作镜像时安装的依赖么?具体可以看arch/x86/boot/genimage.sh中geniso函数。想进一步了解syslinux的,可以参考Syslinux Tutorial

所以决定内核加载到哪里的,是syslinux决定的。既然都是开源代码,那就。。。看代码吧。

在此跳过细节,直接给出结果。在我编译的内核情况下,实模式内核加载到了0x10000,保护模式内核加载到了0x100000。想要看看syslinux的,可以在syslinux上找到我定位内核加载地址的代码。

确认内核加载地址

知道了内核加载到哪里,我们就可以在对应的地址设置断点,来确认这个发现是不是真的。

但是一直看不到停在实模式内核代码上,这是为什么呢?看了代码想起来了,原来实模式内核的第一个扇区是一个引导盘。实际有功效的代码是在512字节后。所以syslinux是直接条到这里开始的么?

那我们就把断点调整以下,看看效果。

怎么样,当你看到在断点停下来的时候,是不是很激动人心!(断点有两次会停在bootloader里,所以前两次的忽略。)

下面上调试的实际结果,来感受以下。

反汇编实模式内核代码

实模式内核加载地址+偏移512的断点触发了。确认当前地址是0x10200。

查看当前寄存器,确认目前在实模式,CR0的pe是小写。页表也没有打开pg是小写。

此时赶紧打开arch/x86/boot/header.S确认一下反汇编的结果。

实模式内核512偏移处先是一个jmp,接下来20条指令和bochs中反汇编的是一模一样啊。这不就是咱要找的吗!

反汇编保护模式内核代码

已经看到了实模式内核的真容,那接下来就看看保护模式的内核吧。

我们直接continue后,就停在了0x100000的地址。这个就是我们刚才设置保护模式内核的断点地址。

查看寄存器,此时保护模式确实已经打开,CR0的PE是大写的。不过页表还没有开启。

反汇编一下代码,我们继续来看看是不是我们期待的。

这时候要看哪里的代码呢?对了,是arch/x86/boot/compressed/head_64.S。其中startup_32就是我们要找的。

startup_32开始的20条指令和反汇编里显示的是不是也是一模一样?那就说明我们又找对啦。

至此,我们已经做好了用bochs探索内核进入start_kernel前的准备。感觉就像那黑暗的隧道里,照进了光。

PS: 感谢《自己动手写操作系统》,没有它我可能还在黑暗中摸索。

Last updated

Was this helpful?