第三章:Linux内核编译——从源码到烧录的完整闭环

说实话,内核编译这件事,很多刚入行的朋友觉得挺神秘的。好像敲几个命令,等个十几分钟,就能得到一个能跑的内核。其实没那么玄乎,但也没那么简单。我做了这么多年嵌入式,内核编译翻车的次数,两只手都数不过来。今天我就把这块掰开了讲,从源码获取到最终烧录,每一步该注意什么,坑在哪里,一次性说清楚。

3.1 内核源码获取:别下错了版本

获取内核源码,第一件事就是确认你用的芯片型号。瑞芯微的芯片,比如RK3568、RK3588,它们对应的内核分支是不一样的。我见过有人拿着RK3399的内核去编译RK3568,结果折腾了两天没跑起来——嗯,这种低级错误其实挺常见的。

官方推荐的方式是从瑞芯微的GitHub仓库拉取:

git clone https://github.com/rockchip-linux/kernel.git -b rk356x-4.19

这里有个细节:-b参数指定分支。不同芯片对应不同分支,比如:

芯片型号 推荐内核分支
RK3566/RK3568 rk356x-4.19
RK3588 rk3588-5.10
RK3399 rk3399-4.19
注意:不要直接从kernel.org拉主线内核。主线内核没有瑞芯微的板级补丁,你编译出来也跑不起来。我曾经有个同事图省事,直接拉了主线5.10,结果设备树不匹配,折腾了一周才换回来。

拉完代码后,建议先看看arch/arm64/boot/dts/rockchip/目录下有没有你板子的设备树文件。如果没有,说明分支选错了,或者你需要自己写设备树——那是后面章节的内容了。

3.2 内核配置与裁剪:少即是多

内核配置,说白了就是决定「哪些功能要编译进内核,哪些不要」。默认配置通常很臃肿,把能开的都开了。但嵌入式设备资源有限,你想想看,一个只有256MB Flash的设备,你塞进去一个500MB的内核,那不是搞笑吗?

瑞芯微官方通常会提供一个defconfig文件,比如:

make ARCH=arm64 rockchip_linux_defconfig

这个文件是官方针对某类板子的基础配置。但注意,它不一定完全适合你的板子。我个人的习惯是:先用defconfig编译一次,确保能跑起来,然后再逐步裁剪。

3.2.1 裁剪的核心思路

裁剪不是乱删。你得知道你的板子上有哪些硬件。比如:

  • 没有WiFi模块,就把无线网络驱动全关掉
  • 没有蓝牙,就把蓝牙协议栈去掉
  • 只用ext4文件系统,就把btrfs、xfs、f2fs全关了

进入配置菜单:

make ARCH=arm64 menuconfig

这里我分享一个技巧:用/键可以搜索配置项。比如你想找CONFIG_USB_NET_DRIVERS,直接按/输入关键词,回车就能定位。不用一个个菜单翻,效率高很多。

小提示:裁剪时,不确定的驱动先保留。我曾经为了省空间,把某个不认识的驱动关了,结果板子的以太网口不工作了。后来发现那个驱动是GMAC的PHY控制部分。所以,不确定就留着,等验证完再删。

3.2.2 模块与静态编译的选择

内核有两种编译方式:

  • 静态编译(built-in):直接编进内核镜像,启动时自动加载
  • 模块(module):编译成.ko文件,需要时手动或自动加载

我的建议是:启动必需的驱动用静态编译,非必需的用模块。比如存储控制器、根文件系统驱动必须静态编译,否则内核都起不来。而像USB摄像头驱动、音频编解码器这些,用模块就行,省空间。

3.3 编译:耐心是美德

配置好了,接下来就是编译。命令很简单:

make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- -j4

这里-j4表示用4个线程并行编译。如果你的电脑是8核,可以改成-j8。但注意,不是线程越多越好。我试过-j32,结果内存爆了,编译直接崩溃。一般来说,线程数不超过CPU物理核心数的1.5倍比较稳妥。

编译完成后,你会得到两个关键文件:

  • arch/arm64/boot/Image —— 内核镜像
  • arch/arm64/boot/dts/rockchip/你的板子.dtb —— 设备树文件
重要:如果你改了设备树,一定要重新编译设备树。单独编译设备树的命令是:make ARCH=arm64 dtbs。我见过有人改了dts文件,然后只编译了内核,没编译dtb,结果设备树还是旧的,折腾半天找不到原因。

3.4 烧录:最后一步,也是最容易翻车的一步

编译好了,怎么烧到板子上?瑞芯微的烧录方式主要有两种:

3.4.1 使用RKDevTool烧录

这是官方提供的Windows工具。操作步骤:

  1. 板子进入Loader模式(按住RECOVERY键,上电)
  2. 打开RKDevTool,选择对应的分区
  3. boot.img(包含内核和设备树)烧到Boot分区
  4. 点击「执行」开始烧录

这里要注意:boot.img不是直接拿Imagedtb拼起来的。你需要用mkbootimg工具把它们打包:

mkbootimg --kernel arch/arm64/boot/Image \
          --dtb arch/arm64/boot/dts/rockchip/你的板子.dtb \
          --base 0x00200000 \
          -o boot.img
警告:--base参数的值取决于你的芯片。RK3568是0x00200000,RK3588是0x00200000,但RK3399是0x00280000。搞错了内核起不来。我建议你从官方SDK里找一个能用的boot.img,用dumpimage工具查看它的参数,然后照着来。

3.4.2 使用fastboot烧录

如果你的板子已经跑起来了,可以用fastboot:

fastboot flash boot boot.img
fastboot reboot

这种方式更快,适合开发阶段频繁调试。但前提是你的bootloader支持fastboot。瑞芯微的U-Boot默认是支持的,但有些第三方板子可能没开。

3.5 验证:跑起来才算数

烧录完成后,上电看串口日志。如果看到:

Starting kernel ...
[    0.000000] Booting Linux on physical CPU 0x0000000000 [0x412fd050]
...

说明内核已经开始启动了。接着看有没有挂载根文件系统,有没有启动init进程。如果卡在某一步,比如:

[    1.234567] VFS: Cannot open root device "mmcblk0p2"

说明内核找不到根文件系统。这时候检查两件事:一是设备树里mmc节点的配置对不对,二是内核有没有编译对应的存储驱动。

调试技巧:如果内核起不来,可以在U-Boot里用bootargs加上init=/bin/sh,这样内核会直接启动一个shell,方便排查问题。我调试新板子时经常用这招。

3.6 避坑指南

最后,我总结几个我踩过的坑,你遇到了可以少走弯路:

  • 内核版本与工具链不匹配:4.19内核用gcc 7.x,5.10内核用gcc 9.x。版本差太多编译会报错。我试过用gcc 11编译4.19内核,结果一堆语法错误。
  • 设备树编译警告不能忽略:编译dtb时如果有警告,比如Warning (unit_address_vs_reg),最好修掉。有些警告会导致设备树解析失败,外设不工作。
  • 烧录后没反应,先检查串口线:别笑,我至少有三次以为内核挂了,结果发现是串口线松了。先确认硬件连接,再怀疑软件。

好了,这一章的内容就到这里。内核编译说难不难,说简单也不简单。关键是每一步都要理解它在做什么,而不是机械地敲命令。下一章我们会讲设备树,那是嵌入式Linux开发的另一个核心。到时候见。