重新来梳理一下内核编译。
简单粗暴的选择 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 |