4、Linux内核编译:内核源码获取、RK平台内核配置、设备树(DTS)基础、内核编译与模块管理

内核编译,说白了就是把Linux内核源码变成能在板子上跑起来的二进制文件。很多新手觉得这步就是敲几条命令,其实坑不少。我在RK平台上折腾过几十次内核编译,从RK3288到RK3588都碰过,今天把关键环节掰开揉碎讲清楚。

本章核心脉络:源码怎么拿 → 怎么配 → 设备树是什么 → 怎么编译 → 模块怎么管。一条线走下来,你就能自己编译出一个可启动的内核。

Linux内核编译全流程框架 源码获取 内核配置 设备树(DTS) 编译 模块管理 部署到目标板 💡 核心原则:先配后编,先板级后驱动,模块按需加载,避免编译整个内核。

4.1 内核源码获取

Rockchip官方维护的内核仓库在GitHub上,分两个主线:rk-kernel-4.4(老平台如RK3288/RK3399)和rk-kernel-5.10(新平台如RK3568/RK3588)。我个人习惯直接从官方仓库拉,而不是用百度网盘那些打包好的。

# 以RK3588的5.10内核为例
git clone https://github.com/rockchip-linux/kernel.git -b rk-kernel-5.10 --depth=1

小技巧:加 --depth=1 只拉最新一次提交,省流量省时间。完整历史有十几个G,没必要。

拉下来后,目录结构长这样:

目录/文件说明
arch/arm64/boot/dts/rockchip/RK平台设备树文件,每个板子一个.dts
drivers/驱动代码,RK的驱动散落在各个子目录
Makefile顶层编译入口
.config内核配置(编译前生成)

我曾经遇到过一个问题:从百度网盘下载的源码包,编译时总报一些奇怪的语法错误。后来发现是文件在传输过程中损坏了。从那以后,我坚持用git clone,至少能保证完整性校验。

4.2 RK平台内核配置

内核配置,说白了就是决定「这个内核要支持哪些功能」。RK平台一般提供默认配置文件,放在 arch/arm64/configs/ 下,命名规则是 rockchip_<板型>_defconfig。

# 加载RK3588 EVB板的默认配置
make ARCH=arm64 rockchip_linux_defconfig

# 然后可以用菜单界面微调
make ARCH=arm64 menuconfig

为什么需要手动调?因为默认配置是给官方EVB板用的。你换了个不同的DDR颗粒、改了网口芯片、或者加了某个传感器,就得自己打开对应的驱动选项。

注意:menuconfig 依赖 ncurses-dev,Ubuntu上先装 sudo apt install libncurses-dev。我第一次在纯净容器里编译,卡在这步半天。

配置完成后,.config 文件就生成了。我建议把它备份一份:

cp .config my_board_defconfig

这样下次重新拉源码,直接 cp my_board_defconfig .config 就行,不用再进menuconfig重新选一遍。

4.3 设备树(DTS)基础

设备树,英文叫Device Tree,缩写DTS。它是什么?说白了就是一份硬件描述清单,告诉内核:你的板子上有哪些外设、接在哪个GPIO上、用哪个时钟。

RK平台每个板子都有一个对应的 .dts 文件,比如 rk3588-evb.dts。它通过 #include 引用公共的 .dtsi 文件:

// rk3588-evb.dts 简化示例
/dts-v1/;

#include "rk3588.dtsi"

/ {
    model = "Rockchip RK3588 EVB";
    compatible = "rockchip,rk3588-evb", "rockchip,rk3588";

    chosen {
        stdout-path = &uart2;
    };

    &uart2 {
        status = "okay";
        pinctrl-names = "default";
        pinctrl-0 = <&uart2m0_xfer>;
    };

    &i2c0 {
        status = "okay";
        clock-frequency = <400000>;

        hym8563: rtc@51 {
            compatible = "haoyu,hym8563";
            reg = <0x51>;
            status = "okay";
        };
    };
};

这里有几个关键点:

  • model/compatible:板子标识,内核靠它匹配驱动
  • chosen:控制台输出到哪个串口
  • &uart2:引用 rk3588.dtsi 里定义的节点,然后使能并配置
  • i2c0下的rtc节点:挂载一个I2C RTC芯片,地址0x51

避坑指南:我曾经在RK3399项目上,因为DTS里忘了加 status = "okay",结果调试了整整两天才发现——默认是 disabled,外设根本不会初始化。嗯,这个坑踩过一次就忘不了了。

编译设备树的命令:

make ARCH=arm64 dtbs

生成的 .dtb 文件在 arch/arm64/boot/dts/rockchip/ 下,烧录时把它放到boot分区就行。

4.4 内核编译

配置和设备树都准备好了,接下来就是编译。RK平台一般是ARM64架构,所以交叉编译是标配:

# 设置交叉编译器路径(以RK3588 SDK为例)
export CROSS_COMPILE=aarch64-linux-gnu-
export ARCH=arm64

# 编译内核镜像
make Image -j$(nproc)

# 编译设备树
make dtbs -j$(nproc)

# 编译内核模块
make modules -j$(nproc)

编译产物:

文件路径说明
Imagearch/arm64/boot/Image内核镜像,烧录到boot分区
*.dtbarch/arm64/boot/dts/rockchip/*.dtb设备树二进制,与内核镜像配套
*.ko各个驱动目录下内核模块,按需安装到根文件系统

提速技巧:第一次编译建议全量,之后如果只改了DTS或某个驱动,可以用 make dtbs 或 make drivers/xxx/ 局部编译,省时间。我平时改DTS,基本10秒就编完了。

4.5 模块管理

内核模块,就是那些可以按需加载的驱动。比如Wi-Fi驱动、USB转串口驱动,没必要编进内核里,做成模块更灵活。

模块的安装:

# 安装到指定目录
make modules_install INSTALL_MOD_PATH=./modules_install

# 查看安装后的目录结构
tree modules_install/lib/modules/

在目标板上,用 modprobe 或 insmod 加载模块:

# 加载Wi-Fi驱动(自动处理依赖)
modprobe rtl8821ce

# 查看已加载模块
lsmod

# 卸载模块
modprobe -r rtl8821ce

重要:模块版本必须与内核版本严格一致。我曾经在RK3568上,把5.10内核编出来的模块放到4.4内核上,结果 insmod 直接报 Invalid module format。内核版本号对不上,驱动加载不了。

模块的自动加载,可以通过 /etc/modules 或 /etc/modules-load.d/ 配置。比如:

# /etc/modules-load.d/rtc.conf
rtc_hym8563

这样系统启动时就会自动加载RTC驱动。

个人经验:我习惯把常用模块编进内核(比如串口、I2C、MMC),把不常用的做成模块(比如Wi-Fi、蓝牙、USB网卡)。这样内核镜像小,启动快,模块按需加载,内存占用也低。