1. 嵌入式Linux内核概述:内核版本演进、源码结构与配置系统

各位同学,咱们今天正式开讲。嵌入式Linux内核,说白了就是一套专门跑在资源受限硬件上的操作系统内核。我做了十几年嵌入式,见过太多人一上来就make menuconfig瞎点一通,最后编译出来的内核要么跑不起来,要么大得离谱。嗯,这章咱们先把地基打牢。

1.1 内核版本演进:从Linus的玩具到工业级系统

Linux内核从1991年诞生到现在,版本号一直在变。我个人习惯把版本演进分成三个阶段:

阶段 版本范围 特点
早期 0.01 ~ 2.6 单内核、不支持抢占、驱动少
成熟期 2.6 ~ 4.x 可抢占内核、设备模型、DTB引入
现代 5.x ~ 6.x 实时补丁、Rust支持、BPF大爆发

为什么我要提这个?因为你在选内核版本时,不是越新越好。我在一个ARM Cortex-A7的项目上,曾经固执地用了当时最新的5.10,结果发现某个网卡驱动在4.19上稳定得很,5.10上反而有bug。后来我学乖了——嵌入式项目选内核,优先选LTS版本,比如4.19、5.10、6.1这些。

我的建议: 新项目起步,直接选最新的LTS版本。如果是从老平台迁移,先看原厂BSP支持的内核版本,再考虑升级。

1.2 内核源码结构:别被目录吓到

第一次解压Linux内核源码,看到几十个目录,很多人头皮发麻。其实核心目录就那么几个,我列个清单:

  • arch/ —— 体系结构相关代码。ARM、x86、RISC-V都在这里。嵌入式开发最常逛的地方。
  • drivers/ —— 驱动代码。占了源码一半以上的体积。你想想看,一个内核支持几千种设备,驱动能不多吗?
  • kernel/ —— 核心调度、进程管理、信号处理。这部分一般不动。
  • mm/ —— 内存管理。页分配、slab、vmalloc都在这里。
  • fs/ —— 文件系统。ext4、fat、nfs、procfs……
  • net/ —— 网络协议栈。TCP/IP、socket、netfilter。
  • include/ —— 头文件。你写驱动时#include的东西大多来自这里。
  • scripts/ —— 编译脚本、配置工具。Kconfig解析器就在这里。

我记得刚入行时,导师跟我说了一句话,至今受用:"你不需要看懂所有代码,但要知道去哪找代码。" 比如你想改某个ARM开发板的启动流程,直接进arch/arm/mach-xxx/;想调网卡驱动,去drivers/net/ethernet/。

避坑指南: 千万不要直接修改内核核心目录(kernel/、mm/、fs/)的代码来做板级适配。正确的做法是写驱动、改设备树、或者用内核提供的hook接口。我曾经为了快速调通一个外设,直接改了kernel/sched/core.c,结果下次内核升级时,改的代码全冲突了,教训深刻。

1.3 内核配置系统:Kconfig与Makefile的配合

内核配置系统,说白了就是一套"开关管理"机制。Kconfig定义"有什么开关",Makefile决定"开关怎么用"。我画了一张图,帮你理清关系:

内核配置系统工作流程 Kconfig 文件 定义配置选项 配置工具 menuconfig / xconfig .config 文件 保存配置结果 Makefile 文件 条件编译规则 编译过程 make / make modules zImage / uImage 最终产物 关键流程说明: 1. Kconfig 定义选项 → 配置工具读取并展示给开发者 2. 开发者选择后 → 生成 .config 文件(所有开关的最终状态) 3. Makefile 读取 .config → 根据 CONFIG_XXX 决定编译哪些文件 4. 最终生成内核镜像(zImage/uImage)和模块(.ko)

1.3.1 Kconfig语法速览

Kconfig文件里,最常用的就是config、menuconfig、choice、menu这几个关键字。我写个例子:

menu "My Custom Drivers"

config MY_DRIVER
    tristate "My custom driver support"
    depends on ARM
    help
      This is a custom driver for my embedded project.

config MY_DRIVER_DEBUG
    bool "Enable debug output"
    depends on MY_DRIVER
    default n

endmenu

这里tristate表示三种状态:编译进内核(y)、编译为模块(m)、不编译(n)。bool只有y/n两种。depends on就是依赖关系——你选了MY_DRIVER,才能看到MY_DRIVER_DEBUG。

注意: 依赖关系写不好,会导致配置界面里选项"凭空消失"。我遇到过最离谱的一次,某个驱动依赖了ARCH=XXX,结果我忘了选架构,在menuconfig里找了半天没找到那个选项。后来查Kconfig才发现,depends on条件不满足时,选项根本不会显示。

1.3.2 Makefile中的条件编译

Kconfig定义完开关,Makefile里怎么用?很简单:

obj-$(CONFIG_MY_DRIVER)     += my_driver.o
obj-$(CONFIG_MY_DRIVER_DEBUG) += my_driver_debug.o

如果CONFIG_MY_DRIVER=y,那么obj-y就会把my_driver.o编译进内核。如果是m,就编译成模块。如果是n,这行等于没写。

嗯,这里有个小技巧:调试驱动时,先用=m编译成模块,这样出了问题不用重新烧写整个内核,insmod/rmmod就能快速迭代。等稳定了再改成=y编进内核。

1.4 本章小结

这一章我们聊了三件事:内核版本怎么选、源码目录怎么看、配置系统怎么玩。说白了,就是让你拿到一个内核源码包后,知道从哪下手。下一章咱们会动手编译第一个内核,到时候这些概念都会用上。

课后动手: 下载一个4.19或5.10的LTS内核,解压后看看arch/arm/目录下有哪些子目录。再执行make ARCH=arm help,看看支持哪些开发板。这是你第一次和内核源码"握手"。