内核源码获取与结构分析:从kernel.org下载源码、arch/目录详解、Kconfig与Makefile体系
做嵌入式Linux开发,第一步就是跟内核源码打交道。很多新手上来就git clone一个完整仓库,结果发现光下载就要半小时,编译又报一堆错——其实问题往往出在「没搞清楚内核源码到底长什么样」。
这一章,我带你把内核源码的骨架摸清楚。从哪下载、怎么选版本、arch目录里藏着什么玄机、Kconfig和Makefile又是怎么配合的。搞懂这些,后面配置内核、移植驱动才会顺手。
2.1 从kernel.org获取源码
官方下载地址就是 kernel.org。我个人习惯直接去 https://www.kernel.org/ 看首页那个大黄色按钮,上面写着「Latest Stable Kernel」。点进去就能看到所有主线版本和长期支持(LTS)版本。
选版本有个原则:嵌入式项目尽量用LTS版本。比如5.10.y、5.15.y、6.1.y这些,社区维护周期长,bug修复稳定。我在一个工业控制项目里用过一次主线最新版,结果某个驱动接口变了,折腾了两天才适配回来——从那以后,非LTS版本我只在实验板上玩。
| 方式 | 命令/操作 | 适用场景 |
|---|---|---|
| wget 压缩包 | wget https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.1.76.tar.xz | 快速获取特定版本,不保留git历史 |
| git clone | git clone --depth=1 git://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git | 需要跟踪补丁或做二次开发 |
| torrent | 从kernel.org下载.torrent文件 | 网络不稳定时,校验更可靠 |
下载完解压:
tar -xf linux-6.1.76.tar.xz
cd linux-6.1.76
嗯,这里要注意:解压后第一件事不是急着编译,而是先看看顶层目录结构。我见过有人直接在源码根目录下make,结果把编译中间文件跟源码混在一起,后面想清理都麻烦。建议单独建一个 build 目录做 O= 输出。
2.2 顶层目录结构速览
执行 ls -l,你会看到几十个目录和文件。别慌,核心的就那么几个:
- arch/ —— 架构相关代码,移植工作的主战场
- drivers/ —— 设备驱动,占了源码一半以上的体积
- kernel/ —— 核心调度、进程管理、irq等
- mm/ —— 内存管理
- fs/ —— 文件系统实现
- include/ —— 头文件,驱动开发经常要翻
- Documentation/ —— 内核文档,很多坑这里都有记载
- Kconfig 和 Makefile —— 配置和编译的入口文件
为什么说arch/是移植主战场?因为不同CPU架构的启动流程、内存布局、中断控制器、时钟管理全都不一样。你想想看,ARM的GIC和x86的APIC能一样吗?这部分代码必须按架构隔离。
2.3 arch/目录详解
进入 arch/ 目录,你会看到:
alpha/ arm/ arm64/ mips/ riscv/ x86/ ...
arc/ csky/ hexagon/ parisc/ sparc/ xtensa/
每个子目录对应一种CPU架构。做嵌入式最常用的是 arm/、arm64/ 和 riscv/。我重点讲arm,其他架构结构类似。
以 arch/arm/ 为例,内部结构:
| 子目录/文件 | 作用 |
|---|---|
| boot/ | 启动相关代码,包括压缩后的内核镜像生成逻辑 |
| configs/ | 各开发板的默认配置文件,比如 vexpress_defconfig |
| include/ | 架构相关的头文件,比如 asm/io.h、asm/memory.h |
| kernel/ | 架构核心代码,如信号处理、ptrace、时钟事件 |
| lib/ | 架构优化的库函数,比如memcpy、cache刷写 |
| mach-*/ | 旧版机器码对应的板级代码(新内核已逐步淘汰) |
| mm/ | 架构相关的内存管理,如页表创建、DMA映射 |
| Kconfig | 架构级配置选项 |
| Makefile | 架构级编译规则 |
arch/arm/boot/dts/(设备树文件)和 arch/arm/configs/(默认配置)。我在一个项目中接手过一块国产ARM芯片,厂商只给了patch,结果设备树文件放错了目录,内核死活起不来——后来发现是dtsi的include路径写错了。
2.4 Kconfig体系:配置选项的骨架
Kconfig是内核配置系统的核心。它定义了所有可配置选项的层次结构、依赖关系和类型。你运行 make menuconfig 时看到的界面,就是由Kconfig文件生成的。
Kconfig语法其实不复杂,我拆开给你看:
config MY_DRIVER
bool "Enable My Custom Driver"
depends on ARM
select GPIO
help
This option enables support for my custom hardware.
Say Y if you have this device.
每个字段的含义:
- config —— 定义配置项名称,最终会变成
CONFIG_MY_DRIVER - bool —— 类型,还有tristate(三态:Y/M/N)、int、string等
- depends on —— 依赖条件,不满足则选项不可见
- select —— 反向依赖,选中此项会自动选上GPIO
- help —— 帮助信息,menuconfig里按?能看到
Kconfig文件分布在各个目录下。顶层 Kconfig 通过 source 语句逐级包含子目录的Kconfig:
source "arch/arm/Kconfig"
source "drivers/Kconfig"
source "fs/Kconfig"
这种树状结构保证了配置选项的组织清晰。我曾经调试过一个USB网卡驱动,怎么都选不上,最后发现是 depends on USB 没满足——USB子系统根本没被选进内核。这就是依赖链断裂的典型问题。
make olddefconfig 或 make menuconfig 重新生成 .config。我曾经直接手动改了 .config 文件,结果漏了一个依赖选项,编译到一半报错,浪费了半小时排查。
2.5 Makefile体系:编译的指挥棒
内核的Makefile体系比Kconfig更庞大,但核心逻辑很简单:递归编译。顶层Makefile调用各子目录的Makefile,子目录再调用更下层的Makefile,最终把所有目标文件链接成内核镜像。
看一个典型的驱动Makefile:
obj-$(CONFIG_MY_DRIVER) += my_driver.o
my_driver-objs := core.o init.o io.o
这里 obj-$(CONFIG_MY_DRIVER) 的意思是:如果 CONFIG_MY_DRIVER 为y,就把 my_driver.o 编进内核;如果为m,就编成模块。后面的 my_driver-objs 指定了多个源文件合并成一个目标文件。
常用的Makefile变量:
| 变量 | 含义 |
|---|---|
| obj-y | 无条件编译进内核 |
| obj-m | 编译成可加载模块 |
| obj-n | 不编译 |
| lib-y | 编译成库文件 |
| ccflags-y | 传递给编译器的额外参数 |
| ldflags-y | 传递给链接器的额外参数 |
顶层Makefile还定义了编译目标:
make vmlinux # 编译未压缩的内核
make zImage # 编译压缩后的内核镜像(ARM常用)
make Image # 编译未压缩的内核镜像(arm64常用)
make modules # 编译所有选为模块的驱动
make dtbs # 编译设备树二进制文件
我个人习惯在开发阶段先用 make -j$(nproc) zImage 快速验证编译是否通过,最后再统一编模块和设备树。
2.6 Kconfig与Makefile的联动
这两者是怎么配合的?我给你画个流程图:
流程很简单:
- Kconfig定义选项(比如
CONFIG_MY_DRIVER) - 运行
make menuconfig生成.config文件 .config里记录了你选的值(CONFIG_MY_DRIVER=y)- 顶层Makefile通过
include .config把这些变量导入 - 各子目录Makefile用
obj-$(CONFIG_MY_DRIVER)决定是否编译
嗯,这里有个细节:.config 文件里除了你手动选的选项,还有很多自动推导出来的依赖选项。所以不要手动编辑 .config,一定要通过 make menuconfig 或 make olddefconfig 来修改。
grep CONFIG_MY_DRIVER .config —— 看是否被选中grep -r "CONFIG_MY_DRIVER" drivers/ —— 看哪些文件引用了这个配置make drivers/my_driver/ —— 单独编译某个驱动目录,快速验证
2.7 实战:从零开始走一遍流程
假设我们要为一块ARM开发板配置内核。完整流程是:
- 下载源码:选LTS版本,比如6.1.76
- 设置默认配置:
make ARCH=arm vexpress_defconfig - 微调配置:
make ARCH=arm menuconfig,添加你需要的驱动 - 编译:
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j4 zImage modules dtbs - 检查输出:
arch/arm/boot/zImage和arch/arm/boot/dts/*.dtb
我在第一次做这个流程时,忘了指定 CROSS_COMPILE,结果编译出来的内核跑在本机x86上,放到ARM板子上直接panic。从那以后,我每次编译前都会先确认交叉编译器路径对不对。
这一章的内容就到这里。源码结构搞清楚了,Kconfig和Makefile的联动逻辑也明白了,后面配置内核、写驱动、调设备树的时候,你就知道该去哪个目录找什么文件了。