1. 课程导论与环境准备:Linux内核模块概念、开发环境搭建
大家好,欢迎来到这门实战课。我是你们的老朋友,一个跟Linux内核打了十几年交道的嵌入式工程师。
说实话,每次带新人入门,我最怕的就是他们一上来就啃《Linux设备驱动程序》那本大厚书。不是说书不好,而是太容易劝退了。我自己的经验是——先动手,再理论。你先把一个模块编译出来、加载进去、看到内核日志里打出你的名字,那种成就感比什么都管用。
这一章,我们就先把地基打好。别急,后面有的是机会深入。
1.1 什么是Linux内核模块?
先问个问题:你写了一个驱动程序,想让Linux内核认识你的硬件。怎么办?
最笨的办法——重新编译整个内核。把代码塞进内核源码树,make,然后重启。每次改一行代码,就得来一遍。我在第一个项目里就这么干过,编译一次40分钟,一天下来光等编译了。后来带我的老工程师看不下去了,丢给我一句话:「用模块啊,小伙子。」
内核模块(Kernel Module),说白了就是一段可以在运行时动态加载到内核的代码。它不需要重新编译整个内核,不需要重启机器。insmod加载,rmmod卸载,就这么简单。
| 特性 | 静态编译进内核 | 内核模块 |
|---|---|---|
| 是否需要重新编译内核 | 是 | 否 |
| 是否需要重启 | 是 | 否 |
| 调试迭代速度 | 慢(小时级) | 快(秒级) |
| 典型应用场景 | 核心子系统、启动必需驱动 | 设备驱动、文件系统、网络协议 |
核心要点:模块的本质就是一段内核代码,运行在内核空间,拥有最高权限。写模块就是在写内核代码——一个空指针就能让整个系统挂掉。嗯,我当年就干过这事,教训深刻。
1.2 开发环境搭建
环境搭建这块,我建议你分两条路走。一条是纯软件模拟,适合学习;另一条是交叉编译,适合嵌入式实战。我们两条都讲。
1.2.1 虚拟机方案(适合初学者)
最简单的方式:装个Ubuntu虚拟机,直接在宿主机上编译和测试模块。我推荐Ubuntu 22.04 LTS,内核版本5.15左右,够新也够稳定。
步骤很简单:
- 安装VirtualBox或VMware
- 创建Ubuntu虚拟机,分配至少2核CPU、4GB内存
- 安装必要的开发工具
sudo apt update
sudo apt install build-essential linux-headers-$(uname -r)
sudo apt install git vim cscope # 我个人习惯用这些
这里有个坑:linux-headers-$(uname -r) 这个包必须装。它包含了当前内核的编译配置和头文件,没有它,你的模块根本编译不过去。
注意:如果你更新了内核版本,记得重新安装对应版本的linux-headers。我曾经因为升级内核忘了装头文件,折腾了半小时才找到原因。
1.2.2 交叉编译链方案(适合嵌入式)
如果你跟我一样是做嵌入式开发的,那虚拟机方案就不够用了。你的目标板可能是ARM、RISC-V或者MIPS,x86上编译出来的模块没法直接在目标板上跑。这时候就需要交叉编译链。
以ARM Cortex-A系列为例:
# 安装ARM交叉编译链
sudo apt install gcc-arm-linux-gnueabihf
# 验证安装
arm-linux-gnueabihf-gcc --version
交叉编译的核心思路:在x86主机上编译,生成ARM架构的可执行代码。你想想看,你的开发机是x86,目标板是ARM,编译器必须知道目标架构的指令集、ABI、字节序等等。这就是交叉编译链干的事。
| 目标架构 | 交叉编译链前缀 | 常见场景 |
|---|---|---|
| ARM (32-bit) | arm-linux-gnueabihf- | 树莓派、STM32MP1 |
| AArch64 (ARM64) | aarch64-linux-gnu- | 树莓派4/5、RK3588 |
| RISC-V (64-bit) | riscv64-linux-gnu- | 全志D1、SiFive |
1.3 内核源码树获取与配置
模块编译离不开内核源码树。为什么?因为模块需要依赖内核的头文件、Makefile规则和配置选项。没有源码树,你连printk都用不了。
1.3.1 获取内核源码
有三种方式:
- 方式一:apt安装(推荐初学者)
sudo apt install linux-source-$(uname -r | cut -d'-' -f1)
安装后源码在/usr/src/目录下。 - 方式二:从kernel.org下载
wget https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.1.tar.xz
适合需要特定版本或做内核裁剪的场景。 - 方式三:git克隆(适合开发)
git clone git://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
我习惯用这种方式,方便切换分支和查看历史。
1.3.2 配置内核
拿到源码后,需要配置。配置的目的是生成.config文件,告诉内核哪些功能要编译、哪些要作为模块、哪些不要。
cd linux-6.1
# 基于当前系统的配置(推荐)
make oldconfig
# 或者使用默认配置
make defconfig
# 或者图形化配置(需要ncurses库)
make menuconfig
我个人习惯用make oldconfig。它会读取你当前运行内核的配置,只问那些新增选项。省事,而且保证兼容性。
小技巧:配置完成后,执行make modules_prepare。这一步会生成模块编译所需的Module.symvers文件和必要的头文件。很多新手漏掉这一步,编译模块时报错找不到__this_module,其实就是没做prepare。
1.4 知识体系总览
这一章的内容比较多,我画了一张图帮你理清脉络。
1.5 本章小结
这一章我们做了三件事:
- 理解了内核模块的本质——动态加载的内核代码,不用重启,不用重新编译整个内核。
- 搭建了开发环境——虚拟机方案适合入门,交叉编译链方案适合嵌入式实战。两条路我都走过,各有优劣,看你的目标是什么。
- 获取并配置了内核源码树——这是模块编译的基础。记住
make modules_prepare这个步骤,我当年就是在这上面栽过跟头。