重新来梳理一下内核编译。
简单粗暴的选择 SOC / 开发板厂商所提供的内核是明智的。
编译内核和编译 U-boot 类似,都是以下 3 个步骤:
- 引用默认配置
- 在配置上做修改
- 编译得到镜像文件
内核文件分布
arch:与不同架构相关的文件Documentation:内核相关文档,虽然有些文档很老了,但是依然是第一参考资料。drivers:设备驱动fs:文件系统include:内核头文件init:内核启动相关代码kernel:内核核心代码,包括调度、锁、定时器、电源管理、调试代码。mm:内存管理net:网络协议scripts:很有用的脚本tools:对开发和检查内核很有用的工具集
KConfig
内核通过Kbuild来读取Kconfig文件进行配置。
Documentation/kbuild对此做了详细解释
ARCH:指定要编译的架构其值就是在
arch目录下的子目录名xxxx_defonfig:默认的配置
选项类型
KConfig 的配置具有以下几种类型:
bool:其值要么是y要么就是不会被定义CONFIG_DEVMEM=y
tristate:用于指定一个模块是被设置为模块(m),还是被编译进内核(y)int:10 进制的值hex: 16 进制的值string:字符串值
依赖与选择
depends代表当前选项依赖于其他选项:
1 | config MTD_CMDLINE_PARTS |
select则是用于使能其他选项:
1 | 当 ARM 选项被使能时,其他 select 选项指明的选项也会被使能 |
使用 menuconfig
使用 menuconfig 需要确保ncurses,flex,bison被安装:
1 | sudo apt install libncurses5-dev flex bison |
当开始编译内核后,会自动生成include/generated/autoconf.h文件,包含配置文件的宏定义,这是一个很好的用于检查的文件。
标记自己内核的版本
可以通过修改General setup -> Local version来为版本增加自己的后缀。
然后使用make来查看是否生效:
1 | make ARCH=arm kernelrelease |
这个输出可以与运行时的 uname 进行对比,以排查是否相同的内核。
编译内核
内核构建系统Kbuild从.config文件中获取配置,然后进行编译。
编译的输出类型
根据不同的 bootloader,其所需要一般内核压缩包是不一样的:
U-Boot:一般可以适配
uImage和zImage$ make -j 4 ARCH=arm CROSS_COMPILE=arm-cortex_a8-linux-gnueabihf- zImage
x86:
bzImage其他的 bootloader:
zImage
编译输出内核文件
编译完成后,在顶层目录会有vmlinux和System.map文件。
vmlinux是内核的 ELF 二进制文件,如果使能了CONFIG_DEBUG_INFO,那么此文件将会包含很多调试信息,可以使用kgdb这种工具对内核进行调试。
System.map文件包含内核完整的符号表。
Image:vmlinux去掉所有调试信息后的纯净的二进制文件zImage:将Image文件压缩后的文件uImage:zImage加上 64 字节的 U-boot 头
在编译过程中,如果有编译错误,那么可以加上V=1选项来查看编译命令:
1 | make -j 4 ARCH=arm CROSS_COMPILE=arm-cortex_a8-linux- |
编译设备树
设备树编译的搜寻路径也是arch/$ARCH/boot/dts/,所以编译设备树指定ARCH变量即可:
1 | make ARCH=arm dtbs |
编译模块
编译模块和编译内核是一样的,只是指定其编译类型是模块即可:
1 | make -j 4 ARCH=arm CROSS_COMPILE=arm-cortex_a8-linux-gnueabihf- modules |
默认情况下,.ko的模块文件会与源码目录在一起。
可以设置INSTALL_MOD_PATH来指定安装目录,模块会安装在目录的./lib/modules/<kernel_version>文件夹中。
1 | make -j4 ARCH=arm CROSS_COMPILE=arm-cortex_a8-linux-gnueabihf- \ |
清理编译输出
make根下面几种选择来进行清理:
clean:删除目标文件和其他中间文件mrproper:删除所有的中间文件,包含.config文件distclean:在mrproper的基础上,删除基本的备份文件、补丁文件等
编译 imx8mm 内核
为了简化编译,这里使用米尔科技的内核分支。
先安装基础包:
1 | sudo apt install -y libssl-dev libelf-dev |
首先需要将交叉编译工具链,加入当前 SHELL 的环境变量中:
1 | export PATH=/home/cec/x-tools/aarch64-unknown-linux-gnu/bin:${PATH} |
然后按照惯例,先清理一下中间文件:
1 | make distclean |
配置常使用的全局变量:
1 | export CROSS_COMPILE=aarch64-unknown-linux-gnu- |
接下来为内核指定要编译的构架,及其使用的默认配置:
1 | make myd_imx8mm_defconfig |
最后就是编译内核文件、模块、设备树:
1 | make -j8 dtbs Image modules |
Image文件编译后位于arch/arm64/boot/Image- 设备树位于
arch/arm64/boot/dts/myir/
编译 bbb 内核
按照以下步骤即可:
1 | 清除中间文件 |
启动内核
当没有文件系统时
当仅启动了内核,而没有文件系统时,就会出现内核panic以表示无法挂载根文件系统:
1 | [ 1.886379] Kernel panic - not syncing: VFS: Unable to mount |
为了将系统从内核态切换到用户态,内核需要挂载根文件系统,然后执行根文件系统中的初始化程序。
这个是开始于执行init/main.c中的rest_init()函数,它会创建一个 PID 为 1 的进程,然后执行kernel_init()。
接着尝试执行/init程序,如果执行失败则会尝试执行prepare_namespace()函数,这个函数会读取root=命令行参数,挂载对应的分区,root命令一般如下:
1 | root=/dev/<disk name><partition number> |
挂载成功后,将会依次尝试执行/sbin/init,/etc/init,/bin/init,/bin/sh,其中任何一个执行成功了,后面的便不会被执行了。
当然也可以设定
init=参数来指定执行哪个特定的程序。
内核的命令行参数
目标内核的命令行参数都是通过设备树中的bootargs属性来设定了,在Documentation/kernel-parameters.txt有参数的详细说明,下面是一些常用的参数:
debug:设置调试信息输出等级,小于该数值的调试信息将被输出init=:在挂载根文件系统后,所运行的init程序路径lpj=:设置loops_per_jiffy以降低开机测试所消耗的时间panic=:当内核出现 panics 时的行为小于 0:当出现 panic 则立即重启
等于 0(默认):当出现 panic 不重启
大于 0:当出现 panic 时,等待多少秒后重启
quiet:除了紧急信息,其他的调试信息都不输出rdinit=:与init=一致,不过这个是针对ramdisk的ro:以只读的方式挂载根文件系统root=:指定挂载根文件系统的设备rootdelay=:等待多少秒后才挂载根文件系统,用于等待设备初始化完成rootfstype=:指定根文件系统的类型,默认情况下都是自动检测的rootwait:一直等到设备初始化完毕以后,才挂载根文件系统rw:以读写的方式挂载根文件系统
imx8mm 内核启动
制作启动 SD 卡
从 Uboot 中的启动命令可知:它将在 mmc 的 1 分区中寻找Image文件和对应的设备树。
并且,前面我们将由imx-mkimage打包的 bootloader 拷贝到了 SD 卡的 33K 偏移处,也就是说需要保留这部分裸数据,前面制作好的flash.bin有 1M 多的大小,需要考虑好这个偏移。
由于 SD 卡是 512 字节的扇区,先用 fdisk 简单粗暴的将第一个分区的起始扇区设为 20480,这样就预留了有 10MB 的空间给 bootloader。
使用
sudo mkfs.vfat /dev/sdd1将分区格式化为 FAT32 格式将编译得到的
Image和myb-imx8mm-base.dtb拷贝到 SD 卡分区这里选择
myb-imx8mm-base.dtb,是因为前面打包 bootloader 也是这个设备树修改 U-boot 默认环境变量
fdt_file的值为myb-imx8mm-base.dtb,这样才能一一对应
bbb 启动
将编译好的zImage和am335x-boneblack.dtb拷贝到 SD 卡中,启动 u-boot 后。
首先,读取内核到内存:
1 | fatload mmc 0:1 0x80200000 zImage |
然后,读取设备树到内存:
1 | fatload mmc 0:1 0x80f00000 am335x-boneblack.dtb |
设置终端设备:
1 | setenv bootargs console=ttyO0,115200 |
使用 bootz 启动:
1 | bootz 0x80200000 - 0x80f00000 |