本人水平相当有限,不当之处,欢迎指正。
在各种各样使用Linux的计算机系统中,系统启动的早期阶段,通常都会有这样一个环节,boot loader程序将内核映像加载到内存中,然后跳转到映像的起始位置开始执行。
接下来,内核就跑起来了。
那么,这个内核映像到底是个什么东东呢。本文就来简单介绍一下Linux内核映像的生成过程。
本文依据的内核版本 2.6.32
(一) 编译内核源码得到原始的内核目标文件kernel.o
这其实也就是真正意义上的内核了。系统中实际运行着的内核,其实就是他了。
这是一个elf格式的目标文件。
这个elf文件的入口代码在一个汇编文件中,一般叫head.S,用来实现最初的初始化,然后将执行流程转移到统一的内核初始化入口函数start_kernel(C代码)。
对于arm而言,入口就是arch\arm\kernel\head.S中的ENTRY(stext)。
对于32位X86,入口就是arch\X86\kernel\head_32.S中的ENTRY(startup_32),然后转到C函数i386_start_kernel,再转到start_kernel。
内核目标文件的文件名,在不同架构的makefile中叫法可能不尽相同。我们这里就叫他 kernel.o 好了。
不同架构下的具体文件名,参见arch\xxx\boot\compressed\Makefile
另外,我们下文中所使用的文件名,也可能与makefile中的命名不一致。
不是不想一致,而是不同架构makefile中的命名确实不尽相同。
因此,我们这里为了表达的方便,就重新统一命名啦。
好了,kernel.o虽然是真正的内核,但他不是一下子就能运行起来的。就像火箭的发射需要发射架一样。
kernel.o要想跑起来,也需要再搭几个支架。
接下来的事情,就是一步步地在搭这个架子。
(二) 用objcopy工具给 kernel.o 减肥,得到kernel.bin
经过objcopy处理(其实就是二进制化)后,得到只剩下纯代码与数据的文件kernel.bin(raw binary格式)。
注意,kernel.bin已经不是elf文件了。他里面只有纯粹的指令与数据。
只所以这样做,是因为启动阶段的系统,软件环境很简陋,没有代码来分析elf文件的结构。
要加载一个程序,就是将他拷到内存中某个位置,然后跳到该位置执行。
因此,程序必须经过精心的链接,再去除所有不需要的信息,只留下纯粹的指令与数据。
要将一个elf文件二进制化,通过命令“objcopy -O binary src_file dst_file”即可。
(三) 用gzip压缩 kernel.bin 得到 piggy.gz
(四) 创建piggy.o用于包含piggy.gz文件的全部数据
piggy.o是一个elf文件,他内部包含了piggy.gz文件的全部数据。
piggy.o的目的,就是为了将piggy.gz作为数据与内核映像的其他部分进行链接。
在arm下,通过对arch\xxx\boot\compressed\piggy.S文件的编译,实现了piggy.o的创建。
在i386下,通过arch\i386\boot\compressed\vmlinux.scr链接脚本以及
arch\i386\boot\compressed\Makefile中类似如下的依赖关系,直接链接得到了piggy.o
$(obj)/piggy.o: $(src)/vmlinux.scr $(obj)/piggy.gz
(五) 将piggy.o及其他东东链接成vmlinux
将piggy.o 与一些外围代码head.o、misc.o、链接成vmlinux。
外围代码,主要用于解压内核,并将执行流转移到内核的入口代码处执行。
因此,对于更上层的引导者来说,vmlinux是一个可以自解压并自行运行的内核。
对于32位x86来说,外围代码head.o、misc.o对应于arch\x86\boot\compressed下面,分别对应于head_32.S,misc.c
head_32.S是外围代码的入口,他调用misc.c中的解压例程,将内核解压,得到真正的内核,然后跳转到内核入口代码执行。
(六) 生成最终的内核映像文件
a) 对于arm来说,步骤如下:
用objcopy工具给 vmlinux 减肥,得到zImage
vmlinux是elf文件,经过objcopy处理后,得到只剩下代码与数据的文件zImage(raw binary格式)。
注意,zImage已经不是elf文件了。
b) 对于x86来说,步骤与arm略有不同。
用objcopy工具给 vmlinux 减肥,得到vmlinux.bin(raw binary格式)。
再将bootsect(raw binary格式) 、setup(raw binary格式)与vmlinux.bin拼接到vmlinux.bin前面,得到bzImage。
其中,bootsect对应于arch\i386\boot\bootsect.S,setup对应于arch\i386\boot\setup.S。
这两个文件有什么用呢?
(1) bootsect.S
其实是用于写到软盘的0扇区中(512字节),用于从软盘启动Linux内核。在2.6的内核中,这个文件已经不起作用了。
因此,2.6的内核中如果跑到他,他就简单打印一点错误信息,告诉用户需要由boot loader加载内核。
然后让用户按任意键重启。
而boot loader(例如,grub)在加载内核后,并没有跳转到内核映像起始位置执行,而是跳过了前面的512字节,即跳过了bootsect.S的内容。
(2) setup.S
一般来说,如果是从arm u-boot环境启动。u-boot会将单板基本硬件信息放到一个特定位置,然后内核可以由此获取硬件信息。
而对于pc机来说,则是由setup.S来获取(通过bios)基本硬件信息,放到合适的位置,然后内核再由此获取硬件信息。
如果安装内核的话,新编译的bzImage将被拷贝到类似/boot/vmlinuz-2.6.18-194.el5这样的路径,后者的路径则有可能同时再被更新到/boot/vmlinuz软链接中。这样一来,/boot/vmlinuz总是指向系统中最新安装的内核。