2. 交叉编译工具链构建
说实话,很多初学者拿到内核源码第一反应就是懵。几万个文件,几百个目录,从哪看起?我当年也一样,对着 linux-2.6 的 tarball 发呆了半天。后来做项目多了,慢慢摸清了门道——内核源码的结构其实很有规律,你只要抓住几个关键目录和两套核心机制,剩下的就是按图索骥。
2.1 内核源码目录:别被数量吓到
先看顶层目录。我习惯用 tree -L 1 扫一眼,大概十几个目录。真正核心的,其实就下面这几个:
| 目录 | 干什么的 | 我常去翻什么 |
|---|---|---|
arch/ |
体系结构相关代码 | arch/arm/、arch/arm64/,板级文件、启动代码都在这里 |
drivers/ |
设备驱动,最大的一块 | drivers/i2c/、drivers/spi/、drivers/net/,驱动开发的主战场 |
fs/ |
文件系统实现 | fs/ext4/、fs/proc/、fs/sysfs/,虚拟文件系统也在这 |
kernel/ |
核心调度、进程管理 | 一般不动,除非改调度策略 |
mm/ |
内存管理 | 调 OOM 或者改内存分配策略时会看 |
net/ |
网络协议栈 | TCP/IP、Netfilter 都在这里 |
include/ |
全局头文件 | include/linux/ 下的 device.h、platform_device.h 是常客 |
Documentation/ |
内核文档 | 遇到不懂的配置项,先来这里翻翻 |
嗯,这里要注意:arch/ 和 drivers/ 是嵌入式工程师最常打交道的两个目录。你想想看,移植内核到一块新板子,改的基本就是 arch/arm/mach-xxx/ 下的板级文件;写驱动,基本就在 drivers/ 下新建目录。
cd arch/arm/configs/ 看看有没有现成的 defconfig 文件。如果有,直接拿来做基础配置,能省不少事。
2.2 Kconfig 与 Makefile:内核的“骨架”和“肌肉”
内核源码能组织得这么清晰,靠的就是两套机制:Kconfig 和 Makefile。说白了,Kconfig 负责“有什么选项”,Makefile 负责“怎么编译”。
2.2.1 Kconfig:配置菜单的“剧本”
每个支持配置的目录下都有一个 Kconfig 文件。它定义了 menuconfig 里看到的菜单项、依赖关系和帮助信息。举个例子:
config CONFIG_MY_DRIVER
tristate "My custom driver support"
depends on I2C
help
This is a custom driver for my sensor.
Say Y to build it into kernel, M for module.
这里 tristate 表示三种状态:Y(编入内核)、M(编译为模块)、N(不编译)。depends on I2C 表示这个驱动依赖 I2C 子系统——如果 I2C 没开,这个选项就灰掉。
depends on SPI。结果 menuconfig 里选项亮着,选了 Y 编译直接报错,说找不到 SPI 核心头文件。查了半天才发现是依赖没写。所以,依赖关系一定要写清楚。
2.2.2 Makefile:告诉编译器怎么干活
同目录下的 Makefile 则负责把 Kconfig 的配置翻译成编译指令:
obj-$(CONFIG_MY_DRIVER) += my_driver.o
如果 CONFIG_MY_DRIVER 为 y,就编译进内核;为 m,就编译成 .ko 模块;为 n,就不编译。就这么简单。
多文件驱动也很常见:
obj-$(CONFIG_MY_DRIVER) += my_driver.o
my_driver-objs := main.o i2c_ops.o sensor_core.o
这样就把三个 .c 文件打包成一个驱动模块。
2.3 menuconfig:内核配置的“控制面板”
配置内核,我几乎只用 make menuconfig。它基于 ncurses 的文本界面,在终端里就能操作,不需要图形桌面。
基本操作流程:
- 加载默认配置:
make defconfig或make ARCH=arm xxx_defconfig - 进入菜单:
make menuconfig(如果交叉编译,加ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf-) - 导航:方向键移动,回车进入子菜单,空格/Y/M/N 切换选项状态
- 搜索:按
/键输入关键词,快速定位配置项 - 保存退出:生成
.config文件
/ 搜索时,会显示该配置项被哪些其他配置依赖,以及依赖了哪些配置。调试驱动时,如果某个选项灰掉,用搜索功能查依赖链,基本能定位问题。
我个人的工作流是这样的:先 make ARCH=arm myboard_defconfig 加载板级默认配置,然后 make menuconfig 微调,把需要的驱动选上,不需要的关掉。最后 diff .config .config.old 看看改了哪些,心里有数。
2.4 内核编译与烧录:从源码到跑起来
配置好了,接下来就是编译。嵌入式环境基本都是交叉编译:
export ARCH=arm
export CROSS_COMPILE=arm-linux-gnueabihf-
make -j4 zImage
make -j4 modules
make -j4 dtbs
这里 -j4 表示 4 线程并行编译,能快不少。编译产物:
arch/arm/boot/zImage:压缩后的内核镜像arch/arm/boot/dts/xxx.dtb:设备树二进制文件- 各个目录下的
.ko文件:内核模块
烧录方式取决于板子。我常用的几种:
| 方式 | 适用场景 | 命令示例 |
|---|---|---|
| TFTP 网络加载 | 开发调试阶段,频繁改内核 | tftp 0x82000000 zImage; bootz 0x82000000 |
| SD 卡烧录 | 量产或现场部署 | dd if=zImage of=/dev/sdb1 |
| fastboot | Android 或支持 fastboot 的板子 | fastboot flash boot zImage |
| JTAG/SWD | 裸机或 bootloader 损坏时救砖 | 配合 OpenOCD 或 JLink 工具 |
cp zImage /tftpboot/,在 U-Boot 里 tftp 加载,10 秒就能看到新内核启动。不用反复插拔 SD 卡,效率高很多。
烧录完重启,串口输出里看到 Starting kernel ... 然后进入文件系统,嗯,这感觉就对了。
2.5 本章知识结构图
下面这张图把内核源码结构、配置机制和编译流程串起来了,你可以对照着看:
从源码目录到 Kconfig/Makefile,再到 menuconfig 配置、make 编译、最后烧录运行,这条链路就是嵌入式 Linux 系统构建的核心流程。你把这个链条跑通了,后面写驱动、调系统,心里就有底了。