4、Linux内核移植:从源码到跑起来
好,咱们进入正题。内核移植这事儿,说白了就是让Linux内核认识你的硬件平台。我刚开始做自动驾驶平台移植时,觉得这步就是改改配置、编译一下,结果第一次启动直接黑屏——嗯,从那以后我再也不敢小看这一步了。
4.1 内核源码获取与配置
先说说源码。我个人习惯从kernel.org拉主线代码,但做自动驾驶平台时,我建议你用芯片厂商提供的BSP内核。为什么?因为厂商已经帮你适配好了大部分驱动,你只需要做平台相关的微调。
获取源码后,第一件事就是配置。这里我要强调:千万别直接make menuconfig从头配。我见过太多新手这么干,配了三天三夜,最后编译还报错。
# 加载厂商默认配置
make ARCH=arm64 xxx_defconfig
# 进入图形化配置界面
make ARCH=arm64 menuconfig
menuconfig里要注意几个关键项:
- System Type:选择你的SoC型号,比如NVIDIA Orin、Qualcomm SA8295
- Device Drivers:这里要勾选你平台的外设驱动,比如CAN控制器、以太网PHY、摄像头接口
- File systems:自动驾驶常用ext4、squashfs(只读根文件系统)
4.2 设备树编写与编译
设备树(Device Tree)是Linux内核用来描述硬件信息的机制。说白了,就是告诉内核:你的板子上有哪些外设、它们挂在哪条总线上、中断号是多少。
我刚开始写设备树时,最头疼的就是地址映射。你想想看,一个SoC内部有几十个外设,每个外设的寄存器基地址都得对得上芯片手册。
设备树的基本结构是这样的:
/dts-v1/;
/ {
model = "My Autonomous Driving Platform";
compatible = "vendor,platform-name";
chosen {
bootargs = "console=ttyS0,115200 root=/dev/mmcblk0p2 rw";
};
memory@80000000 {
device_type = "memory";
reg = <0x0 0x80000000 0x0 0x80000000>;
};
soc {
#address-cells = <2>;
#size-cells = <2>;
compatible = "simple-bus";
ranges;
uart0: serial@28000000 {
compatible = "vendor,uart";
reg = <0x0 0x28000000 0x0 0x1000>;
interrupts = <0 33 4>;
clock-frequency = <24000000>;
};
can0: can@31000000 {
compatible = "vendor,can";
reg = <0x0 0x31000000 0x0 0x1000>;
interrupts = <0 45 4>;
};
};
};
编译设备树用DTC工具:
# 编译设备树
dtc -I dts -O dtb -o myplatform.dtb myplatform.dts
# 反编译(调试用)
dtc -I dtb -O dts -o myplatform.dts myplatform.dtb
4.3 内核启动参数设置
Bootargs是内核启动时传递给内核的参数。它决定了内核怎么找到根文件系统、用什么控制台输出、内存怎么分配。
常见的bootargs参数:
| 参数 | 说明 | 示例 |
|---|---|---|
| console | 指定控制台设备 | console=ttyS0,115200 |
| root | 指定根文件系统位置 | root=/dev/mmcblk0p2 |
| rw | 以读写方式挂载根文件系统 | rw |
| init | 指定init进程路径 | init=/sbin/init |
| mem | 限制可用内存大小 | mem=4G |
在自动驾驶平台上,我通常会加这几个参数:
console=ttyS0,115200 root=/dev/mmcblk0p2 rw rootwait
cma=256M@0x10000000
isolcpus=2-3
rcupdate.rcu_expedited=1
解释一下:
- cma:预留连续内存,给摄像头DMA用
- isolcpus:隔离CPU核心,用于实时任务
- rcupdate:加速RCU处理,减少延迟
earlyprintk,这样内核在初始化早期就能输出日志。我曾经靠这个参数定位到一个内存初始化时序问题。
4.4 常见启动问题排查
内核启动不起来,这是家常便饭。我总结了几类常见问题:
4.4.1 内核恐慌(Kernel Panic)
最常见的是找不到根文件系统。你会看到类似这样的输出:
VFS: Cannot open root device "mmcblk0p2" or unknown-block(0,0)
Please append a correct "root=" boot option; here are the available partitions:
...
排查步骤:
- 检查bootargs里的root参数是否正确
- 确认设备树里MMC/SD控制器节点是否正确
- 检查内核是否编译了对应的文件系统驱动(比如ext4)
4.4.2 控制台无输出
启动后串口没任何输出。我遇到过这种情况,原因是:
- 串口引脚配置不对(检查设备树里的pinctrl)
- 波特率不匹配(bootargs里写115200,但终端设成了9600)
- 内核没编译串口驱动
4.4.3 设备驱动加载失败
比如CAN控制器没起来,或者以太网不通。排查方法:
# 查看内核日志
dmesg | grep -i can
# 查看设备树是否被正确解析
ls /proc/device-tree/
# 检查驱动是否加载
lsmod
4.4.4 内存相关问题
自动驾驶平台内存需求大,经常遇到内存分配失败。排查方法:
- 检查bootargs里的mem参数是否限制了内存
- 查看
/proc/meminfo确认可用内存 - 用
cat /proc/iomem查看内存映射
嗯,内核移植这块内容不少,但核心就三点:配置要对、设备树要准、bootargs要合理。我建议你从最简单的配置开始,先让内核能启动到shell,再逐步添加外设驱动。别想着一步到位,那只会让你debug到怀疑人生。
下一章我们会聊根文件系统的构建,到时候再细说怎么让系统真正跑起来。