- 论坛徽章:
- 3
|
本帖最后由 captivated 于 2020-03-31 18:44 编辑
x86 arch 的启动代码比较复杂,为了说清楚我的具体问题,所以先做一个简述(免得说我自己没把基本问题搞清楚,省点抬杠时间)。
此外以下简述主要针对 x86_64 arch 而不是 i386.
1. 启动映像生成:
x86 arch 的启动映像为 $OUT/arch/x86/boot/bzImage. $OUT 表示 kernel 编译输出目录(如果编译时不指定 -o 输出目录,则和编译源码是同一个目录)。
bzImage 构成如下。为了方便,$OUT/arch/x86/boot 目录简称为 $(boot) 路径.
1) 最终的 bzImage 是由 $(boot)/setup.bin 和 $(boot)/vmlinux.bin 组装成的.
$(boot)/vmlinux.bin 又由 $(boot)/compressed/vmlinux 通过 objcopy 剥离掉 .comment 和 .note 段而成.
而 $(boot)/compressed/vmlinux 则由 $(boot)/compressed 下面的各个目标文件组成, 里面最重要的是
$(boot)/compressed/piggy.o 这个文件.
- bzImage <-+ $(boot)/setup.bin
- #######-+ $(boot)/vmlinux.bin <-+ $(boot)/compressed/vmlinux <-+ $(boot)/compressed/head_64.o
- ############################################### $(boot)/compressed/misc.o
- ############################################### ...
- ############################################### $(boot)/compressed/piggy.o
复制代码
2) 为什么说 piggy.o 重要, 因为它是由压缩了的 vmlinux 构成的. 这里默认选 gzip 压缩.
而 vmlinux.bin.gz 又是由 compressed 同目录下的 vmlinux.bin 和 vmlinux.relocs 构成的.
vmlinux.bin 和 vmlinux.relocs, 则来自于 $OUT 目录下的 VMLINUX(实际文件名就是 $OUT/vmlinux. 大写的意思是表示它其实就是编译最终的链接文件, 从它到最终的 bzImage 不过是一堆二进制工具为了启动加载协议做的一堆变换而已).
- $(boot)/compressed/piggy.o <+ $(boot)/compressed/vmlinux.bin.gz <-+ $(boot)/compressed/vmlinux.bin
- ############################################### $(boot)/compressed/vmlinux.relocs
复制代码
2. x86 arch 启动问题
x86 相关的 boot protocal 有几种. 不管哪一种,总之 bzImage 得要 bootloader 加载到内存里面然后去运行才行.
前面的映像构建过程为什么是那样子, 估计也和 x86 arch “历史悠久”, boot protocal 复杂相关.
但大概来说, 有这么几种 boot protocal:
1) 实模式 boot protocal.
这种情况下, bootloader 是运行在实模式的. 就算 bootloader 自己切换了保护模式, 它加载完 kernel(bzImage)并跳转过去执行前, 也得切回实模式, 因为 protocal 就是协议嘛, 大家要有个商量. 这时 bootloader 切换过去后, 最先从 setup.bin 开始运行(bzImage 是由 setup.bin + vmlinux.bin 包的), 也就是走
arch/x86/boot/header.S -> _start -> calll main
arch/x86/boot/main.c -> main -> go_to_protected_mode
arch/x86/boot/pm.c -> go_to_protected_mode -> protected_mode_jump
arch/x86/boot/pmjump.S -> GLOBAL(protected_mode_jump) -> jmpl *%eax
经过这些步骤, 就会开始执行到 vmlinux.bin 里面的东西了.
2) 32 位 boot protocal
现在 bootloader 已经很强大了, 所以通常 bootloader 自己已经切到 32 位 protected mode, 然后就不走 setup.bin 那一套了.
前面说了 bzImage 是由 setup.bin + vmlinux.bin(都在 $(boot) 目录下)组装而成, 跳过 setup.bin 的话, 当然就直接从 vmlinux.bin 开始执行了.
bootloader 为什么可以这样做, 因为 bzImage 就是 bootloader 加载的, 它当然知道 setup.bin vmlinux.bin 分别在什么内存位置.
一般来说, setup.bin 必须要运行在实模式, 因此其加载地址是物理内存 1MB 以下的, 而 vmlinux.bin 会刚刚好从 1MB 位置开始放置.
anyway, 这时候走的是这个:
arch/x86/boot/compressed/head_64.S -> ENTRY(startup_32) -> ENTRY(startup_64) -> jmp *%eax
这里还要说明一下. 前面映像构建过程说得清楚, 真正的 kernel 是 piggy.o(由 $(OUT)/vmlinux 压缩, 然后生成的目标文件).
startup_32 -> startup_64 主要是从 32 位保护模式切换到 64 bit long mode. 切换之前会建立一个 4GB 简单 identity map(因为切换到 64 bit long mode 的条件之一就是, paging 必须是开的, 即 CR0.PG == 1).
而 startup_64 呢, 则会调用一个解压缩函数将 vmlinux.bin.gz 解压, 然后跳转到解压后的 kernel 入口去执行. 这里面可能还会有重定位处理等等.
如果关掉 KASLR, 那么解压后的 kernel, 也就是真正的 $(OUT)/vmlinux, 默认会放在 16MB 的位置.
3) 64 位 boot protocal
顾名思义, 就是 bootloader 有点过分强大(或者讨厌)了, 跳转到 kernel 之前, 它会自己先切到 64 bit long mode.
也就是直接执行 arch/x86/boot/compressed/head_64.S 中的 startup_64 了.
这时最初的 paging 是 bootloader 建立的.
3. vmlinux entry
好了,前面这些复杂的过程略过. 不管是先从 arch/x86/boot/setup.S 开始, 还是先从 arch/x86/boot/compressed/head_64.S 开始,
最终解压缩后的 kernel 入口点是:
arch/x86/kernel/head_64.S 里面的 startup_64 函数(or 一个全局符号).
有人可能会问这不和 compressed/head_64.S 里面的 startup_64 重名吗, 答案是它们根本没链接在一起, 各管各的, 不会有重定义错误放心.
啊, 约了不知道多少次电影请喝了多少次奶茶爬了多少次山, 终于可以约爬床了, 终于宽衣解带了, 兴奋之情溢于言表啊...
结果才看了特么几行, 我就被郁闷到了...
...
具体问题叙述还有点小麻烦, 呆会贴上来.
...
|
|