在 module_exit 上正确关闭结构文件 *

标签 c linux linux-kernel linux-device-driver

我正在学习如何编写 linux 驱动程序,但我对如何正确优雅地使用内核 API 感到有些困惑。 我尝试编写一个简单的 misc 驱动程序,它在/dev/hello 上创建一个设备节点。在用户空间中,读者可以在设备上读取并阻塞,直到写者在设备上写入一些数据,所有读者都获得了写入的数据。如果在读取器读取旧数据之前有新数据到来,则读取器将丢失旧数据。

为了实现它,我为每个打开的文件创建一个struct hello_file_data,并将它们放在一个全局链接列表中,数据字段指示有一些数据可以读取。

static LIST_HEAD(hello_opened_file_list);

struct hello_file_data {
    struct list_head    entry;
    struct file *       owner_file;
    int                 data;
};

在读取函数中,我使用wait_event_interruptible来阻塞这个线程,等待文件的struct hello_file_datadata字段变为1。

static ssize_t hello_read(struct file * file, char __user * data, size_t n, loff_t * offset_p)
{
    int res;
    struct hello_file_data * fdat = file->private_data;

    res = wait_event_interruptible(hello_wait_data, hello_dead || fdat->data);
    if (res) {
        return res;
    }
    if (hello_dead) {
        return -ENODEV;
    }
    n = min(hello_bufsize, n);
    if (copy_to_user(data, hello_buffer, n)) {
        return -EFAULT;
    }
    fdat->data = 0;
    return n;
}

在写入函数中,我迭代全局链表以将每个文件的struct hello_file_datadata 字段设置为1,然后通知所有读取线程唤醒。

static ssize_t hello_write(struct file * file, const char __user * data, size_t n, loff_t * offset_p)
{
    struct hello_file_data * fdat = file->private_data;

    if (!n) {
        return 0;
    }
    n = min(sizeof(hello_buffer), n);
    if (copy_from_user(hello_buffer, data, n)) {
        return -EFAULT;
    }
    hello_bufsize = n;
    spin_lock(&hello_list_lock);
    list_for_each_entry(fdat, &hello_opened_file_list, entry) {
        fdat->data = 1;
    }
    spin_unlock(&hello_list_lock);
    wake_up_interruptible(&hello_wait_data);
    return n;
}

我对代码有3个困惑

  1. 当调用module_exit时,我必须等到所有struct file *都正确关闭,使用wait_event合适吗?
  2. 当这个内核模块将被 rmmod 删除时,我无法关闭(分离?)struct file *,所以 rmmod 命令将阻塞直到这些文件被程序关闭;有更好的处理方法吗?
  3. 在迭代所有 struct file * 时,有没有办法使用内核 API 而不是管理我自己的链表?

你好.c

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/list.h>
#include <linux/fs.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/miscdevice.h>
#include <linux/uaccess.h>

static LIST_HEAD(hello_opened_file_list);
static DEFINE_SPINLOCK(hello_list_lock);
static DECLARE_WAIT_QUEUE_HEAD(hello_wait_data);
static DECLARE_WAIT_QUEUE_HEAD(hello_wait_all_file_close);

static char     hello_buffer[1024];
static size_t   hello_bufsize = 0;
static int      hello_dead = 0;

struct hello_file_data {
    struct list_head    entry;
    struct file *       owner_file;
    int                 data;
};

static int hello_open(struct inode * inode, struct file * file)
{
    struct hello_file_data * fdat;

    fdat = kzalloc(sizeof(struct hello_file_data), GFP_KERNEL);
    if (!fdat) {
        return -ENOMEM;
    }
    fdat->owner_file = file;
    fdat->data = 0;
    file->private_data = fdat;
    spin_lock(&hello_list_lock);
    list_add(&fdat->entry, &hello_opened_file_list);
    spin_unlock(&hello_list_lock);
    return 0;
}

static int hello_release(struct inode * inode, struct file * file)
{
    struct hello_file_data * fdat = file->private_data;
    int notify_module_exit = 0;

    spin_lock(&hello_list_lock);
    list_del(&fdat->entry);
    if (hello_dead && list_empty(&hello_opened_file_list)) {
        notify_module_exit = 1;
    }
    spin_unlock(&hello_list_lock);
    file->private_data = NULL;
    kfree(fdat);

    if (notify_module_exit) {
        wake_up(&hello_wait_all_file_close);
    }
    return 0;
}

static ssize_t hello_read(struct file * file, char __user * data, size_t n, loff_t * offset_p)
{
    int res;
    struct hello_file_data * fdat = file->private_data;

    res = wait_event_interruptible(hello_wait_data, hello_dead || fdat->data);
    if (res) {
        return res;
    }
    if (hello_dead) {
        return -ENODEV;
    }
    n = min(hello_bufsize, n);
    if (copy_to_user(data, hello_buffer, n)) {
        return -EFAULT;
    }
    fdat->data = 0;
    return n;
}

static ssize_t hello_write(struct file * file, const char __user * data, size_t n, loff_t * offset_p)
{
    struct hello_file_data * fdat = file->private_data;

    if (!n) {
        return 0;
    }
    n = min(sizeof(hello_buffer), n);
    if (copy_from_user(hello_buffer, data, n)) {
        return -EFAULT;
    }
    hello_bufsize = n;
    spin_lock(&hello_list_lock);
    list_for_each_entry(fdat, &hello_opened_file_list, entry) {
        fdat->data = 1;
    }
    spin_unlock(&hello_list_lock);
    wake_up_interruptible(&hello_wait_data);
    return n;
}

static struct file_operations hello_fops = {
    .open       = hello_open,
    .read       = hello_read,
    .write      = hello_write,
    .release    = hello_release,
};

static struct miscdevice hellodev = {
    .minor = MISC_DYNAMIC_MINOR,
    .name = "hello",
    .fops = &hello_fops,
};

static int hello_module_init(void)
{
    return misc_register(&hellodev);
}

static void hello_module_exit(void)
{
    misc_deregister(&hellodev);
    hello_dead = 1;
    wake_up_interruptible(&hello_wait_data);
    wait_event(hello_wait_all_file_close, ({
        int empty;
        spin_lock(&hello_list_lock);
        empty = list_empty(&hello_opened_file_list);
        spin_unlock(&hello_list_lock);
        empty;
    }));
}

module_init(hello_module_init);
module_exit(hello_module_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("xfan");
MODULE_DESCRIPTION("This is test driver");

最佳答案

这不是正确的做法。事实上,正确的方法对你来说要简单得多。内核构建框架与内核运行时加载器一起将为您的模块构建一个struct module(称为"THIS_MODULE")。您需要在 file_operations 结构的 .owner 槽中放置一个指向它的指针。这很容易做到:

static struct file_operations hello_fops = {
    .owner      = THIS_MODULE,        // <<<<<<========
    .open       = hello_open,
    .read       = hello_read,
    .write      = hello_write,
    .release    = hello_release,
};

它的工作方式是内核跟踪所有属于您的模块的打开文件(通过 owner 成员)。也就是说,每当打开设备实例时,它都会增加与您的模块关联的引用计数。当设备实例关闭时,您的 hello_release 被调用,然后模块引用计数递减。内核不允许您的模块在它仍然拥有打开的文件时被卸载,因此您需要在卸载您的模块之前追踪并终止任何持有文件引用的用户进程。这是可靠地执行此操作的唯一方法(听起来这就是您想要的)。

不幸的是,许多内核模块/驱动程序示例是在过去创建的,当时模块必须使用 try_module_getmodule_put 进行自己的引用计数,所以很多样本都没有解释这是如何工作的。尽管 get/put 机制存在竞争:模块本身确实不能可靠地引用计数。

如果你这样做,那么你就不必担心:当你的 module_exit 被调用时,你可以确信没有属于你的模块的打开的设备实例。

关于在 module_exit 上正确关闭结构文件 *,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53012935/

相关文章:

c - 如何在换行到达之前读取 tty 设备上的终端输入?

c++ - C函数将原始图像转换为png

c - 字符串文字导致小内存泄漏?

linux - 如何将linux或perl中特定列中的科学记数法更改为数字

Linux 内核设备驱动程序从设备到用户空间内存的 DMA

c - 如何在混杂模式下打开原始 UDP 套接字?

linux - 在 Makefile 中设置环境变量

c - Linux 内核 - 如何将 jprobe 与 kretprobe 匹配?

c - 从旧的 init_timer 到新的 timer_setup 的适配

Linux 多播 sendto() 性能因本地监听器而降低