构建内核
了解内核映像的构建过程无论是对学些内核,还是运行内核开发都有诸多帮助。所以在构建内核之前,首先讨论了内核的构建过程。
对于编译内核而言,一条make命令就足够了。因此,构建内核最困难的地方不是编译,而是编译前的配置
内核启动的最后,内核要从根文件系统加载用户空间的程序从而转入用户空间。因此,我们准备了一个基本的根文件系统来配合内核的启动。
内核映像的组成

如果将内核的映像比较航天器,则setup.bin部分就类似于火箭的一级推进子系统。最初,这部分负责将内核加载进内存,并为后面内核包含模式的运行建立基本的环境。但后来加载内核的功能被分离到Bootloader中,setup.bin则退货为复制Bootloader将内核加载到内存。
紧接着,包围在32位包含模式部分外的是非解压缩。这部分可以看作是火箭的耳机推进子系统,复制将压缩的内核解压到合适的位置,并进行内核重定位,在完成这个环节后,其从内核映像脱离。
最后是内核的32位包含模式部分vmlinux。这部分相当于航天器的有效载荷,即类似于最后运行的卫星,最有这部分最后留在轨道内运行。内核构建时,将对有效载荷vmlinux进行压缩,然后与耳机推进系统装配位vmlinux.bin。
一级推进系统--setup.bin
在进行内核初始化时,需要一些信息,如显示信息、内存信息等。
曾经,这些信息由工作在实模式下的setupbin通过BIOS获取,保存在内核中的变量boot_params中。在我拿出信息收集后,setup.bin将CPU切换到保护模式,并调整到内核的保护部分执行。内核将setup.bin收集的信息复制到vmlinu的数据段中。
随着新的BIOS标准的出现,尤其是ELFi的出现,为了支持这些新标准,开发者们制定了32位启动协议。Bootloader实现手机这些信息的功能。内核启动时不再需要首先运行实模式部分,而是直接跳转到内核的保护模式部分。
事实上,出来手机信息功能外,setup.bin的另一个重要功能就是复制在内核和Bootloader之间传递信息。32位启动协议约定在setup.bin中分配一块空间用来承载这些信息,在构建映像时,内核构建胸痛需要将这些信息写道setup.bin的这块空间中。
二级推进系统--内核非压缩部分
内核的保护部分是经过压缩的,因此运行前需要解压缩。内核在压缩的映像外包围了一部分非压缩代码,Bootloader在加载映像后跳转至外围的这段非压缩部分。这些没有经过解压缩的指令可以直接送给CPU执行,由这段CPU可执行的指令复制解压内核的压缩部分你。
出来解压以外,非压缩部分还复制内核重定位。内核可以配置为可重定位的,所谓可重定位即内核可以被Bootloader加载到内存任何位置。但是在链接内核时,链接器需要假定一个加载地址,然后以这个假定地址位参考,位各个符号分配运行时地址。显然,如果加载地址和链接时假定的地址不同,那么需要对符号的地址进行重新修订,这就是内核重定位。
内核非压缩部分工作在保护模式下,其占用的内存在完成使命后将会被释放。
有效载荷--vmlinux
在编译时,kbuild分别构建内核各个子目录中的目标文件,然后将它们链接位vmlinux。为了缩小内核体积,kbuild删除了vmlinux中的一些不必要的信息,并将其命名为vmlinux.bin,最后将vmlinux.bin压缩位vmlinux.bin.gz。
那么为什么内核要进行压缩呢?
- 最初,因为在某系体系架构上,特别时i386,系统启动时运行于实模式状态,可以寻址空间只能在1MB以下,如果内核尺寸过大,将无法正常加载,因此对内核进行了压缩。在内核载进完毕后,CPU切换到保护模式,可以寻找更大的地址空间,于是就可以将压缩过的内核展开了。
- 另外一个原因时,2.4及更早版本的内核,需要可以容乃在一张软盘上,所以内核也要进行压缩。
以上都时历史原因,这些限制已经不存在,但内核的压缩还是保留了下来。
映像的格式
内核映像采用裸二进制格式进行组织。但是从Linux 2.6.26版开始,内核的有效载荷部分,采用了ELF格式。
内核映像的构建过程
kbuild简介
内核的构建系统位kbuild,可以看作利用GNU Make组织的一套复制的构建系统。虽然kbuild很复杂,但也是有章可循的,下面两点时理解kbuild的关键:
- Makefile的包含。将共同使用的变量或规则定义在一个文件中在是要使用Makefile中包含整个文件
- 顶层Mkaefile包含平台相关的Makefile
- Makefile.build包含各个子目录下的Makefile
- 使用指定Makefile的方式进行递归
构建过程概述
组成内核映像的各个部分的构建顺序如下:
- 构建有效载荷,并将其压缩位vmlinux.bin.gz;
- 构建二级推进系统,并将二级推进系统装配到有效载荷上,组成vmlinux.bin;
- 构建一级推进系统,即构建setup.bin;
- 将setup.bin和vmlinux.bin组合位gzImage。
vmlinux的构建过程
所有的体系结构都需要构建vmlinux,所有vmlinux的构建规则在顶层的Makefile中。
vmlinux.bin的构建过程
- kbuild使用objcopy,将顶层Makefile构建好的内核映像vmlinux复制到arch/x86/boot/commpressed目录下,删除了".comment"段、符号表和重定位表,并命名位vmlinux.bin
- kbuild 压缩内核映像vmlinux.bin
- kbuild建筑内核自带的程序mkpiggy构建一个汇编程序piggy.S,该汇编程序就是vmlinux.bin.gz加上一些解压内核时需要的信息。
- kbuild将head_32.o、misc.o以及包含压缩映像的piggy.o等目标文件链接位vmlinux.bin,保存到arch/x86/boot目录下。
setup.bin的构建过程
bzimage的组合过程
程序build就是将set.bin和vmlinux.bin简单地链接位bzImage。
内核映像构建过程总结

内核映像的构建过程大体上可以概况为“三次编译链接,一次组合”
- 第一次编译链接,kbuild分别编译各个子目录下的目标文件,然后将它们链接为ELF格式的vmlinux,并存放在顶层目录中。这一步当相遇构建有效载荷。
- 第二次编译链接。
- build使用工具objcopy,将顶层目录的vmlinux复制到arch/x86/boot/compressed目录下,去掉其中的符号信息、重定位信息,删除段“.comment”,并命名为vmlinux.bin。然后,kbuild将其压缩为vmlinux.bin.gz,封装到piggy.S中,并调用汇编器将其编译为piggy.o,这一步时对有效载荷进行了压缩。
- kbuild调用编译器编译arch/x86/boot/compressed目录下的head_32.c、misc.c等作为内核的非压缩部分,这一步相当于构建二级推进系统。
- 然后,kbuild条用链接器将压缩的有效载荷和二级推进系统链接为vmlinux。
- kbuild调用objcopy将arch/x86/boot/compressed目录下的vmlinux复制到arch/x86/boot目录下,同时将其转换为裸二进制格式,并命名为vlinux.bin
- 第三次编译链接,kbuild将arch/x86/boot下的a20.o、bioscall.o等目标文件链接为setup.elf,使用objcopy将其转换为裸二进制格式,并命名为setup.bin。这一步相当于构建一级推进系统。
- 一次组合,最后,kbuild调用内核自带的程序build,将vmlinux.bin和setup.bin合并为gzImage。
内核映像的布局:

配置内核
我们使用ake memuconfig配置内核,需要安装依赖:
$ sudo apt-get install libncurses5-dev
交叉编译内核设置
默认情况下,内核构建系统默认内核时本地编译。如果为交叉编译,我们需要设置两个变量:
- ARCH,指明目标体系架构,即编译好的内核运行在什么平台上,如x86、arm或mips等。
- CROSS_COMPILE指定使用的交叉编译器的前缀。对于我们的交叉工具链来说,时i686-none-linux-gnu-
可以使用多种方法定义这两个变量,比如通过在环境变量中定义:
make ARCH=i386 CROSS_COMPILE=i686-none-linux-gnu-
也可以直接更改顶层Makefile
基本内核配置
内核为很多平台附带了默认配置文件,保存在arch/
$ make i386_defconfig
kbuild 还提供了创建一个最小配置的方法:
$ make allnoconfig
接下来我们已这个基本配置为基础。
配置处理器
- 选择处理器,较早的处理器开发的程序都可以在较新的处理器上运行,但是反过来则不一定了。配置步骤
- 执行make menuconfig
- 选择菜单项Processor type and features
- 选择菜单项 Processor family
- 选择对应的处理器。
- 配置内核支持SMP,如果机器有多颗CPU,为了更好地发挥多颗CPU的性能,需要配置内核支持SMP。配置步骤
- 执行 make menuconfig;
- 选择Processor type and features;
- 选中 Symmetric multi-processing support。
配置内核支持模块
如果使用模块机制,只需要单独编译驱动,然后动态加载,即可进行调试;而不必冲洗编译整个内核,甚至重启系统。配置步骤:
- 执行 make menuconfig
- 旋转菜单项Enable loadable module supper,允许内核动态加载模块;
- 旋转 module unloading,允许内核动态卸载模块。
配置硬盘控制器驱动
以SATA硬盘为例,需要从三个方面考虑
- 硬盘控制器的接口,SATA控制器使用的时PCI接口,挂在PCI总线上,所以首先需要配置内核支持PCI总线
- 与SCSi层之间的关系,在内核中SATA设备被实现位一个SCSI设置,因此虽然目标机器上可能没有SCSI设备,但是要支持SATA控制器,内核雅瑶配置支持SCSI。
- 底层设备驱动,内核将SATA驱动从逻辑上划分位两层
- SATA Translation,这一层复制SCSI和SATA协议之间的翻译,在SATA驱动中被封装位libata模块
- Low level Device Driver, 在SATA translation 层下,是直接面对设备的底层驱动。
配置
- 配置PCI总线,因此SATA控制器使用的是PCI接口,所以我们首先来配置内核支持PCI总线。步骤:
- 执行 make menuconfig
- 选择Bus options
- 选择PCI support。
- 配置SCSI
- 执行 make menuconfig
- 选择 Device Drivers
- 选择 SCSI device support
- 选中 SCSI device support 和 SCSI disk support,注意它们都编译仅内核,而不是编译为模块
- 配置SATA控制器驱动,步骤:
- 执行 make menuconfig
- 选择 Device Drivers
- 选择Serial ATA and Parallel ATA drivers
- 选择ATCI SATA support和Intel ESB,ICH,PIIX3,PIIX4 PATA/SATA support。
构建内核:
$ cd /vita/build/linux-3..7.4
$ make bzImage
$ mkdir /vita/sysroot/boot
$ cp arch/x86/boot/bzImage /vita/sysroot/boot
将bzImage复制到测试机的/vita/boot 目录下。
在测试机/boot/grub/grub.cfg 中添加
menuentry 'vita'{
set root='(hd0,2)'
linux /boot/bzImage root=/dev/sda2 ro
}
启动测试机。
配置文件系统
- 执行 make menuconfig
- 选择菜单项 File system
- 旋转配置The Extend 4(ext4)filesystem,并将其直接编译进内核。
格式化ext4文件系统时,工具mke2fs.ext4会默认支持“hugu_file”特性,而该特性要求大于2TB的块设备,因此,我们配置内核支持这一特性
- 执行 make menuconfig
- 选择菜单项 Enable the block layer
- 选中配置 Support for large(2TB+) block devices and files。
配置内核支持elf文件格式
已经配置了内核支持ext4文件系统。但是内核从文件系统加载文件,仅支持文件系统还是不够的,内核还要支持具体的文件构建,当前Linux系统使用的标准二进制格式时ELF,因此需要配置Linux支持ELF格式。步骤
- 执行 make menuconfig
- 选择 Executable file formats/Emulations
- 旋转配置 Kernel support for ELF binaries
构建基本根文件系统
根文件系统的基本目录结构
Linux根文件系统的目录结构时依照Filesystem Hierarchy Standard Group指定的 Filessystem Hierarchy Standard 标准。

安装C库
几乎所有的程序都依赖C库,它时整个系统的基础,因此我们首先安装C库到根文件系统。这里的文件系统只是个临时系统,我们只需按照$SYSROOT/lib 目录下的动态库及相应的动态链接/加载器需要的符号链接。
$ cd /vita
$ mkdir rootfs
$ mkdir rootfs/lib
$ cp -d sysroot/lib/* rootfs/lib
$ cp -d cross-tool/i686-none-linux-gnu/lib/lib*.so.*[0-9] rootfs/lib/
安装shell
安装C库后,构建基本的应哟管程序的基础已经具备了,接下来我们需要为内核准则用户空间的程序了。在Linux中,专门复制启动的软件包,都提供了一个二进制程序作为第一个进程执行的用户空间的程序,但是为简单期间,我们使用bash。安装bash的命令如下:
$ cd /vita/build
$ tar xvf ../source/bash-4.2.tar.gz
$ cd bash-4.2
$ ./configure --prefix=/usr \
--bindir=/bin --without-bash-malloc
$ make
$ make install DESTDIR=$SYSROOT
将bash安装到rootfs中:
$ cd /vita
$ mkdir rootfs/bin
$ cp sysroot/bin/bash rootfs/bin/
$ cd rootfs/bin
$ ln -s bash sh
安装根文件系统到目标系统
为了减少文件系统可以使用strip删除ELF中运行时不需要的符号:
$ cd /vita/rootfs
$ i686-none-linux-gnu-strip lib/* bin/*
压缩:
$ cd /vita/rootfs
$ tar zcvf ../rootfs.tgz *
至此,一个基本的内核已经构建完成。它可以运行在X86体系结构上。