7. Linux内核的编译

这里的Linux内核指的是Kernel,或者称内核也是指的Kernel

7.1. 为什么要自己编译Kernel

虽然我们提供的内核已经支持绝大多数功能了,但是对于一些需要定制功能的客户来说不一定有他们需要的功能配置,所以本章将讲解如何配置与编译内核

7.2. 获取kernel

7.2.1. 下载源代码

有三个方法获取Kernel源码,一个是Kernel官方内核源码,一个是RockChip官方的kernel源码, 一个是经过我们修改适配我们板卡的kernel源码,我们这里使用我们提供的源码为例, 对官方源码感兴趣的小伙伴也可以下载来学习配置。

我们的kernel是根据Rockchip官方提供的kernel定制的,Rockchip的kernel是根据kernel官方LTS(长期支持)版本进行芯片适配的。 我们作为嵌入式开发者,一般只需要使用芯片厂商适配好的kernel进行开发即可, 对于从kernel官方下载新版本来适配这是芯片厂商做的事情。

目前LubanCat-RK356x-Linux-SDK使用kernel 4.19版本。LubanCat-RK3588-Linux-SDK使用kernel 5.10版本,LubanCat_Linux_Generic_SDK使用kernel 5.10版本。

由于内核主线对特定芯片的开发时慢于芯片厂商的,并且芯片厂商很少提交到内核主线, 就导致芯片的很多功能并没有被实现,所以目前我们只能使用Rockchip较旧的kernel版本。

Kernel官方内核源码:https://www.kernel.org/

Rockchip内核源码: https://github.com/rockchip-linux/kernel/

LubanCat内核源码:https://github.com/LubanCat/kernel

使用我们提供的

1
2
3
4
5
6
7
8
# LubanCat-RK系列通用
git clone -b lbc-develop-5.10 https://github.com/LubanCat/kernel

# LubanCat-RK356x(旧版)
git clone -b stable-4.19-rk356x https://github.com/LubanCat/kernel

# LubanCat-RK3588(旧版)
git clone -b stable-5.10-rk3588 https://github.com/LubanCat/kernel

7.3. kernel工程结构分析

学习一个软件,尤其是开源软件,首先应该从分析软件的工程结构开始。 一个好的软件有良好的工程结构,对于读者学习和理解软件的架构以及工作流程都有很好的帮助。

内核源码目录如下:

arch         COPYING        drivers   init     kernel       make_deb.sh  README    sound
block        CREDITS        firmware  ipc      lib          Makefile     samples   tools
build_image  crypto         fs        Kbuild   LICENSES     mm           scripts   usr
certs        Documentation  include   Kconfig  MAINTAINERS  net          security  virt

我们可以看到Linux内核源码目录下是有非常多的目录,且目录下也有非常多的文件, 下面我们简单分析一下这些目录的主要作用。

arch :主要包含和硬件体系结构相关的代码,如arm、x86、MIPS,PPC,每种CPU平台占一个相应的目录。arch中的目录下存放的是各个平台以及各个平台的芯片对Linux内核进程调度、内存管理、 中断等的支持,以及每个具体的SoC和电路板的板级支持代码。

block :在Linux中block表示块设备(以块(多个字节组成的整体,类似于扇区)为单位来整体访问),譬如说SD卡、Nand、硬盘等都是块设备,block目录下放的是一些Linux存储体系中关于块设备管理的代码。

crypto :这个目录下存放的是常用加密和散列算法(如md5、AES、 SHA等),还有一些压缩和CRC校验算法。

Documentation:内核各部分的文档描述。

drivers :设备驱动程序,里面列出了linux内核支持的所有硬件设备的驱动源代码,每个不同的驱动占用一个子目录,如char、block、 net、 mtd、 i2c等。

fs :fs就是file system,里面包含Linux所支持的各种文件系统,如EXT、FAT、 NTFS、 JFFS2等。

include :目录包括编译核心所需要的大部分头文件,例如与平台无关的头文件在 include/linux 子目录下,与cpu架构相关的头文件在include目录下对应的子目录中。

init :内核初始化代码,这个目录下的代码就是linux内核启动时初始化内核的代码。

ipc :ipc就是 inter process commuication ,进程间通信,该目录下都是linux进程间通信的代码。

kernel :kernel就是Linux内核,是Linux中最核心的部分,包括进程调度、定时器等,而和平台相关的一部分代码放在arch/*/kernel目录下。

lib :lib是库的意思,lib目录下存放的都是一些公用的有用的库函数,注意这里的库函数和C语言的库函数不一样的,因为在内核编程中是不能用C语言标准库函数的,所以需要使用lib中的库函数,除此之外与处理器结构相关的库函数代码被放在 arch/*/lib/ 目录下。

mm : 目录包含了所有独立于 cpu 体系结构的内存管理代码,如页式存储管理内存的分配和释放等,而与具体硬件体系结构相关的内存管理代码位于 arch/*/mm 目录下,例如 arch/arm/mm/fault.c

net : 网络协议栈相关代码,net目录下实现各种常见的网络协议。

scripts :这个目录下全部是脚本文件,这些脚本文件不是linux内核工作时使用的,而是用了配置编译linux内核的。

security :内核安全模型相关的代码,例如最有名的SELINUX。

sound : ALSA、 OSS音频设备的驱动核心代码和常用设备驱动。

usr : 实现用于打包和压缩的cpio等。

此处仅列出一些常见的目录。

7.4. 内核配置选项

提示

本节需要输入命令的操作,需在kernel目录下进行

7.4.1. 配置内核选项,使用LubanCat板卡配置

在不同版本的内核和不同系列的鲁班猫板卡使用的配置文件都保存在内核目录的arch/arm64/configs中, 具体使用的配置文件如下:

  • Kernel 5.10.198:根据主芯片型号区分lubancat_linux_xxxx_defconfig,xxxx为芯片型号

  • Kernel 4.19.232:LubanCat-RK356x系列板卡都使用lubancat2_defconfig

  • Kernel 5.10.160:LubanCat-RK3588系列板卡都使用lubancat_linux_rk3588_defconfig

以下以LubanCat-2板卡为例进行说明

Linux内核的配置系统由三个部分组成,分别是:

  • Makefile:分布在 Linux内核源代码根目录及各层目录中,定义 Linux 内核的编译规则;

  • 配置文件:给用户提供配置选择的功能,如Kconfig文件定义了配置项, 在编译时,使用 arch/arm64/configs/lubancat2_defconfig 文件对配置项进行赋值;

  • 配置工具:包括配置命令解释器(对配置脚本中使用的配置命令进行解释) 和配置用户界面(linux提供基于字符界面、基于Ncurses 图形界面以及 基于 Xwindows 图形界面的用户配置界面,各自对应于make config、make menuconfig 和 make xconfig)。

注意

如果自定义配置文件, 编译时要在板卡对应的device/rockchip/rk356x/BoardConfig.mk文件中修改RK_KERNEL_DEFCONFIG的定义。

我们可以通过 make menuconfig KCONFIG_CONFIG=arch/arm64/configs/lubancat2_defconfig ARCH=arm64 命令来查看我们的配置,make menuconfig是一个基于文本选择的配置界面, 推荐在字符终端下使用, 而这个配置文件为 lubancat2_defconfig 此时就可以看到在lubancat2_defconfig的配置选择, 可以通过键盘的”上”、”下”、”左”、”右”、”回车”、”空格”、”?”、”ESC”等按键进行选择配置,具体见:

1
2
3
4
5
#使用图形界面配置需要额外安装 libncurses-dev
sudo apt install libncurses-dev

#执行命令
make menuconfig KCONFIG_CONFIG=arch/arm64/configs/lubancat2_defconfig ARCH=arm64
building_kernel002

比如我们选择配置板卡的ov5648摄像头驱动: ov5648 , 如果找不到这个配置选项在哪里,可以使用 make menuconfig 中的搜索功能, 在英文输入法状态下按下”/”则可以进行搜索,输入”ov5648”找到改配置选项的位置, 当输入错误时,可使用 Ctrl+退格键 删除输入。 具体见:

building_kernel003
building_kernel004

从图中可以很明显看出 ov5648 配置选项位于 -> Device Drivers 选项下的 -> Multimedia support (MEDIA_SUPPORT [=y]) 下的 -> I2C Encoders, decoders, sensors and other helper chips 的选项中, 其实也可以按下 "1" 直接可以定位到对应的选项, 然后选中以下内容即可,具体见图:

building_kernel005

可使用 y、n、m 键更改ov5648驱动的配置时, 其中y表示编译进内核中,m表示编译成模块,n表示不编译。 也可使用空格选择ov5648驱动的配置选项。

想要配置其他的驱动也是如此。

修改完成后,选择右下角Save进行保存, 注意不要保存到原路径,而是保存到.config ,然后使用以下命令来保存defconfig文件并覆盖原来的配置文件。

building_kernel006
1
2
3
4
5
# 保存defconfig文件
make savedefconfig ARCH=arm64

# 覆盖原来的配置文件
cp defconfig arch/arm64/configs/lubancat2_defconfig

这样保存的原因是配置文件默认是精简版本的,编译使用时会和默认的配置文件进行比较从而得到完整的配置,如果直接保存则是完整版本的,会比精简版多几千行配置,不利于观察、修改。

7.5. kernel编译

提示

本节需要输入命令的操作,需在SDK根目录下进行

在LubanCat-SDK中,自动编译脚本基本上都存放在build.sh中,这是SDK的主要功能入口

对于kernel的编译,有两种方式。一种是rk标准的boot分区,称为rkboot分区(已弃用),另一种是野火修改的extboot分区。

rkboot分区是以二进制形式打包的,各个部分存放的地址经过了严格定义,读取文件时通过二进制文件头去判断文件的种类,目前LubanCat板卡已停止支持。

而我们修改的extboot分区,是以ext4格式存储文件的,在系统内是可读可修改的,大大增加了便利性。 我们以此为基础,为extboot分区系统增加了在线更新内核版本,修改设备树插件,切换主设备树的功能, 最终实现了一个镜像通用所有使用同一型号处理器的LubanCat板卡的功能。

7.5.1. extboot分区编译(旧版)

注意

以下内容中对function build_extboot()的描述仅适用于旧版LubanCat-SDK

由于在打包extboot分区时,会打包内核deb包,所以在构建extboot分区镜像前,我们先要进行下一步骤,构建内核deb包。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
function build_extboot(){
    check_config RK_KERNEL_DTS RK_KERNEL_DEFCONFIG || return 0

    echo "============Start building kernel============"
    echo "TARGET_ARCH          =$RK_ARCH"
    echo "TARGET_KERNEL_CONFIG =$RK_KERNEL_DEFCONFIG"
    echo "TARGET_KERNEL_DTS    =$RK_KERNEL_DTS"
    echo "TARGET_KERNEL_CONFIG_FRAGMENT =$RK_KERNEL_DEFCONFIG_FRAGMENT"
    echo "=========================================="
    pwd

    build_check_cross_compile

    cd kernel
    make ARCH=$RK_ARCH $RK_KERNEL_DEFCONFIG $RK_KERNEL_DEFCONFIG_FRAGMENT
    make ARCH=$RK_ARCH $RK_KERNEL_DTS.img -j$RK_JOBS
    make ARCH=$RK_ARCH dtbs -j$RK_JOBS

    echo -e "\e[36m Generate extLinuxBoot image start\e[0m"

    KERNEL_VERSION=$(cat $TOP_DIR/kernel/include/config/kernel.release)

    EXTBOOT_IMG=${TOP_DIR}/kernel/extboot.img
    EXTBOOT_DIR=${TOP_DIR}/kernel/extboot
    EXTBOOT_DTB=${EXTBOOT_DIR}/dtb/

    rm -rf $EXTBOOT_DIR
    mkdir -p $EXTBOOT_DTB/overlay
    mkdir -p $EXTBOOT_DIR/uEnv
    mkdir -p $EXTBOOT_DIR/kerneldeb

    cp ${TOP_DIR}/$RK_KERNEL_IMG $EXTBOOT_DIR/Image-$KERNEL_VERSION

    if [ "$RK_ARCH" == "arm64" ];then
        cp ${TOP_DIR}/kernel/arch/${RK_ARCH}/boot/dts/rockchip/*.dtb $EXTBOOT_DTB
        cp ${TOP_DIR}/kernel/arch/${RK_ARCH}/boot/dts/rockchip/overlay/*.dtbo $EXTBOOT_DTB/overlay
        cp ${TOP_DIR}/kernel/arch/${RK_ARCH}/boot/dts/rockchip/uEnv/uEnv*.txt $EXTBOOT_DIR/uEnv
    else
        cp ${TOP_DIR}/kernel/arch/${RK_ARCH}/boot/dts/*.dtb $EXTBOOT_DTB
        cp ${TOP_DIR}/kernel/arch/${RK_ARCH}/boot/dts/overlay/*.dtbo $EXTBOOT_DTB/overlay
    fi
    cp -f $EXTBOOT_DTB/${RK_KERNEL_DTS}.dtb $EXTBOOT_DIR/rk-kernel.dtb

    if [[ -e ${TOP_DIR}/kernel/ramdisk.img ]]; then
        cp ${TOP_DIR}/kernel/ramdisk.img $EXTBOOT_DIR/initrd-$KERNEL_VERSION
        echo -e "\tinitrd /initrd-$KERNEL_VERSION" >> $EXTBOOT_DIR/extlinux/extlinux.conf
    fi

    cp ${TOP_DIR}/kernel/.config $EXTBOOT_DIR/config-$KERNEL_VERSION
    cp ${TOP_DIR}/kernel/System.map $EXTBOOT_DIR/System.map-$KERNEL_VERSION
    cp ${TOP_DIR}/kernel/logo.bmp $EXTBOOT_DIR/

    cp ${TOP_DIR}/linux-headers-"$KERNEL_VERSION"_"$KERNEL_VERSION"-*.deb $EXTBOOT_DIR/kerneldeb
    cp ${TOP_DIR}/linux-image-"$KERNEL_VERSION"_"$KERNEL_VERSION"-*.deb $EXTBOOT_DIR/kerneldeb

    rm -rf $EXTBOOT_IMG && truncate -s 128M $EXTBOOT_IMG
    fakeroot mkfs.ext4 -F -L "boot" -d $EXTBOOT_DIR $EXTBOOT_IMG

    finish_build
}
  1. check_config检查配置文件是否存在

  2. build_check_cross_compile设置交叉编译参数

  3. 应用定义的内核配置文件

  4. 编译内核镜像

  5. 编译设备树

  6. 创建EXTBOOT_DIR临时文件夹,存放用于生成boot分区内的文件

  7. 复制内核镜像、设备树文件、设备树插件、uEnv环境变量文件

  8. 判断是否添加ramdisk镜像

  9. 复制启动logo、内核配置文件、System.map、内核deb包

  10. 以ext4格式将EXTBOOT_DIR文件夹中的文件打包到extboot.img

在后续的步骤中,会将extboot.img软链接到rockdev/boot.img

7.6. 构建内核deb包

我们在内核运行make bindeb-pkg 会生成至多5个Debian软件包,在LubanCat-SDK中我们可以直接使用以下命令构建。

1
./build.sh kerneldeb

其构建脚本如下

注意

以下内容中对function build_kerneldeb()的描述仅适用于旧版LubanCat-SDK

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
./build.sh kerneldeb

function build_kerneldeb(){
    check_config RK_KERNEL_DTS RK_KERNEL_DEFCONFIG || return 0

    build_check_cross_compile

    echo "============Start building kernel deb============"
    echo "TARGET_ARCH          =$RK_ARCH"
    echo "TARGET_KERNEL_CONFIG =$RK_KERNEL_DEFCONFIG"
    echo "TARGET_KERNEL_DTS    =$RK_KERNEL_DTS"
    echo "TARGET_KERNEL_CONFIG_FRAGMENT =$RK_KERNEL_DEFCONFIG_FRAGMENT"
    echo "=========================================="
    pwd
    cd kernel
    make ARCH=$RK_ARCH $RK_KERNEL_DEFCONFIG $RK_KERNEL_DEFCONFIG_FRAGMENT
    make ARCH=$RK_ARCH bindeb-pkg RK_KERNEL_DTS=$RK_KERNEL_DTS -j$RK_JOBS
    finish_build
}
  1. check_config检查配置文件是否存在

  2. build_check_cross_compile设置交叉编译参数

  3. 应用定义的内核配置文件

  4. 编译内核deb包

生成的软件包如下:

  • linux-image-version:一般包括内核镜像和内核模块,我们还添加了设备树及设备树插件。

  • linux-headers-version:包括创建外部模块所需的头文件

  • linux-firmware-image-version:包括某些驱动程序所需的固件(这里不生成)

  • linux-image-version-dbg:在linux-image-version基础上多了debug符号

  • linux-libc-dev:包括GNU glibc之类与用户程序库相关的标头

提示

version为内核版本

其中我们最主要用到的就是linux-image和linux-headers,而linux-firmware我们则可以通过网络进行安装。

通过安装linux-headers,我们可以直接在板卡上安装gcc编译器进行本地编译,具体的使用方法详见应用部署章节

通过安装linux-image则可以更新内核镜像、设备树和设备树插件、内核模块。

7.6.1. 内核deb包的安装

我们将生成的deb包通过USB存储设备或网络复制到运行Ubuntu或Debian镜像的板卡中,使用以下命令安装软件包

错误

注意在更新时不要断电,否则可能会导致系统损坏无法启动。

1
2
3
4
5
6
7
# 内核为4.19版本
sudo dpkg -i linux-headers-4.19.xxx_4.19.xxx-xxx_arm64.deb
sudo dpkg -i linux-image-4.19.xxx_4.19.xxx-xxx_arm64.deb

# 内核为5.10版本
sudo dpkg -i linux-headers-5.10.xxx_5.10.xxx-xxx_arm64.deb
sudo dpkg -i linux-image-5.10.xxx_5.10.xxx-xxx_arm64.deb

注解

在安装本地deb包时,要在deb包所在的目录下运行上面命令,xxx为deb包实际对应的数字。

等待deb包安装完成后重启,即可完成内核更新。

除了直接使用deb包进行本地更新以外,还可以使用野火软件源进行在线更新

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
sudo apt update

# 更新全部要更新的软件包
sudo apt upgrade

# 只更新linux-image和linux-headers
# 内核为4.19.232
sudo apt install linux-image-4.19.232
sudo apt install linux-headers-4.19.232

# 内核为5.10.160
sudo apt install linux-image-5.10.160
sudo apt install linux-headers-5.10.160

升级完成后重启板卡,即可完成内核更新。