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左右,够新也够稳定。

步骤很简单:

  1. 安装VirtualBox或VMware
  2. 创建Ubuntu虚拟机,分配至少2核CPU、4GB内存
  3. 安装必要的开发工具
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 知识体系总览

这一章的内容比较多,我画了一张图帮你理清脉络。

第一章:课程导论与环境准备 Linux内核模块概念 动态加载/卸载 无需重启内核 内核空间运行 开发环境搭建 虚拟机方案(x86本地) 交叉编译链方案(ARM等) 内核源码树获取与配置 apt安装 kernel.org下载 git克隆 make oldconfig

1.5 本章小结

这一章我们做了三件事:

  • 理解了内核模块的本质——动态加载的内核代码,不用重启,不用重新编译整个内核。
  • 搭建了开发环境——虚拟机方案适合入门,交叉编译链方案适合嵌入式实战。两条路我都走过,各有优劣,看你的目标是什么。
  • 获取并配置了内核源码树——这是模块编译的基础。记住make modules_prepare这个步骤,我当年就是在这上面栽过跟头。