I'm learning how to write linux driver, but I have some confusion about how to use kernel API correctly and elegantly. I try to write a simple misc driver, it creates a device node on /dev/hello. In userspace, readers can read on the device and block until writers write some data on the device, all readers get the data written. If new data comes before the reader reads the old data, the reader will lose the old data.
To implement it, I create a struct hello_file_data for every opened file and put them on a global link-list, the data field indicates there are some data that can be read.
static LIST_HEAD(hello_opened_file_list);
struct hello_file_data {
struct list_head entry;
struct file * owner_file;
int data;
};
In the read function, I use wait_event_interruptible to block this thread and wait for the file's struct hello_file_data's data field become 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;
}
In the write function, I iterate the global linked-list to set every file's struct hello_file_data's data field to 1 then notify all the reader threads to wakeup.
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;
}
I have three puzzles on the code
- When module_exit is called, I must wait until all
struct file *are closed correctly, is it appropriate to use wait_event? - When this kernel module will be removed by rmmod, I have no way to close(detach?)
struct file *, so will the rmmod command block until these files are closed by program; is there a better way to handle it? - When iterating all
struct file *, is there a way to use a kernel API rather than managing my own linked-list?
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");