嵌入式 Linux 驱动开发:第一个内核模块怎么写?

嵌入式 Linux 驱动开发:第一个内核模块怎么写?

单片固件里加个驱动要重新编译整个镜像、重新烧录;Linux 里却只需要一条 insmod 命令,驱动就悄悄挂进了正在运行的内核。这个"热插拔"的秘密叫内核模块(LKM)。今天带你写第一个。

一、为什么内核需要"模块"?

早期内核把所有功能静态编译进镜像。好处是简单确定,坏处是:换块网卡、插个 USB 设备,就要重新配置、重新编译、重新部署整个内核------"牵一发而动全身"。

可加载内核模块(LKM) 解决这个矛盾:把设备驱动等编译成独立文件(.ko),运行时按需加载,不需要重启。

对嵌入式场景格外有价值:开发板出厂烧一个通用内核,不同客户外接的传感器、显示屏千差万别------"一份内核 + 多份驱动"的灵活组合,而不是为每种硬件单独出一份固件。

二、模块和普通程序的三点不同

  1. 跑在内核态 :没有独立进程地址空间,直接运行在内核虚拟地址空间,printkkmalloc 都发生在内核上下文
  2. 不能用标准 C 库 :模块只能调用内核导出的函数(printkkmalloccopy_to_user
  3. 与内核耦合极深:数据结构版本、函数原型必须和当前内核严格一致,否则轻则加载失败,重则内核崩溃(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 直接拒绝。

五、避坑清单

  1. 内核版本必须一致:vermagic 不匹配会拒绝加载,重编模块而不是降级内核
  2. 不要用 printf/malloc:内核态只能用 printk/kmalloc,用了标准库直接编译失败
  3. 返回 0 才是成功:init 函数返回非 0 表示加载失败,内核会回滚
  4. 打印用 KERN_INFO 等级别 :printk 不带级别可能不显示,dmesg 看内核日志
  5. 嵌入式开发板先交叉编译: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/嵌入式科研)|原创内容,转载注明出处

相关推荐
wangqiaowq1 小时前
langfuse linux下的安装部署
linux·运维·服务器
NeilYuen1 小时前
用RDMA实现文件传输
linux
青禾8371 小时前
Docker 从入门到实践:镜像、容器、数据卷与 Docker Compose
运维·docker·容器
HUI-4741 小时前
EG1133|SOT23‑6 18V/3A 电流模式同步降压 DC‑DC,中小功率大电流紧凑型电源优选
嵌入式硬件·硬件工程
Discipline~Hai1 小时前
Linux网络编程05-sqlite3数据库
linux·c语言·网络·数据库·sqlite·linux应用软件编程
wdfk_prog1 小时前
Docker 开发环境到底要交付什么:镜像、源码与运行脚本
运维·docker·容器
Huangjin007_1 小时前
【Linux 系统篇(十七)】进程(五) :进程切换、O(1) 调度算法
linux·运维·服务器
weixin_464307631 小时前
linux虚拟机断网
linux·运维·服务器
志栋智能1 小时前
凌晨3点的告警,如何用AI在5分钟内完成定界?
运维·服务器·数据库·人工智能·自动化