Linux字符设备驱动的完整生命周期:从insmod到rmmod的状态管理与sysfs接口设计
一、字符设备驱动不是简单的读写函数
初学字符设备驱动时容易误解:写好open/read/write/release四个回调,注册一个设备号,就完成了。但生产级驱动必须处理完整的生命周期——模块加载时的资源初始化、设备创建与sysfs接口、引用计数防止热拔拔时模块被提前卸载、模块卸载时的资源释放顺序。忽略任何一个环节都会导致内核崩溃或资源泄漏。
二、生命周期状态机与关键节点
字符设备驱动的生命周期由五个状态组成:
| UNINIT | 模块未加载 | 无 |
| INIT | insmod执行 | 注册设备号、创建cdev、初始化硬件 |
| ACTIVE | 设备节点被打开 | 引用计数+1 |
| BUSY | 有进程持有fd | 引用计数>0,禁止rmmod |
| CLEANUP | rmmod执行且引用计数=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回调。设计原则:
完整驱动代码
#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。
网硕互联帮助中心




评论前必须登录!
注册