第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,那不得疯掉?
基本流程:
- 加载默认配置:
make ARCH=arm64 rockchip_linux_defconfig - 进入图形界面:
make ARCH=arm64 menuconfig - 搜索配置项:按
/键,输入关键词,比如I2C_ROCKCHIP - 保存退出:按
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 文件。
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 知识体系总览
下面这张图,把本章的核心逻辑串起来了。你照着这个思路走,基本不会迷路。
2.6 实战:完整走一遍流程
咱们拿RK3568的I2C驱动举个例子,完整走一遍从配置到加载的流程:
- 确认源码位置:
drivers/i2c/busses/i2c-rockchip.c - menuconfig打开配置:搜索
I2C_ROCKCHIP,设为M(模块) - 单独编译:
make M=drivers/i2c/busses/ modules - 拷贝到目标板:
scp drivers/i2c/busses/i2c-rockchip.ko root@192.168.1.100:/root/ - 加载测试:
insmod i2c-rockchip.ko - 查看日志:
dmesg | tail,确认有没有报错 - 卸载:
rmmod i2c-rockchip
我的经验:这一步看起来简单,但很多人会卡在 Unknown symbol 上。原因通常是内核版本不一致,或者模块依赖的符号没导出。解决办法:用 modinfo i2c-rockchip.ko 查看模块依赖,确保所有依赖模块都已加载。
嗯,这套流程你多跑几遍,形成肌肉记忆。以后写驱动,从改代码到验证,5分钟内搞定不是问题。