第1章:Linux内核源码结构分析
做内核移植这些年,我最大的感受就是——你得先看懂源码在哪儿,才能动手改。很多人一上来就make menuconfig,结果配了半天连文件系统都挂不上。说白了,内核源码的目录结构就是你的地图,Kconfig和Makefile就是你的工具。今天咱们就把这些基础打牢。
1.1 内核源码目录结构
先看看顶层目录。你解压完内核源码,第一眼看到的就是这些文件夹。我习惯把它们分成三类:核心代码、架构相关、工具脚本。
| 目录 | 作用 | 我的经验 |
|---|---|---|
| arch/ | 架构相关代码 | 移植时90%的修改都在这里 |
| drivers/ | 设备驱动 | 我踩坑最多的地方 |
| fs/ | 文件系统 | 根文件系统挂载常出问题 |
| include/ | 头文件 | 宏定义和结构体都在这里 |
| kernel/ | 核心调度、进程管理 | 一般不动,除非你改调度器 |
| mm/ | 内存管理 | MMU相关的坑不少 |
| net/ | 网络协议栈 | 网卡驱动调通后看这里 |
| scripts/ | 编译脚本、配置工具 | Kconfig语法不懂时翻这里 |
嗯,这里有个重点:arch/arm/ 和 arch/arm64/ 是我们做嵌入式移植的主战场。我刚开始做i.MX6ULL移植时,光在arch/arm/mach-imx/目录下就折腾了两周。每个SoC厂商都会在这里放自己的板级文件。
核心目录速记口诀:
arch看架构,drivers找驱动,fs管文件,include藏定义。
1.2 Kconfig与Makefile机制
这两个文件是内核配置系统的灵魂。Kconfig负责「有什么选项可以选」,Makefile负责「选了之后怎么编译」。你想想看,没有它们,内核就成了一个巨大的不可配置的怪物。
Kconfig语法速览
我见过很多新手直接复制别人的Kconfig,结果编译报错一脸懵。其实语法很简单:
config MY_DRIVER
bool "Enable My Driver"
depends on ARM
select GPIO
help
This is a sample driver for learning.
这里要注意:depends on 是依赖关系,select 是反向选择。我曾经犯过一个错——用select强制选中了一个还没初始化的子系统,结果内核启动到一半就panic了。
Makefile的两种写法
驱动编译有两种方式:
- 编译进内核:
obj-y += my_driver.o - 编译成模块:
obj-m += my_driver.o
我个人习惯在开发阶段用obj-m,这样不用每次都烧写整个内核。调试好了再改成obj-y。
小技巧: 在drivers/目录下新建一个自己的驱动文件夹,比如drivers/my_drv/,然后在父目录的Kconfig和Makefile里加上对应的路径。这样不会污染原有代码,也方便后续维护。
1.3 内核配置系统(menuconfig)
make menuconfig 本质上是一个图形化的Kconfig解析器。它读取所有Kconfig文件,生成一个配置菜单。你选中的选项会写入.config文件。
我遇到过最坑的事:有一次我改了.config文件里的一个选项,但没执行make oldconfig,结果编译出来的内核死活不认我的网卡。后来才发现,手动改.config会导致依赖关系错乱。
警告: 永远不要手动编辑.config文件!用make menuconfig、make defconfig或者make savedefconfig来管理配置。否则你会陷入「改了没生效」的噩梦。
常用的配置命令:
make menuconfig:图形化配置make defconfig:使用默认配置make savedefconfig:生成最小化配置make oldconfig:基于现有.config更新
1.4 内核编译流程
编译流程说白了就三步:配置、编译、安装。但每一步都有讲究。
标准编译步骤
# 第一步:配置
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- defconfig
# 第二步:编译
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j4
# 第三步:安装模块
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- modules_install INSTALL_MOD_PATH=./rootfs
这里-j4是并行编译,我建议根据你的CPU核心数来设。比如8核的机器用-j8,但别超过物理核心数,否则反而会变慢。
编译流程背后的逻辑
当你执行make时,内核的顶层Makefile会做这些事情:
- 读取
.config,确定哪些功能要编译 - 递归进入各个子目录,执行子Makefile
- 把所有的
.o文件链接成vmlinux - 生成压缩后的内核镜像
zImage或uImage - 编译设备树
.dtb文件
我记得第一次编译内核时,等了半小时结果报了个链接错误。后来发现是某个驱动没选对依赖。所以我的建议是:第一次编译用默认配置,先确保环境没问题,再慢慢加功能。
避坑指南: 我曾经在编译时忘了指定ARCH和CROSS_COMPILE,结果用gcc编译ARM内核,生成了x86的镜像。烧到板子上当然起不来。所以这两个变量一定要写对,或者直接export到环境变量里。
知识体系总览
下面这张图是我自己总结的内核源码分析框架,你可以把它当作学习路线图:
这张图把今天讲的内容串起来了。你从目录结构入手,搞清楚每个文件夹是干嘛的;然后学习Kconfig和Makefile,知道怎么添加自己的代码;接着用menuconfig配置内核;最后执行编译流程。每一步都有坑,但每一步也都有规律可循。
好了,这一章的内容就到这儿。记住:内核移植不是玄学,是工程。把基础打牢,后面的事情就顺了。
公众号:蓝海资料掘金营,微信deep3321