Я учусь писать драйвер для 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_data
файла data
не станет 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_data
каждого файла значение * 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;
}
У меня есть три загадки с кодом
- Когда вызывается module_exit, я должен ждать, пока все
struct file *
будут закрыты правильно, уместно ли использовать wait_event?
- Когда этот модуль ядра будет удален с помощью rmmod, у меня нет возможности закрыть (отсоединиться?)
struct file *
, поэтому будет блокироваться команда rmmod, пока эти файлы не будут закрыты программой; есть ли лучший способ справиться с этим?
- При повторении всех
struct file *
, есть ли способ использовать API ядра вместо управления моим собственным связанным списком?
hello.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");