文章目录
- 阻塞 IO(BIO)
- 模型
- 等待队列头
- init_waitqueue_head
- DECLARE_WAIT_QUEUE_HEAD
- 等待队列项
- 使用方法
- 驱动程序
- 应用程序
- 模块使用
- 参考
阻塞 IO(BIO)
模型
等待队列是内核实现阻塞和唤醒的内核机制。
等待队列以循环链表为基础结构,链表头和链表项分别为等待队列头
和等待队列元素
。
整个等待队列由等待队列头进行管理。
等待队列头
等待队列头使用结构体 wait_queue_head_t
,定义在 linux/wait.h
,结构体原型如下:
struct wait_queue_head {spinlock_t lock;struct list_head head;
};
typedef struct wait_queue_head wait_queue_head_t;
等待队列头的初始化有两种方法
init_waitqueue_head
- 定义一个等待队列头
wait_queue_head_t read_wait
- 等待 队列头初始化
init_waitqueue_head(&read_wait)
DECLARE_WAIT_QUEUE_HEAD
DECLARE_WAIT_QUEUE_HEAD
宏一次性完成等待队列头的定义和初始化
等待队列项
等待队列项使用结构体 struct wait_queue_entry
,定义在 linux/wait.h
,结构体原型如下:
struct wait_queue_entry {unsigned int flags;void *private;wait_queue_func_t func;struct list_head entry;
};
使用方法
- 初始化等待队列头,并将条件设置为假的(condition=0)
- 在需要阻塞的地方调用
wait_event
或者wait_event_interruptible
,使进程进入休眠 - 当条件满足时,需要解除休眠,先将条件设置为真(condition=1),然后调用
wake_up
或者wake_up_interruptible
函数唤醒等待队列中的休眠进程。
驱动程序
#include "linux/device/class.h"
#include "linux/export.h"
#include "linux/uaccess.h"
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/cdev.h>
#include <linux/wait.h>#define CHRDEVBASE_NAME "chrdevbase" /* 设备名 */
#define CHRDEVBASE_NUM 1 /* 设备数目 */static char *string_test = "kernel data this tyustli test";typedef struct {dev_t dev_id; /* 设备号 */struct cdev c_dev; /* cdev */struct class *class; /* 类 */struct device *device; /* 设备 */int major; /* 主设备号 */int minor; /* 次设备号 */int flag; /* read flag */char write_buf[100];char read_buf[100];wait_queue_head_t read_wait; /* 读等待队列头 */
} new_chrdev_t;static new_chrdev_t new_chrdev1;static int chrdevbase_open(struct inode *inode, struct file *file)
{file->private_data =container_of(inode->i_cdev, new_chrdev_t, c_dev); /* 设置私有数据 */printk("k: chrdevbase open\r\n");return 0;
}static ssize_t chrdevbase_read(struct file *file, char __user *buf,size_t count, loff_t *ppos)
{unsigned long ret = 0;new_chrdev_t *dev = (new_chrdev_t *)file->private_data;wait_event_interruptible(dev->read_wait, dev->flag);memcpy(dev->read_buf, string_test, strlen(string_test));ret = copy_to_user(buf, dev->read_buf, count);if (ret == 0) {printk("k: read data success\r\n");} else {printk("k: read data failed ret = %ld\r\n", ret);}return ret;
}static ssize_t chrdevbase_write(struct file *file, const char __user *buf,size_t count, loff_t *ppos)
{unsigned long ret = 0;new_chrdev_t *dev = (new_chrdev_t *)file->private_data;ret = copy_from_user(dev->write_buf, buf, count);if (ret == 0) {printk("k: write data success write data is: %s\r\n", dev->write_buf);} else {printk("k: write data failed ret = %ld\r\n", ret);}dev->flag = 1;wake_up_interruptible(&dev->read_wait);return count;
}static int chrdevbase_release(struct inode *inode, struct file *file)
{printk("k: chrdevbase release\r\n");return 0;
}static struct file_operations chrdevbase_fops = {.owner = THIS_MODULE,.open = chrdevbase_open,.read = chrdevbase_read,.write = chrdevbase_write,.release = chrdevbase_release,
};static int __init chrdevbase_init(void)
{int err = 0;err = alloc_chrdev_region(&new_chrdev1.dev_id, 0, CHRDEVBASE_NUM,CHRDEVBASE_NAME);if (err < 0) {printk("k: alloc chrdev region failed err = %d\r\n", err);goto err_chrdev;}/* get major 1 and minor 1 */new_chrdev1.major = MAJOR(new_chrdev1.dev_id);new_chrdev1.minor = MINOR(new_chrdev1.dev_id);printk("k: newcheled major=%d,minor=%d\r\n", new_chrdev1.major,new_chrdev1.minor);new_chrdev1.c_dev.owner = THIS_MODULE;cdev_init(&new_chrdev1.c_dev, &chrdevbase_fops);err = cdev_add(&new_chrdev1.c_dev, new_chrdev1.dev_id, 1);if (err < 0) {printk("k: cdev add failed err = %d\r\n", err);goto err_cdev_add;}new_chrdev1.class = class_create("chr_test1");if (IS_ERR(new_chrdev1.class)) {err = PTR_ERR(new_chrdev1.class);goto err_class_create;}new_chrdev1.device = device_create(new_chrdev1.class, NULL,new_chrdev1.dev_id, NULL, "chr_test1");if (IS_ERR(new_chrdev1.device)) {err = PTR_ERR(new_chrdev1.device);goto err_device_create;}/* 初始化等待队列头 */init_waitqueue_head(&new_chrdev1.read_wait);new_chrdev1.flag = 0;printk("k: base module init\r\n");return 0;err_device_create:class_destroy(new_chrdev1.class);
err_class_create:cdev_del(&new_chrdev1.c_dev);
err_cdev_add:unregister_chrdev_region(new_chrdev1.dev_id, CHRDEVBASE_NUM);
err_chrdev:return err;
}static void __exit chrdevbase_exit(void)
{device_destroy(new_chrdev1.class, new_chrdev1.dev_id);class_destroy(new_chrdev1.class);cdev_del(&new_chrdev1.c_dev);unregister_chrdev_region(new_chrdev1.dev_id, CHRDEVBASE_NUM);printk("k: base module exit!\r\n");
}module_init(chrdevbase_init);
module_exit(chrdevbase_exit);MODULE_LICENSE("GPL");
MODULE_AUTHOR("tyustli");
MODULE_INFO(intree, "Y"); /* loading out-of-tree module taints kernel */
应用程序
应用程序使用 O_RDWR
默认以阻塞的方式打开
#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"static char usrdata[] = { "user data!" };int main(int argc, char *argv[])
{int fd, retvalue;char *filename;char readbuf[100], writebuf[100];if (argc != 3) {printf("u: error Usage!\r\n");return -1;}filename = argv[1];/* 打开驱动文件 */fd = open(filename, O_RDWR);if (fd < 0) {printf("u: can't open file %s\r\n", filename);return -1;}/* 从驱动文件读取数据 */if (atoi(argv[2]) == 1) {retvalue = read(fd, readbuf, 50);if (retvalue < 0) {printf("u: read file %s failed!\r\n", filename);} else {/* 读取成功,打印出读取成功的数据 */printf("u: read data:%s\r\n", readbuf);}}/* 向设备驱动写数据 */if (atoi(argv[2]) == 2) {memcpy(writebuf, usrdata, sizeof(usrdata));retvalue = write(fd, writebuf, 50);if (retvalue < 0) {printf("u: write file %s failed!\r\n", filename);}}/* 关闭设备 */retvalue = close(fd);if (retvalue < 0) {printf("u: can't close file %s\r\n", filename);return -1;}return 0;
}
模块使用
模块安装
modprobe my_module
查看设备节点
ls /dev
~ # ls /dev/
chr_test1 ptypc tty32 tty7
console ptypd tty33 tty8
cpu_dma_latency ptype tty34 tty9
full ptypf tty35 ttyAMA0
gpiochip0 random tty36 ttyAMA1
gpiochip1 root tty37 ttyAMA2
gpiochip2 rtc0 tty38 ttyAMA3
gpiochip3 snd tty39 ttyp0
hwrng tty tty4 ttyp1
input tty0 tty40 ttyp2
kmsg tty1 tty41 ttyp3
模块使用
~ # /lib/modules/6.5.7+/my_app /dev/chr_test1 1 &
~ # k: chrdevbase open~ # /lib/modules/6.5.7+/my_app /dev/chr_test1 2
k: chrdevbase open
k: write data success write data is: user data!
k: read data success
k: chrdevbase release
~ # u: read data:kernel data this tyustli test
k: chrdevbase release[1]+ Done /lib/modules/6.5.7+/my_app /dev/chr_test1 1
参考
- https://juejin.cn/post/7129070726249709599#heading-0