构建内核

了解内核映像的构建过程无论是对学些内核,还是运行内核开发都有诸多帮助。所以在构建内核之前,首先讨论了内核的构建过程。

对于编译内核而言,一条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。

那么为什么内核要进行压缩呢?

  1. 最初,因为在某系体系架构上,特别时i386,系统启动时运行于实模式状态,可以寻址空间只能在1MB以下,如果内核尺寸过大,将无法正常加载,因此对内核进行了压缩。在内核载进完毕后,CPU切换到保护模式,可以寻找更大的地址空间,于是就可以将压缩过的内核展开了。
  2. 另外一个原因时,2.4及更早版本的内核,需要可以容乃在一张软盘上,所以内核也要进行压缩。

以上都时历史原因,这些限制已经不存在,但内核的压缩还是保留了下来。

映像的格式

内核映像采用裸二进制格式进行组织。但是从Linux 2.6.26版开始,内核的有效载荷部分,采用了ELF格式。

内核映像的构建过程

kbuild简介

内核的构建系统位kbuild,可以看作利用GNU Make组织的一套复制的构建系统。虽然kbuild很复杂,但也是有章可循的,下面两点时理解kbuild的关键:

  1. Makefile的包含。将共同使用的变量或规则定义在一个文件中在是要使用Makefile中包含整个文件
    1. 顶层Mkaefile包含平台相关的Makefile
    2. Makefile.build包含各个子目录下的Makefile
  2. 使用指定Makefile的方式进行递归

构建过程概述

组成内核映像的各个部分的构建顺序如下:

  1. 构建有效载荷,并将其压缩位vmlinux.bin.gz;
  2. 构建二级推进系统,并将二级推进系统装配到有效载荷上,组成vmlinux.bin;
  3. 构建一级推进系统,即构建setup.bin;
  4. 将setup.bin和vmlinux.bin组合位gzImage。

vmlinux的构建过程

所有的体系结构都需要构建vmlinux,所有vmlinux的构建规则在顶层的Makefile中。

vmlinux.bin的构建过程

  1. kbuild使用objcopy,将顶层Makefile构建好的内核映像vmlinux复制到arch/x86/boot/commpressed目录下,删除了".comment"段、符号表和重定位表,并命名位vmlinux.bin
  2. kbuild 压缩内核映像vmlinux.bin
  3. kbuild建筑内核自带的程序mkpiggy构建一个汇编程序piggy.S,该汇编程序就是vmlinux.bin.gz加上一些解压内核时需要的信息。
  4. kbuild将head_32.o、misc.o以及包含压缩映像的piggy.o等目标文件链接位vmlinux.bin,保存到arch/x86/boot目录下。

setup.bin的构建过程

bzimage的组合过程

程序build就是将set.bin和vmlinux.bin简单地链接位bzImage。

内核映像构建过程总结

内核映像的构建过程大体上可以概况为“三次编译链接,一次组合”

  1. 第一次编译链接,kbuild分别编译各个子目录下的目标文件,然后将它们链接为ELF格式的vmlinux,并存放在顶层目录中。这一步当相遇构建有效载荷。
  2. 第二次编译链接。
    1. build使用工具objcopy,将顶层目录的vmlinux复制到arch/x86/boot/compressed目录下,去掉其中的符号信息、重定位信息,删除段“.comment”,并命名为vmlinux.bin。然后,kbuild将其压缩为vmlinux.bin.gz,封装到piggy.S中,并调用汇编器将其编译为piggy.o,这一步时对有效载荷进行了压缩。
    2. kbuild调用编译器编译arch/x86/boot/compressed目录下的head_32.c、misc.c等作为内核的非压缩部分,这一步相当于构建二级推进系统。
    3. 然后,kbuild条用链接器将压缩的有效载荷和二级推进系统链接为vmlinux。
    4. kbuild调用objcopy将arch/x86/boot/compressed目录下的vmlinux复制到arch/x86/boot目录下,同时将其转换为裸二进制格式,并命名为vlinux.bin
  3. 第三次编译链接,kbuild将arch/x86/boot下的a20.o、bioscall.o等目标文件链接为setup.elf,使用objcopy将其转换为裸二进制格式,并命名为setup.bin。这一步相当于构建一级推进系统。
  4. 一次组合,最后,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//configs目录下,如果打算使用x86的32位默认配置,执行下面命令即可:

$ make i386_defconfig

kbuild 还提供了创建一个最小配置的方法:

$ make allnoconfig

接下来我们已这个基本配置为基础。

配置处理器

  1. 选择处理器,较早的处理器开发的程序都可以在较新的处理器上运行,但是反过来则不一定了。配置步骤
    1. 执行make menuconfig
    2. 选择菜单项Processor type and features
    3. 选择菜单项 Processor family
    4. 选择对应的处理器。
  2. 配置内核支持SMP,如果机器有多颗CPU,为了更好地发挥多颗CPU的性能,需要配置内核支持SMP。配置步骤
    1. 执行 make menuconfig;
    2. 选择Processor type and features;
    3. 选中 Symmetric multi-processing support。

      配置内核支持模块

如果使用模块机制,只需要单独编译驱动,然后动态加载,即可进行调试;而不必冲洗编译整个内核,甚至重启系统。配置步骤:

  1. 执行 make menuconfig
  2. 旋转菜单项Enable loadable module supper,允许内核动态加载模块;
  3. 旋转 module unloading,允许内核动态卸载模块。

配置硬盘控制器驱动

以SATA硬盘为例,需要从三个方面考虑

  1. 硬盘控制器的接口,SATA控制器使用的时PCI接口,挂在PCI总线上,所以首先需要配置内核支持PCI总线
  2. 与SCSi层之间的关系,在内核中SATA设备被实现位一个SCSI设置,因此虽然目标机器上可能没有SCSI设备,但是要支持SATA控制器,内核雅瑶配置支持SCSI。
  3. 底层设备驱动,内核将SATA驱动从逻辑上划分位两层
    • SATA Translation,这一层复制SCSI和SATA协议之间的翻译,在SATA驱动中被封装位libata模块
    • Low level Device Driver, 在SATA translation 层下,是直接面对设备的底层驱动。

配置

  1. 配置PCI总线,因此SATA控制器使用的是PCI接口,所以我们首先来配置内核支持PCI总线。步骤:
    1. 执行 make menuconfig
    2. 选择Bus options
    3. 选择PCI support。
  2. 配置SCSI
    1. 执行 make menuconfig
    2. 选择 Device Drivers
    3. 选择 SCSI device support
    4. 选中 SCSI device support 和 SCSI disk support,注意它们都编译仅内核,而不是编译为模块
  3. 配置SATA控制器驱动,步骤:
    1. 执行 make menuconfig
    2. 选择 Device Drivers
    3. 选择Serial ATA and Parallel ATA drivers
    4. 选择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
}

启动测试机。

配置文件系统

  1. 执行 make menuconfig
  2. 选择菜单项 File system
  3. 旋转配置The Extend 4(ext4)filesystem,并将其直接编译进内核。

格式化ext4文件系统时,工具mke2fs.ext4会默认支持“hugu_file”特性,而该特性要求大于2TB的块设备,因此,我们配置内核支持这一特性

  1. 执行 make menuconfig
  2. 选择菜单项 Enable the block layer
  3. 选中配置 Support for large(2TB+) block devices and files。

配置内核支持elf文件格式

已经配置了内核支持ext4文件系统。但是内核从文件系统加载文件,仅支持文件系统还是不够的,内核还要支持具体的文件构建,当前Linux系统使用的标准二进制格式时ELF,因此需要配置Linux支持ELF格式。步骤

  1. 执行 make menuconfig
  2. 选择 Executable file formats/Emulations
  3. 旋转配置 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体系结构上。

results matching ""

    No results matching ""