第2章 内核编译与模块化调试:从源码树到动态加载

说实话,我刚入行那会儿,最怕的就是编译内核。动不动就半小时,还经常编不过。后来摸爬滚打几年,才真正搞明白这套东西该怎么玩。今天咱们就聊聊RK3568平台上的内核编译和模块化调试——说白了,就是让你能又快又准地搞定驱动开发环境。

2.1 内核源码树结构:别被目录吓到

RK3568用的Linux内核,源码树大概长这样。我建议你先记住几个关键目录:

目录 作用 我常用的场景
arch/arm64 ARM64架构相关代码 改dts、调内存映射
drivers/ 所有外设驱动 写新驱动、修bug
include/ 头文件 查结构体定义
Documentation/ 内核文档 查API用法
scripts/ 编译脚本 调试编译问题

嗯,这里要注意:arch/arm64/boot/dts/rockchip/ 下面放着RK3568的设备树文件。我每次拿到新板子,第一件事就是去翻这个目录,看看dts里配了哪些外设。

我的习惯:在源码根目录建一个 my_drivers/ 文件夹,把自己写的驱动放进去。这样不会污染内核源码,也方便git管理。

2.2 menuconfig配置:图形化配置其实很简单

配置内核,我推荐用 make menuconfig。为什么?因为它直观。你想想看,几千个配置项,靠手敲 make ARCH=arm64 defconfig 再改 .config,那不得疯掉?

基本流程:

  1. 加载默认配置:make ARCH=arm64 rockchip_linux_defconfig
  2. 进入图形界面:make ARCH=arm64 menuconfig
  3. 搜索配置项:按 / 键,输入关键词,比如 I2C_ROCKCHIP
  4. 保存退出:按 Exit 直到提示保存,选 Yes

避坑指南:我曾经在menuconfig里改完配置直接退出,结果忘了保存。折腾了半小时才发现 .config 根本没变。后来我养成习惯:每次改完都看一眼 cat .config | grep CONFIG_MY_DRIVER 确认一下。

2.3 单独编译驱动模块:别再全量编译了

全量编译内核?那是给CI服务器干的事。咱们开发阶段,单独编译驱动模块才是正道。速度快,反馈快,心情也好。

假设你写了一个RK3568的GPIO按键驱动,放在 drivers/input/keyboard/ 下:

# 先编译内核头文件和模块依赖
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- modules_prepare

# 单独编译你的驱动模块
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- M=drivers/input/keyboard/ modules

# 编译完成后,会在该目录生成 .ko 文件
ls drivers/input/keyboard/*.ko

我个人习惯写个脚本,把这几步封装起来。每次改完代码,跑一下脚本,10秒内就能拿到新的 .ko 文件。

注意:如果改了头文件或者内核API,必须重新 modules_prepare。我吃过这个亏——改了个结构体,没重新prepare,结果insmod的时候报 Unknown symbol,查了半天才发现是版本不匹配。

2.4 动态加载与卸载:insmod vs modprobe

模块编好了,怎么装上去?两个命令:insmod 和 modprobe。很多人觉得它们差不多,其实差别大了去了。

命令 特点 适用场景
insmod 直接加载,不处理依赖 调试单个模块,快速验证
modprobe 自动处理依赖,加载所有需要的模块 正式部署,模块有依赖关系时

举个例子:你的GPIO按键驱动依赖 gpio-rockchip.ko。用 insmod 的话,你得先手动加载gpio驱动,再加载按键驱动。用 modprobe 就省事了,它会自动帮你搞定。

# 调试阶段:insmod 快速验证
insmod gpio-rockchip.ko
insmod rk3568-gpio-keys.ko

# 查看加载是否成功
lsmod | grep gpio

# 卸载
rmmod rk3568-gpio-keys
rmmod gpio-rockchip

# 正式部署:modprobe 一步到位
modprobe rk3568-gpio-keys

# modprobe 卸载
modprobe -r rk3568-gpio-keys

调试小技巧:用 insmod 加载模块时,加上 dyndbg 参数可以动态打开调试打印:insmod my_driver.ko dyndbg=+p。这样就不用重新编译了。

2.5 知识体系总览

下面这张图,把本章的核心逻辑串起来了。你照着这个思路走,基本不会迷路。

内核编译与模块化调试知识体系 内核源码树结构 menuconfig配置 单独编译模块 动态加载与卸载 核心流程说明 1. 先搞懂源码树结构,知道文件在哪 2. 用menuconfig配置内核,打开需要的驱动选项 3. 单独编译驱动模块,快速迭代 4. 用insmod/modprobe动态加载验证 5. 调试通过后,再考虑编进内核或正式部署

2.6 实战:完整走一遍流程

咱们拿RK3568的I2C驱动举个例子,完整走一遍从配置到加载的流程:

  1. 确认源码位置:drivers/i2c/busses/i2c-rockchip.c
  2. menuconfig打开配置:搜索 I2C_ROCKCHIP,设为 M(模块)
  3. 单独编译:make M=drivers/i2c/busses/ modules
  4. 拷贝到目标板:scp drivers/i2c/busses/i2c-rockchip.ko root@192.168.1.100:/root/
  5. 加载测试:insmod i2c-rockchip.ko
  6. 查看日志:dmesg | tail,确认有没有报错
  7. 卸载:rmmod i2c-rockchip

我的经验:这一步看起来简单,但很多人会卡在 Unknown symbol 上。原因通常是内核版本不一致,或者模块依赖的符号没导出。解决办法:用 modinfo i2c-rockchip.ko 查看模块依赖,确保所有依赖模块都已加载。

嗯,这套流程你多跑几遍,形成肌肉记忆。以后写驱动,从改代码到验证,5分钟内搞定不是问题。