内核加载的几个阶段
了解完了内核加载过程后,我们来总结一下。将加载过程分为几个阶段来整理一下。
bootloader加载bzImage
+----------------------------+
| |
. .
| |
+----------------------------+
| vmlinux.bin |
| |
0x100000 +----------------------------+
| |
. .
| |
+----------------------------+
| setup.bin |
0x10000 +----------------------------+
| |
. .
| |
0x00000 +----------------------------+首先bootloader应该是按照boot protocol将内核加载到内存。bzImage其实分成两部分,setup.bin和vmlinux.bin。
setup.bin是实模式内核
vmlinux.bin是保护模式内核
具体可以看bootloader如何加载bzImage中最上面的那个图。
另外值的注意的一点是实模式内核虽然加载到了0x10000,但是跳入运行的是0x10200。因为前512字节是用来做boot sector的。
从实模式进入保护模式
setup.bin的使命就是让系统切换到保护模式。这部分的工作在protected_mode_jump完成。
内存布局没有变化,这里也就不再描述。
移动保护模式内核,并进入移动后的内核
bootloader如何加载bzImage我们看到vmlinux.bin有个神操作,就是把根目录的vmlinux给压缩后包进去了。所以我们想要运行真正的vmlinux,还得要先把vmlinux给解压出来。
不过在解压缩前,还要做点准备工作。因为内核用的是in place decompression,所以我们先要把这一大包东西移动到计算好的位置,再开始解压缩。
那移动到哪里呢?这个其实也是内核配置好的。在没有配置CONFIG_RELOCATABLE的时候,我们目标将把压缩的内核解压缩到CONFIG_PHYSICAL_START这个地址。这个地址的默认配置是0x1000000。因为要做in place decompression,所以会先把保护模式内核移动到0x1000000后面的某一个位置。这样解压缩后0x1000000开始的内容就是vmlinux了。
注意,此时内存0x100000开始的一段空间,只有靠近尾部的地方存放了vmlinux.bin。等解压缩后此处才有我们想要的内容。
解压缩压缩内核,并按照PHD加载
这部分的工作主要在decompress_kernel中完成,但其中经历了两个变化。
首先是将压缩内核就地解压, __decompress()。
这个时候,0x1000000起始的内存里,就存放了完整的vmlinux这个elf文件。这也是为什么parse_elf直接从output地址开始解析。
解压缩完后,就是第二步按照elf格式加载,parse_elf()。比如我这次编译出的vmlinux的加载信息如下:
parse_elf会根据上面ProgramHeader的信息,将代码加载到指定位置。(PS: 没有配置CONFIG_RELOCATABLE的情况下)
所以parse_elf后,0x1000000处的内容又有变化。当然,这个地址也就是我们真正进入vmlinux时的物理地址。
进入加载后内核,并按照虚拟地址建立页表,从物理地址切换到虚拟地址
刚才看到内核解压缩完成,也知道解压缩后vmlinux开始运行时的真正地址,基本也就对内核的加载了解清楚了。不过其实还差最后一步。
我们之前所有的操作都用的是物理地址,比如0x1000000。但是我们在运行中看到的内核地址都是0xfff开头的虚拟地址。这就涉及到内核页表的构建了。在内核早期页表,我们探索了内核是如何映射高地址的内核虚拟地址的。
这里我们就再来看一下,内核页表设置完后,是怎么切换到虚拟地址的。
这个是更换页表后执行的代码,也就是跳转到0f处保存的地址common_startup_64。我们用nm来查看一下这个值。
用bochs设置断点0x1000000,然后一步步执行到更换cr3后,继续next。
此时的地址是不是正好是common_startup_64的地址0xffffffff81030dd8。
然后我们再反汇编一下,确认接下来要执行的确实是common_startup_64的代码。
看着没有问题。
至此,整个内核bzImage加载的流程就梳理完了。
Last updated
Was this helpful?