1. Linux内核概述:版本演进、架构与源码目录
做嵌入式Linux开发这么多年,我始终觉得,搞懂内核本身是绕不开的一步。很多人上来就急着写驱动、改设备树,结果遇到个oops就抓瞎了。说白了,内核就是操作系统的灵魂,你连它长什么样都不清楚,怎么跟它打交道?
这一章,咱们就先把内核的底牌翻一翻。版本怎么变的、架构怎么设计的、源码目录怎么组织的——这些基础打牢了,后面编译、配置、写驱动才能顺风顺水。
1.1 Linux内核版本演进
Linux内核从1991年Linus Torvalds发布第一个版本到现在,已经走了三十多年。我最早接触的是2.6内核,那时候写驱动还得手动处理很多锁的问题,现在想想真是怀念(也真是折腾)。
版本号的变化其实反映了内核社区的演进思路。早期是A.B.C三段式,比如2.6.32。后来从3.0开始,版本号节奏变快了。到了2015年,Linus干脆把版本号改成了主版本.次版本.补丁版本,也就是我们现在看到的5.x、6.x系列。
关键版本节点速览
- Linux 0.01 (1991) — 最初的原型,只有基本进程管理和文件系统
- Linux 1.0 (1994) — 支持SMP(对称多处理),开始走向生产环境
- Linux 2.6 (2003) — 引入内核抢占、udev、新的调度器,驱动模型大改
- Linux 3.0 (2011) — 主要是版本号重置,功能上延续2.6的演进
- Linux 4.x (2015) — 引入eBPF、live patching、更好的ARM支持
- Linux 5.x (2019) — 支持RISC-V、GPU驱动重构、BPF持续增强
- Linux 6.x (2022) — 更多新硬件支持,性能优化,Rust语言初步引入
我个人习惯是,做产品选型时尽量选LTS(长期支持)版本。比如5.10、5.15、6.1这些,社区会维护好几年,省得频繁升级内核带来的适配工作。你想想看,一个嵌入式产品从开发到停产可能三五年,中间换内核版本?那简直是噩梦。
我的建议:新手学习直接用最新的稳定版就好,比如6.x系列。但做产品,一定选LTS版本。我曾经在一个项目里用了非LTS的4.18,结果半年后社区停止维护,遇到安全漏洞只能自己打补丁,那叫一个酸爽。
1.2 内核架构:宏内核与微内核
操作系统的内核架构,主流就两种:宏内核(Monolithic Kernel)和微内核(Microkernel)。Linux属于前者,但也不是纯粹的宏内核——它引入了模块机制,算是“可加载模块的宏内核”。
这两种架构有什么区别?我画了张图,一看就明白。
宏内核的特点就是“大而全”。进程调度、内存管理、文件系统、网络协议、设备驱动……全都在内核空间里跑。好处是性能高——函数调用直接走,不用来回切换上下文。坏处呢?一个驱动崩了,整个内核可能跟着挂。
微内核正好相反,它只保留最核心的功能(IPC、基本调度、内存映射),其他服务都放到用户空间去跑。这样隔离性好,一个服务挂了不影响别的。但代价就是性能——服务间通信要走IPC,上下文切换频繁。
注意:Linux虽然归类为宏内核,但它通过内核模块(LKM)机制实现了动态加载和卸载驱动。这在一定程度上弥补了宏内核的灵活性不足。我在项目中经常用模块方式调试驱动,改完代码直接insmod/rmmod,不用反复重启系统,效率高很多。
1.3 内核源码目录结构解析
下载一份Linux内核源码,解压之后你会看到一堆目录。第一次看的人可能会懵——这么多文件夹,从哪下手?
别急,我带你捋一遍核心目录。记住一个原则:按功能模块划分,每个目录负责一类子系统。
| 目录 | 作用 | 你该关注什么 |
|---|---|---|
arch/ |
体系结构相关代码 | ARM、x86、RISC-V等平台启动、内存映射、中断 |
drivers/ |
设备驱动 | 写驱动主要在这里,按设备类型分目录 |
fs/ |
文件系统实现 | ext4、proc、sysfs、debugfs等 |
kernel/ |
核心内核代码 | 调度、进程管理、信号、模块加载 |
mm/ |
内存管理 | 页分配、slab、虚拟内存 |
net/ |
网络协议栈 | TCP/IP、socket、网络设备 |
include/ |
头文件 | 驱动开发常引用的linux/、asm/ |
scripts/ |
编译和配置脚本 | Kconfig、Makefile辅助工具 |
Documentation/ |
内核文档 | 写驱动前先翻翻,很多坑文档里写了 |
tools/ |
用户空间工具 | perf、selftest等调试工具 |
这里面,arch/和drivers/是嵌入式开发最常打交道的两个目录。arch下面按CPU架构分,比如arch/arm/、arch/arm64/、arch/x86/。每个架构下面又有boot/(启动代码)、mm/(内存管理)、mach-xxx/(板级支持)等子目录。
drivers/目录就更大了——它占了整个内核源码的一半以上。里面按设备类型分:i2c/、spi/、gpio/、usb/、pci/、net/、video/……你写什么驱动,就去对应的子目录找参考。
一个小技巧:我刚开始学驱动时,经常在drivers/里找现成的驱动代码来模仿。比如你要写一个I2C温度传感器驱动,就去drivers/i2c/下面看看已有的芯片驱动是怎么写的。内核源码本身就是最好的学习资料。
还有一个目录容易被忽略——include/。这里面定义了内核编程的核心数据结构和API。比如include/linux/device.h定义了设备模型,include/linux/platform_device.h定义了平台设备接口。写驱动时,头文件引用错了,编译直接报错。嗯,这里要注意,#include <linux/xxx.h>和#include <asm/xxx.h>的区别——前者是架构无关的通用头文件,后者是架构相关的。
1.4 内核源码获取与版本选择
源码去哪下?官方渠道就一个:kernel.org。你也可以用git直接拉Linus的树:
git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
但说实话,对于嵌入式开发,我更推荐用LTS版本的tarball。git仓库太大,而且你不需要整个历史。直接下载压缩包,解压就能用:
wget https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.1.55.tar.xz
tar -xf linux-6.1.55.tar.xz
cd linux-6.1.55
版本选择建议:
- 学习阶段:用最新的稳定版(如6.5+),体验新特性
- 产品开发:用LTS版本(如5.10、5.15、6.1),社区长期维护
- 芯片厂商SDK:用厂商提供的内核版本,他们做了适配和验证
我曾经在一个项目里踩过坑——用了太新的内核版本,结果某个外设的驱动在主线里还没稳定,厂商的BSP也不支持,最后只能自己backport补丁,折腾了两周。所以,做产品别追新,稳定第一。