第四章:内核配置基础

好,咱们进入内核配置这个环节。说实话,很多做嵌入式的朋友,一开始都被内核配置搞晕过。菜单一层套一层,选项多到眼花。我当年第一次配内核,愣是花了三天才把系统跑起来。今天我就把这里面的门道给你捋清楚。

一、Linux内核源码管理

先说说内核源码怎么管。你从kernel.org或者芯片厂商那拿到源码,第一件事不是急着配,而是先看看目录结构。

内核源码顶层目录,我习惯先看这几个:

  • arch/:架构相关代码。ARM、x86、RISC-V都在这里。
  • drivers/:驱动代码。你写的驱动最终要放到这里。
  • fs/:文件系统。ext4、fat、yaffs2都在里面。
  • net/:网络协议栈。TCP/IP、Wi-Fi、蓝牙。
  • Documentation/:文档。嗯,虽然有时候不太全。

我个人习惯,拿到一个新内核版本,先跑一下 make mrproper 清理干净。为什么?因为有时候厂商给的源码里会残留一些编译中间文件,不清掉容易出怪问题。

# 彻底清理内核源码树
make mrproper

# 查看当前内核版本
make kernelversion

这里有个坑:make mrproper 会删掉你的配置文件。所以如果你已经配好了,记得先备份 .config。我曾经手一抖,配了三天的配置全没了……嗯,从那以后我养成了备份的好习惯。

二、内核Kconfig机制

Kconfig 是什么?说白了就是内核的菜单系统。每个驱动、每个功能模块,都有一个 Kconfig 文件来描述它。你运行 make menuconfig 看到的那些菜单,就是 Kconfig 渲染出来的。

举个例子,你写了一个 LED 驱动,想把它加到内核配置菜单里。你需要在驱动目录下创建一个 Kconfig 文件:

# drivers/leds/Kconfig
config LEDS_MY_DRIVER
    tristate "My LED Driver support"
    depends on LEDS_CLASS
    help
      This is a custom LED driver for my board.
      Say Y here to compile it into the kernel,
      or M to build it as a module.

这里 tristate 表示三种状态:Y(编进内核)、M(编成模块)、N(不编译)。depends on 是依赖关系,比如你的驱动依赖 LED 子系统,那就得写上。

我在项目中遇到过,有人写 Kconfig 忘了加 depends on,结果编译时一堆未定义符号。嗯,依赖关系一定要理清楚。

Kconfig 常用语法速查:

  • bool:二选一,Y/N
  • tristate:三选一,Y/M/N
  • depends on:依赖其他选项
  • select:自动选中其他选项
  • default y:默认开启

小技巧:make menuconfig 时,按 / 键可以搜索选项。比如你忘了某个驱动叫什么名字,直接搜关键词就行。这个功能我天天用。

三、内核defconfig配置

defconfig 是什么?就是默认配置。每个芯片厂商都会提供一个 defconfig 文件,比如 arch/arm/configs/imx_v7_defconfig。你用它作为起点,再根据你的板子做调整。

使用 defconfig 的流程很简单:

# 加载厂商默认配置
make imx_v7_defconfig

# 然后手动微调
make menuconfig

# 保存配置
make savedefconfig

这里有个关键点:make savedefconfig 会生成一个精简版的 defconfig,只包含非默认值的选项。这样你的配置文件就很小,方便版本管理。

我建议你这样做:

  1. 先用厂商的 defconfig 启动内核
  2. 确认能跑起来后,再逐步添加你需要的功能
  3. 每加一个功能,就保存一次 defconfig

为什么要这么麻烦?因为有时候你一口气加了一堆选项,结果内核起不来了,你根本不知道是哪个选项导致的。一步一步来,出了问题好排查。我曾经为了省事,一次加了十几个驱动,结果内核 panic,查了整整一天……

注意:不同内核版本的 defconfig 格式可能不一样。比如 4.x 和 5.x 的 defconfig 就有差异。升级内核时,不要直接拿旧版的 defconfig 用,最好重新生成。

四、内核模块配置

模块配置,说白了就是决定哪些驱动编进内核,哪些编成模块。这里我分享几个原则:

  • 必须用的驱动编进内核:比如串口、网卡、根文件系统所在的存储设备。这些如果编成模块,内核启动时加载不了,系统就起不来。
  • 可选功能编成模块:比如 USB 摄像头、Wi-Fi 驱动、音频编解码器。这些可以在系统启动后动态加载。
  • 不用的直接关掉:减少内核体积,加快编译速度。

模块编译出来后,是 .ko 文件。你可以用 insmod 加载,rmmod 卸载,lsmod 查看已加载的模块。

# 加载模块
insmod my_driver.ko

# 查看模块信息
modinfo my_driver.ko

# 自动加载依赖模块
modprobe my_driver

这里有个坑:insmod 不会自动解决依赖关系。比如你的驱动依赖 leds_class,你得先加载 leds_class.ko。而 modprobe 会自动处理依赖,所以我一般用 modprobe

模块的自动加载,靠的是 /etc/modules 或者 /etc/modules-load.d/ 目录下的配置文件。你只要把模块名写进去,系统启动时就会自动加载。

模块配置实战建议:

  • 开发阶段:把所有驱动都编成模块,方便调试
  • 发布阶段:把稳定驱动编进内核,减少模块加载时间
  • 模块签名:如果产品有安全要求,记得给模块签名

最后说一句,内核配置这东西,没有捷径。多配几次,多踩几个坑,自然就熟了。我刚开始做嵌入式那会,每次配内核都要折腾好几天。现在嘛,拿到一块新板子,半天就能把内核配好跑起来。你多练练,也能做到。

下一章,咱们聊聊 Buildroot 怎么管理内核配置,以及如何把内核配置集成到 Buildroot 的构建流程里。到时候我会分享一些自动化配置的技巧,让你少走弯路。