C 语言与内核模块(LKM)
更新时间:2026-08-26。本文是
languages/c/主题专家层文档,也是 C 语言主题的收束篇。C 语言最硬核的应用之一,就是写 Linux 内核模块——直接跑在内核态、能操作硬件、崩溃会拖垮整个系统。本文讲清内核模块的结构,作为"C 语言 → 内核世界"的入口。
本文要回答的问题
- 内核模块(.ko)和普通程序有什么本质区别?
- 一个最小的内核模块长什么样?怎么编译、加载、卸载?
- 内核态编程和用户态编程有哪些根本差异?
一、内核态 vs 用户态
普通 C 程序跑在用户态,内核模块跑在内核态:
| 对比 | 用户态程序 | 内核模块 |
|---|---|---|
| 运行权限 | 受限(ring 3) | 最高权限(ring 0) |
| 可用函数 | libc(printf、malloc) | 内核 API(printk、kmalloc) |
| 出错后果 | 只崩溃自己 | 拖垮整个系统(panic) |
| 调试 | gdb | printk + dmesg / kgdb |
| 崩溃信息 | core dump | oops / panic |
核心差异:内核态没有 libc、没有内存保护、不能随便用浮点、栈很小(8KB 内核栈 vs 8MB 用户栈)。
二、最小的内核模块
c
// hello_module.c
#include <linux/module.h>
#include <linux/kernel.h>
static int __init hello_init(void) {
printk(KERN_INFO "Hello, kernel module!\n"); // 内核日志,不是 printf
return 0; // 返回 0 表示加载成功
}
static void __exit hello_exit(void) {
printk(KERN_INFO "Goodbye, kernel module!\n");
}
module_init(hello_init); // 加载时调用的入口
module_exit(hello_exit); // 卸载时调用的出口
MODULE_LICENSE("GPL");
MODULE_AUTHOR("derekzhuo");
MODULE_DESCRIPTION("A minimal kernel module");关键组成
| 部分 | 作用 |
|---|---|
module_init(fn) | 声明"加载时执行 fn" |
module_exit(fn) | 声明"卸载时执行 fn" |
printk | 内核日志函数(不是 printf) |
MODULE_LICENSE | 许可证声明(GPL 才能用部分内核符号) |
__init / __exit | 提示编译器:初始化后内存可回收 |
三、编译内核模块(Makefile)
makefile
# Makefile
obj-m += hello_module.o
all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean编译需要 kernel-devel(内核头文件)和 root 权限:
bash
make # 编译出 hello_module.ko
insmod hello_module.ko # 加载模块
dmesg | tail # 看 printk 输出("Hello, kernel module!")
rmmod hello_module # 卸载模块实测环境说明:云虚拟机(CentOS 7,3.10 内核)通常没有 kernel-devel,且需要 root,无法演示完整加载流程;本站实验在内核模块方向预留了入口,需在带内核头文件的物理机/容器里实践。
四、内核模块的应用
| 应用 | 说明 |
|---|---|
| 设备驱动 | 字符设备、块设备、网络设备驱动 |
| 文件系统 | 自定义文件系统 |
| 网络过滤 | netfilter 钩子(防火墙) |
| 性能观测 | 内核探针、tracepoint(配合本站 perf/eBPF 主线) |
| 安全 | LSM(Linux 安全模块) |
与本项目的关联:本站的性能剖析主线(perf、eBPF、tracepoint)大量涉及内核模块——
perf依赖内核的 perf 子系统,eBPF 程序跑在内核态。写内核模块是理解这些工具"底层在干嘛"的必经之路。
五、内核态编程的禁区
| 禁区 | 原因 |
|---|---|
| 别用浮点 | 内核态默认禁用 FPU(性能 + 上下文切换成本) |
| 别用大栈 | 内核栈只有 8KB,递归、大数组会栈溢出 |
| 别 sleep 随意 | 中断上下文、持锁时不能睡眠 |
| 别用 printf/malloc | 内核态用 printk/kmalloc/vmalloc |
| 谨慎并发 | 内核并发更复杂(SMP、中断、抢占) |
六、与本站主线衔接
- 系统调用:内核模块实现新系统调用的方式,见 系统调用。
- 进程调度:内核模块视角的 task_struct、调度,见 进程与线程。
- perf/eBPF:内核观测机制,见 perf 使用指南。
- 内存布局:内核地址空间与用户态的差异,见 内存布局与 ABI。
一句话总结
内核模块用 module_init/module_exit 声明入口出口、printk 打日志、kernel-devel 编译成 .ko 后 insmod/rmmod 加载卸载;内核态没有 libc、栈只有 8KB、出错就 panic,是最硬核也最危险的 C 语言应用——也是理解 perf/eBPF 等性能工具的底层入口。