嵌入式 Linux 驱动开发:第一个内核模块怎么写?
单片固件里加个驱动要重新编译整个镜像、重新烧录;Linux 里却只需要一条
insmod命令,驱动就悄悄挂进了正在运行的内核。这个"热插拔"的秘密叫内核模块(LKM)。今天带你写第一个。
一、为什么内核需要"模块"?
早期内核把所有功能静态编译进镜像。好处是简单确定,坏处是:换块网卡、插个 USB 设备,就要重新配置、重新编译、重新部署整个内核------"牵一发而动全身"。
可加载内核模块(LKM) 解决这个矛盾:把设备驱动等编译成独立文件(.ko),运行时按需加载,不需要重启。
对嵌入式场景格外有价值:开发板出厂烧一个通用内核,不同客户外接的传感器、显示屏千差万别------"一份内核 + 多份驱动"的灵活组合,而不是为每种硬件单独出一份固件。
二、模块和普通程序的三点不同
- 跑在内核态 :没有独立进程地址空间,直接运行在内核虚拟地址空间,
printk、kmalloc都发生在内核上下文 - 不能用标准 C 库 :模块只能调用内核导出的函数(
printk、kmalloc、copy_to_user) - 与内核耦合极深:数据结构版本、函数原型必须和当前内核严格一致,否则轻则加载失败,重则内核崩溃(oops/panic)
可以把模块理解成"内核的插件"------不独立运行,依附宿主、用宿主 API、守宿主规则。
三、第一个内核模块长什么样
c
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
static int __init hello_init(void) {
printk(KERN_INFO "Hello, kernel module!\n");
return 0; /* 返回 0 = 加载成功 */
}
static void __exit hello_exit(void) {
printk(KERN_INFO "Goodbye, kernel module!\n");
}
module_init(hello_init); /* 加载入口:insmod 时调用 */
module_exit(hello_exit); /* 卸载入口:rmmod 时调用 */
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Kruptos");
MODULE_DESCRIPTION("My first kernel module");
编译后用 insmod hello.ko 加载,dmesg 能看到打印;rmmod hello 卸载。整个过程不重启、不重编内核。
四、代码演示:模拟 insmod 的加载检查
python
def insmod(module_name, kernel_ver):
"""简化:检查 .ko 文件 + vermagic 内核版本兼容"""
if module_name.endswith(".ko") and "v6.1" in kernel_ver:
return (f"[OK] {module_name} 已加载到内核 {kernel_ver}\n"
f" printk: Hello, kernel module!\n"
f" 注册成功,模块处于运行态")
return f"[FAIL] vermagic 不匹配(模块为其他内核版本编译)"
print(insmod("hello_drv.ko", "v6.1.0"))
print()
print(insmod("hello_drv.ko", "v5.15.0"))
运行输出:
[OK] hello_drv.ko 已加载到内核 v6.1.0
printk: Hello, kernel module!
注册成功,模块处于运行态
[FAIL] vermagic 不匹配(模块为其他内核版本编译)
看出关键点了:模块必须和当前内核版本匹配 (vermagic 校验)------这就是为什么交叉编译时工具链版本要对齐,否则 insmod 直接拒绝。
五、避坑清单
- 内核版本必须一致:vermagic 不匹配会拒绝加载,重编模块而不是降级内核
- 不要用 printf/malloc:内核态只能用 printk/kmalloc,用了标准库直接编译失败
- 返回 0 才是成功:init 函数返回非 0 表示加载失败,内核会回滚
- 打印用 KERN_INFO 等级别 :printk 不带级别可能不显示,
dmesg看内核日志 - 嵌入式开发板先交叉编译:x86 上编的模块 ARM 板子加载不了
六、想系统学嵌入式 Linux 驱动?
本文精选自 ima 知识号【Kruptos】《嵌入式 Linux 驱动开发详解》订阅库(第 009 期内核模块是什么、第 010 期 module_init 机制等 100 期系统教程,从内核架构、模块机制、字符设备、platform 总线到设备树与实战驱动,每期配可运行代码)。
📚 完整系列 100 期 + 配套代码,已在 ima 知识号发布
本文只是系列的一个切片。完整系列(100 期系统教程 + 每期可运行代码)在 ima 知识号【Kruptos】持续更新中:
- 🗂 67+ 技术知识库:信号与系统、SDR 软件无线电、数字信号处理、操作系统、AI Agent、大模型微调......几乎覆盖全部软硬件技术栈
- 🧠 8 款 AI 技能:系列生产、知识库管理、CMMI 受管开发、自进化 Agent 等,已在 ima 技能广场上架,即装即用
- ✅ 全部免费订阅,后续更新自动推送
🔍 订阅方式:打开 ima(腾讯智能工作台)→ 搜索「Kruptos」→ 一键订阅。或在 ima 内直接搜索《嵌入式 Linux 驱动开发详解》等知识库名称。
💬 你写过最"惊险"的内核代码是什么?评论区聊聊------想看字符设备还是 platform 总线,点赞高的安排。
作者:Kruptos(西电毕业,13 年无线通信/DSP/嵌入式科研)|原创内容,转载注明出处