嵌入式Linux内核移植基础:内核源码获取与版本选择、Kconfig与Makefile机制、内核配置(menuconfig)与编译

好,咱们正式开始啃内核移植这块硬骨头。说实话,很多刚入行的朋友一听到「移植」两个字就觉得头大,觉得那是内核大神才干的活。其实不然,你只要把几个关键环节搞明白,这事儿就没那么玄乎。

我个人习惯把内核移植拆成三块:源码怎么拿、配置怎么玩、编译怎么过。今天咱们就把这三块挨个捋一遍。

内核源码获取与版本选择

先说说源码。你可能会问:「不就是去 kernel.org 下载吗?」嗯,理论上没错,但实际项目里没那么简单。

我的建议:别直接拿最新版内核往板子上怼。做产品选内核,讲究的是「稳」字当头。

我一般会参考这几个维度来选版本:

考量因素 说明 我的经验
芯片厂商BSP 厂商提供的板级支持包通常绑定某个内核版本 优先用厂商推荐的版本,省掉大量适配工作
LTS长期支持版 LTS版本维护周期长达6年 产品选型我必选LTS,安全补丁不断更
社区活跃度 版本太老,驱动和工具链支持会掉队 建议选发布不超过2年的LTS版本
Modbus协议栈兼容性 有些内核版本对串口和网络子系统的API有变动 我踩过坑,4.19到5.4的tty层改动就挺大

获取源码的方式,我常用的就三种:

  1. git clone 官方仓库:适合需要跟踪最新补丁的情况
  2. 下载tar包:适合固定版本、不打算频繁更新的项目
  3. 芯片厂商提供的BSP包:里面已经打好了板级补丁,开箱即用
注意:如果你用的是厂商BSP,一定要确认它基于哪个主线版本。我曾经接手过一个项目,厂商BSP基于3.10,但客户要求用5.10的新特性,结果光合并补丁就花了两周。

Kconfig与Makefile机制

内核的配置系统,说白了就是靠 Kconfig 和 Makefile 这两兄弟撑起来的。你想想看,几千个驱动、几百个功能模块,怎么管理?靠的就是这套机制。

Kconfig 负责定义「有什么选项可以选」。每个驱动目录下都有一个 Kconfig 文件,里面写着这个驱动叫什么、依赖什么、有什么依赖关系。

举个例子,咱们Modbus网关里要用到串口驱动,它的Kconfig片段大概长这样:

config SERIAL_8250
    tristate "8250/16550 and compatible serial support"
    depends on HAS_IOMEM
    select SERIAL_CORE
    help
      This is the driver for the standard serial ports.
      If you want to use a serial port, say Y here.

你看,tristate 表示可以编进内核(Y)、编成模块(M)或者不编(N)。depends on 是依赖关系,select 是自动选中其他选项。

Makefile 则负责「怎么编译」。它根据 Kconfig 里生成的配置宏,决定哪些 .c 文件参与编译。

obj-$(CONFIG_SERIAL_8250) += 8250.o
obj-$(CONFIG_SERIAL_8250_PCI) += 8250_pci.o

这里的 CONFIG_SERIAL_8250 就是配置宏。如果配置成 Y,就编译进内核;配置成 M,就编译成模块;没配置,就不编译。

核心逻辑:Kconfig 定义菜单 → menuconfig 生成 .config → Makefile 读取 .config 决定编译内容。这条链路你搞清楚了,内核配置就算入门了。

内核配置(menuconfig)与编译

好,到了实操环节。配置内核,我几乎只用 make menuconfig。为什么?因为它有图形界面,选项一目了然,还能搜索。

基本流程是这样的:

  1. 清理环境:make distclean,把之前的配置和编译产物全清掉
  2. 导入默认配置:make ARCH=arm defconfig,或者用厂商提供的配置文件
  3. 个性化配置:make ARCH=arm menuconfig,按需打开或关闭功能
  4. 保存配置:退出时自动保存为 .config 文件
  5. 编译内核:make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j4

这里我重点说说 menuconfig 里的搜索功能。按 / 键,输入关键词,就能找到所有相关选项。这个功能我几乎天天用,尤其是当你知道某个驱动名字但不知道它在菜单树哪个位置的时候。

小技巧:配置完记得检查 .config 文件,看看你需要的选项是不是真的被选中了。有时候依赖关系没满足,选项会被自动取消,menuconfig 里不会报错。

编译的时候,有几个参数你必须搞清楚:

参数 含义 说明
ARCH 目标架构 arm、arm64、x86、mips 等
CROSS_COMPILE 交叉编译工具链前缀 比如 arm-linux-gnueabihf-
-jN 并行编译线程数 一般设为 CPU 核心数的 1.5~2 倍
LOADADDR 内核加载地址 某些架构需要指定,比如 U-Boot 引导时

编译完成后,你会得到几个关键产物:

  • zImage / uImage:压缩后的内核镜像
  • vmlinux:未压缩的内核,带调试信息
  • arch/arm/boot/dts/xxx.dtb:设备树二进制文件
  • modules:编译出的内核模块,分布在各个目录下
我曾经踩过的坑:编译时忘了指定 ARCH 和 CROSS_COMPILE,结果用本机的 gcc 去编译 ARM 内核,编译倒是通过了,烧到板子上直接起不来。后来花了半天才排查出来。所以,这两个参数我建议写成环境变量,或者写个脚本,别每次都手敲。

下面这张图,是我自己总结的内核配置与编译的完整流程,你可以对照着看:

嵌入式Linux内核配置与编译流程 获取内核源码 make distclean 导入默认配置/厂商配置 make menuconfig make ARCH=arm ... -j4 输出产物:zImage / uImage / .dtb / modules 烧录到目标板 → 验证启动 关键参数 ARCH=arm CROSS_COMPILE=arm-linux- -j4 (并行编译) 常见问题 • 忘记指定ARCH • 依赖关系未满足 • 设备树与内核不匹配

最后说一句,配置和编译这事儿,熟能生巧。我第一次配内核的时候,对着菜单看了两个小时不知道选什么。后来做多了,闭着眼睛都能把Modbus网关需要的串口、网络、GPIO、SPI这些子系统配出来。

嗯,内核移植的基础就这些。你先把源码拿到手,把 menuconfig 玩熟,把编译流程跑通,后面咱们再聊设备树和驱动移植,就顺理成章了。