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

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

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

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

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

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

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

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

  1. 跑在内核态 :没有独立进程地址空间,直接运行在内核虚拟地址空间,printk、kmalloc 都发生在内核上下文
  2. 不能用标准 C 库 :模块只能调用内核导出的函数(printk、kmalloc、copy_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/嵌入式科研)|原创内容,转载注明出处

相关推荐
大哥您好4 小时前
QEMU 11.1.2 多架构编译指南(x86_64 / aarch64 / riscv64)
linux·服务器
国科安芯5 小时前
立方星与微纳卫星综合电子微型化趋势下的抗辐射MCU选型综述
人工智能·单片机·嵌入式硬件·mcu·risc-v·抗辐射
xunyan62345 小时前
3-1 独立按键控制LED亮灭(SDCC + Notepad++ + STC-ISP/普中ISP)
单片机·嵌入式硬件·接口隔离原则
道满科技5 小时前
Function Calling 参数总出错?只修参数就行
linux·windows·python
Gauss松鼠会6 小时前
高斯数据库(openGauss)的物理存储结构
运维·服务器·数据库·sql·opengauss·经验总结
WD137298015576 小时前
WD5030L,为充电桩、工业设备、5G 基站、车载系统、消费电子提供电源,7-30V 的宽压输入
stm32·单片机·嵌入式硬件·5g·车载系统·51单片机
Android系统攻城狮6 小时前
Linux Gstreamer深度解析之gst_audio_ring_buffer_prepare_read调用流程与实战(七十四)
linux·运维·服务器·gstreamer音视频·音视频进阶
杨云龙UP6 小时前
PostgreSQL 18.4 数据库整库迁移实战:pg_dump 备份与 pg_restore 恢复完整操作手册
运维·服务器·数据库·postgresql·pg_dump·pg_restore
我科绝伦(Huanhuan Zhou)6 小时前
Linux 通用压力测试脚本合集:磁盘 + 网络
linux·网络·压力测试
无人生还别怕7 小时前
如何安装任意版本docker
运维·docker·容器