Linux字符设备驱动的完整生命周期:从insmod到rmmod的状态管理与sysfs接口设计
Linux字符设备驱动的完整生命周期从insmod到rmmod的状态管理与sysfs接口设计一、字符设备驱动不是简单的读写函数初学字符设备驱动时容易误解写好open/read/write/release四个回调注册一个设备号就完成了。但生产级驱动必须处理完整的生命周期——模块加载时的资源初始化、设备创建与sysfs接口、引用计数防止热拔拔时模块被提前卸载、模块卸载时的资源释放顺序。忽略任何一个环节都会导致内核崩溃或资源泄漏。二、生命周期状态机与关键节点字符设备驱动的生命周期由五个状态组成状态触发条件关键动作UNINIT模块未加载无INITinsmod执行注册设备号、创建cdev、初始化硬件ACTIVE设备节点被打开引用计数1BUSY有进程持有fd引用计数0禁止rmmodCLEANUPrmmod执行且引用计数0释放cdev、注销设备号、释放硬件资源生命周期状态机stateDiagram-v2 [*] -- UNINIT: 模块源码编译 UNINIT -- INIT: insmod执行 INIT -- ACTIVE: cdev_add成功 设备可用 ACTIVE -- BUSY: open() 引用计数1 BUSY -- BUSY: 多次open() 计数递增 BUSY -- ACTIVE: release() 计数-1到0 ACTIVE -- CLEANUP: rmmod且计数0 BUSY -- WAITING: rmmod但计数0 等待释放 WAITING -- ACTIVE: 所有fd关闭 计数归零 自动卸载 CLEANUP -- UNINIT: 释放完成 note right of BUSY: try_module_get 增加模块引用计数 note right of ACTIVE: module_put 减少模块引用计数核心约束try_module_get在open时增加模块引用计数module_put在release时减少。引用计数0时rmmod会被拒绝保证正在使用的模块不会被卸载。三、引用计数与热插拔的实现机制模块引用计数内核通过struct module的refcnt字段追踪模块引用。驱动必须手动维护/* open回调中增加引用计数 */ static int dev_open(struct inode *inode, struct file *filp) { if (!try_module_get(THIS_MODULE)) return -ENODEV; /* 模块正在卸载拒绝打开 */ filp-private_data dev_data; return 0; } /* release回调中减少引用计数 */ static int dev_release(struct inode *inode, struct file *filp) { module_put(THIS_MODULE); return 0; }try_module_get而非module_get如果模块正在卸载过程中try_module_get返回0安全失败而module_get会直接增加计数导致卸载失败不安全。热拔拔处理热拔拔场景设备物理移除时驱动需要通知所有持有fd的进程并等待它们关闭fd后再卸载模块。static void handle_hot_unplug(void) { /* 通知用户空间设备已移除 */ sysfs_notify(dev_data.dev.kobj, NULL, status); /* 标记设备不可用后续open拒绝 */ dev_data.removed true; /* 等待所有持有者释放 */ while (dev_data.open_count 0) { msleep(100); } /* 所有fd已关闭可以安全卸载 */ }四、sysfs接口设计与完整驱动代码sysfs接口是驱动与用户空间的配置通道。每个属性文件对应一个show/store回调。设计原则只暴露配置参数和状态信息不暴露内部实现细节store回调必须验证输入值的合法性属性文件权限严格区分配置文件只允许root写入状态文件对所有人只读完整驱动代码#include linux/module.h #include linux/kernel.h #include linux/fs.h #include linux/cdev.h #include linux/device.h #include linux/slab.h #include linux/uaccess.h #include linux/kobject.h #define DEVICE_NAME mydev #define CLASS_NAME mydev_class #define BUF_SIZE 4096 static dev_t dev_num; static struct cdev my_cdev; static struct class *my_class; static struct device *my_device; struct dev_private { char buffer[BUF_SIZE]; size_t data_len; bool removed; int open_count; int max_buffer_size; /* 可配置参数 */ }; static struct dev_private dev_data { .max_buffer_size BUF_SIZE, }; /* ---- sysfs属性 ---- */ static ssize_t status_show( struct device *dev, struct device_attribute *attr, char *buf) { return sprintf(buf, open_count: %d\n data_len: %zu\n removed: %d\n max_buffer_size: %d\n, dev_data.open_count, dev_data.data_len, dev_data.removed, dev_data.max_buffer_size); } static ssize_t max_buffer_size_store( struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { int new_size; if (kstrtoint(buf, 10, new_size)) return -EINVAL; if (new_size 1 || new_size BUF_SIZE) return -EINVAL; dev_data.max_buffer_size new_size; return count; } static DEVICE_ATTR(status, 0444, status_show, NULL); static DEVICE_ATTR(max_buffer_size, 0644, NULL, max_buffer_size_store); /* ---- 文件操作 ---- */ static int dev_open(struct inode *inode, struct file *filp) { if (dev_data.removed) return -ENODEV; if (!try_module_get(THIS_MODULE)) return -ENODEV; dev_data.open_count; filp-private_data dev_data; return 0; } static int dev_release(struct inode *inode, struct file *filp) { dev_data.open_count--; module_put(THIS_MODULE); return 0; } static ssize_t dev_read( struct file *filp, char __user *buf, size_t len, loff_t *off) { struct dev_private *priv filp-private_data; if (*off priv-data_len) return 0; size_t to_copy min(len, priv-data_len - *off); if (copy_to_user(buf, priv-buffer *off, to_copy)) return -EFAULT; *off to_copy; return to_copy; } static ssize_t dev_write( struct file *filp, const char __user *buf, size_t len, loff_t *off) { struct dev_private *priv filp-private_data; size_t to_copy min(len, (size_t)priv-max_buffer_size - *off); if (to_copy 0) return -ENOSPC; if (copy_from_user(priv-buffer *off, buf, to_copy)) return -EFAULT; *off to_copy; priv-data_len *off; return to_copy; } static struct file_operations fops { .owner THIS_MODULE, .open dev_open, .release dev_release, .read dev_read, .write dev_write, }; /* ---- 模块初始化与卸载 ---- */ static int __init mydev_init(void) { /* 动态分配设备号 */ if (alloc_chrdev_region(dev_num, 0, 1, DEVICE_NAME) 0) return -ENOMEM; /* 初始化并添加cdev */ cdev_init(my_cdev, fops); my_cdev.owner THIS_MODULE; if (cdev_add(my_cdev, dev_num, 1) 0) goto fail_cdev; /* 创建设备类和设备节点 */ my_class class_create(CLASS_NAME); if (IS_ERR(my_class)) goto fail_class; my_device device_create(my_class, NULL, dev_num, NULL, DEVICE_NAME); if (IS_ERR(my_device)) goto fail_device; /* 创建sysfs属性文件 */ device_create_file(my_device, dev_attr_status); device_create_file(my_device, dev_attr_max_buffer_size); pr_info(mydev: 初始化完成 major%d minor%d\n, MAJOR(dev_num), MINOR(dev_num)); return 0; fail_device: class_destroy(my_class); fail_class: cdev_del(my_cdev); fail_cdev: unregister_chrdev_region(dev_num, 1); return -1; } static void __exit mydev_exit(void) { /* 等待所有持有者释放 */ while (dev_data.open_count 0) { pr_warn(mydev: 等待open_count归零 (%d)\n, dev_data.open_count); msleep(100); } /* 按注册逆序释放资源 */ device_remove_file(my_device, dev_attr_status); device_remove_file(my_device, dev_attr_max_buffer_size); device_destroy(my_class, dev_num); class_destroy(my_class); cdev_del(my_cdev); unregister_chrdev_region(dev_num, 1); pr_info(mydev: 卸载完成\n); } module_init(mydev_init); module_exit(mydev_exit); MODULE_LICENSE(GPL); MODULE_AUTHOR(zhongyiren); MODULE_DESCRIPTION(字符设备驱动完整生命周期示例);资源释放的逆序原则sysfs属性→设备节点→设备类→cdev→设备号。每个资源的释放顺序必须与注册顺序严格相反否则会产生 dangling reference。五、总结字符设备驱动的生命周期由五个状态构成UNINIT→INIT→ACTIVE→BUSY→CLEANUP每个状态转换有明确的触发条件和关键动作引用计数通过try_module_get/module_put维护open时增加、release时减少计数0时rmmod被拒绝保证安全热拔拔处理需要标记设备不可用、通知用户空间、等待所有fd关闭后才执行卸载不能强制卸载正在使用的模块sysfs接口设计原则只暴露配置和状态、store回调必须验证输入、权限严格区分root写入与只读资源释放必须按注册逆序执行sysfs→device→class→cdev→chrdev_region逆序释放避免dangling reference导致的内核崩溃

相关新闻

2026高性价比geo监测工具排行榜|免费geo排名查询平台全解析

2026高性价比geo监测工具排行榜|免费geo排名查询平台全解析

信通院 2026 年 Q2 生成式搜索行业报告显示,国内超 66% 用户使用 AI 问答完成品牌筛选、产品对比,零点击搜索成为主流流量形态。传统 seo 依靠网页排名的运营逻辑大幅失效,geo(生成式引擎优化)成为企业数字化营销刚需。…

2026/7/7 0:07:41阅读更多 →
数据流图DFD:程序的“血管造影“

数据流图DFD:程序的“血管造影“

数据流图DFD:程序的"血管造影" 开篇:人体血管和软件系统 你知道人体是怎么工作的吗? 血液从心脏出发,通过动脉流向全身,然后通过静脉流回心脏。 在这个过程中,血液会经过各种"器官":肝脏、胃、肾脏、大脑…… 每个器官都会对血液做不同的"…

2026/7/7 0:07:41阅读更多 →
基于AI的本地化影视内容分析流水线:从语音转写到剧情摘要

基于AI的本地化影视内容分析流水线:从语音转写到剧情摘要

🚀 30款热门AI模型一站整合,DeepSeek/GLM/Qwen 随心用,限时 5 折。 👉 点击领海量免费额度 这次我们来看一个名为“末行手記”的影视内容分析项目。虽然标题看起来像是一部网飞韩剧的剧情简介,但在技术领域&#xf…

2026/7/7 0:07:41阅读更多 →
Git cherry-pick 原理与工程实践:从重演式重建到生产级避坑

Git cherry-pick 原理与工程实践:从重演式重建到生产级避坑

1. 项目概述:为什么 cherry-pick 是每个 Git 用户必须亲手“拧紧”的一把扳手在真实世界的协作开发中,我见过太多团队把git merge当成万能胶水——所有分支都往主干上糊,结果主干越来越臃肿,历史越来越模糊,一个 bug 出…

2026/7/7 1:02:46阅读更多 →
羽球联盟 HarmonyOS NEXT 实战系列 (14/20):装备资讯分类与本地兜底合并策略

羽球联盟 HarmonyOS NEXT 实战系列 (14/20):装备资讯分类与本地兜底合并策略

文章导读 装备页不仅展示列表,还要按球拍、球鞋、球包、配件、穿线等类别切换。远程抓取到的装备数据可能类别不全,因此列表页会把本地未覆盖类目补进来。刷新、分类、空态和详情跳转需要在同一页内保持逻辑一致。 页面效果 装备资讯页的体验目标很明…

2026/7/7 1:02:46阅读更多 →
惊!AI 巨头 Anthropic 对中国用户暗藏黑手,国产大模型崛起刻不容缓!

惊!AI 巨头 Anthropic 对中国用户暗藏黑手,国产大模型崛起刻不容缓!

01 “隐写间谍门”的操作手法万万没想到,世界 AI 巨头 Anthropic 竟曝出惊天丑闻。他们一边打着安全的旗号,一边往中国人的电脑里装木马。Anthropic 在自己的智能体 Claude Code 中偷偷植入了间谍软件,专门针对中国用户,一开始&a…

2026/7/7 1:02:46阅读更多 →
拯救你的B站缓存视频:m4s-converter无损转换工具使用指南

拯救你的B站缓存视频:m4s-converter无损转换工具使用指南

拯救你的B站缓存视频:m4s-converter无损转换工具使用指南 【免费下载链接】m4s-converter 一个跨平台小工具,将bilibili缓存的m4s格式音视频文件合并成mp4 项目地址: https://gitcode.com/gh_mirrors/m4/m4s-converter 你是否曾经在B站缓存了珍贵…

2026/7/7 1:02:46阅读更多 →
连接器SI仿真精度提升:CST背景材料与边界条件3大关键参数设置

连接器SI仿真精度提升:CST背景材料与边界条件3大关键参数设置

CST连接器信号完整性仿真精度提升:背景材料与边界条件的三大黄金法则在高速数字电路和射频系统设计中,连接器的信号完整性(SI)仿真已成为产品开发流程中不可或缺的一环。作为电磁仿真领域的标杆工具,CST Studio Suite凭借其多物理场耦合能力和…

2026/7/7 1:02:46阅读更多 →
实用Windows风扇控制软件:FanControl让你的电脑安静又高效

实用Windows风扇控制软件:FanControl让你的电脑安静又高效

实用Windows风扇控制软件:FanControl让你的电脑安静又高效 【免费下载链接】FanControl.Releases This is the release repository for Fan Control, a highly customizable fan controlling software for Windows. 项目地址: https://gitcode.com/GitHub_Trendin…

2026/7/7 0:57:46阅读更多 →
从GitHub安全案例解析常见漏洞与防护实践

从GitHub安全案例解析常见漏洞与防护实践

1. 项目概述:从GitHub Trending看安全实战 最近在GitHub Trending上看到一个项目,叫 skills4/skills ,它因为一些安全漏洞案例被大家讨论。这其实是一个挺典型的场景:一个旨在展示或教授某种技能的仓库,本身却成了安…

2026/7/6 4:26:20阅读更多 →
MLT 2026启示:因果推理与概率建模驱动下一代LLM应用

MLT 2026启示:因果推理与概率建模驱动下一代LLM应用

# MLT 2026启示:因果推理与概率建模驱动下一代LLM应用## 一、背景与挑战:从“黑箱预测”到“可信推理”2026年6月,第7届机器学习与趋势国际会议(MLT 2026)将在悉尼召开。会议议程中,“因果与可解释机器学习…

2026/7/6 2:48:33阅读更多 →
通达OA SQL注入漏洞深度剖析:从手工注入到自动化利用与防御

通达OA SQL注入漏洞深度剖析:从手工注入到自动化利用与防御

1. 项目概述与漏洞背景最近在梳理一些历史OA系统的安全风险时,通达OA v11.6版本中的一个老漏洞又进入了我的视线。这个漏洞位于/general/bi_design/appcenter/report_bi.func.php文件中,是一个典型的SQL注入点。虽然这个漏洞的利用方式看起来并不复杂&am…

2026/7/7 1:03:28阅读更多 →
Acunetix v24.8 深度解析:DAST漏洞扫描器核心原理与DevSecOps实践

Acunetix v24.8 深度解析:DAST漏洞扫描器核心原理与DevSecOps实践

1. 项目概述:Acunetix v24.8 高级版漏洞扫描器深度解析作为一名在网络安全领域摸爬滚打多年的老兵,我深知一款趁手的“兵器”对于安全测试工作意味着什么。今天要聊的,就是Web应用安全测试领域里一个响当当的名字——Acunetix。特别是其v24.8…

2026/7/7 0:02:41阅读更多 →
国产化信创改造:达梦/人大金仓适配与多数据库兼容方案实战(SpringBoot)

国产化信创改造:达梦/人大金仓适配与多数据库兼容方案实战(SpringBoot)

国产化信创改造:达梦/人大金仓适配与多数据库兼容方案实战(SpringBoot) 🌐 演示地址:http://ruoyioffice.com | 📦 源码1GitHub:ruoyi-office | 📦 源码2GitCode:ruoyi-o…

2026/7/7 0:02:41阅读更多 →
CentOS 7/8 SSH 连接失败:5步系统性排错流程与决策树

CentOS 7/8 SSH 连接失败:5步系统性排错流程与决策树

CentOS SSH连接故障排查:从基础检查到深度修复的完整指南引言当你尝试通过Xshell或其他SSH客户端连接CentOS服务器时,突然遭遇"Connection refused"或"Connection timed out"的错误提示,这种经历对任何运维人员或开发者来…

2026/7/7 0:02:41阅读更多 →
YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

如果你在部署 YOLOv8 时,发现推理速度只有可怜的 1-2 FPS,而别人的演示视频却能跑到 30 FPS 以上,那么问题很可能不在模型本身,而在于你的整个处理链路。很多开发者拿到一个训练好的 YOLOv8 模型后,会直接使用官方示例…

2026/7/6 4:45:01阅读更多 →
Coze与Dify对比指南:低代码AI应用开发从入门到实战

Coze与Dify对比指南:低代码AI应用开发从入门到实战

1. 从零到一:为什么你需要了解 Coze 和 Dify?如果你对 AI 应用开发感兴趣,但一看到“大模型”、“智能体”、“工作流”这些词就头疼,觉得门槛太高,那这篇文章就是为你准备的。很多开发者,包括我自己&#…

2026/7/6 4:45:01阅读更多 →
AI生图工具怎么选?2026年6月版实测对比

AI生图工具怎么选?2026年6月版实测对比

做自媒体的朋友应该都有体会:配图一直是个让人头疼的问题。2026年,AI生图工具已经非常成熟了,但工具太多反而不知道怎么选。以下是截至2026年6月我对主流AI生图工具的实测对比。Midjourney V8.1:速度之王2026年6月11日&#xff0c…

2026/7/6 4:45:03阅读更多 →