云计算百科
云计算领域专业知识百科平台

Linux字符设备驱动的完整生命周期:从insmod到rmmod的状态管理与sysfs接口设计

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回调。设计原则:

  • 只暴露配置参数和状态信息,不暴露内部实现细节
  • 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导致的内核崩溃
  • 赞(0)
    未经允许不得转载:网硕互联帮助中心 » Linux字符设备驱动的完整生命周期:从insmod到rmmod的状态管理与sysfs接口设计
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!