概念与术语
内核里面的一个队列,可以将对象加入到队列中,
可以选择条件休眠和无条件休眠,一般情况下是条件休眠用得较多。
作用:阻塞读。当运用程序读数据但是现在还无数据可读时,往往会先挂起,
让其他线程先走,等有数据可读时再唤醒。
那么什么时候唤醒呢?怎样唤醒呢?
这里等待队列就派上用场了。读的时候若无数据可读则加入到等待队列中,
写入的时候唤醒这个队列。
可以类比运用程序编程中的pthread_cond_wait()与pthread_cond_broadcast()记忆。
函数接口
include/linux/wait.h
1.初始化:
/*
* 输入参数: 队列头
*/
int init_waitqueue_head(&queue);
/*
* 输入参数: h:队列头
* w:等待队列节点
* 功能: 从等待队列中移除
*/
remove_wait_queue(wait_queue_head_t *h, wait_queue_t *w);
/*
* 输入参数: h:队列头
* w:等待队列节点
* 功能: 添加到等待队列中去
*/
add_wait_queue(wait_queue_head_t *h, wait_queue_t *w);
2.条件休眠函数:
这些函数内部封装了条件不满足调度其他线程的函数(如shedlu函数)的逻辑,
若不想用这些函数可以自己调用更加底层的函数完成这个逻辑:
1.判断条件是否满足;
2.__set_current_state(TASK_INTERRUPTABLE);标记当前线程挂起;
3.shedule(),调度其他进程;
4.等待,直到被唤醒。
/*
* wq:等待队列头
* condition:等待条件,满足则往下走,不满足则休眠
*/
wait_event(wq, condition)
/*
* 带超时的条件休眠
*/
wait_event_timeout(wq, condition, timeout)
/*
* 可中断的条件休眠
* 返回值: 成功:0 失败:负数
*/
wait_event_interruptible(wq, condition)
3.唤醒:
/*
* 输入参数:等待队列头
* 返回值: none
*/
void wake_up(struct __wait_queue *x)
/*
* 输入参数:等待队列头
* 返回值: none
*/
void wake_up_interruptible(struct __wait_queue *x)
工程实例
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <asm/current.h>
#include <linux/sched.h>
#include <linux/uaccess.h>
#include <asm/atomic.h>
#include <linux/mutex.h>
#include <linux/wait.h>
#include <linux/device.h>
static struct class *cls = NULL; //设备类,为自动创建设备
static int major = 0;
static int minor = 0;
const int count = 6;
#define DEVNAME "demo"
static struct cdev *demop = NULL; //字符型设备句柄,为自动创建设备
static atomic_t tv; //定义一个原子操作
static wait_queue_head_t wq; //定义一个等待队列
static int counter = 0;
//打开设备
static int demo_open(struct inode *inode, struct file *filp)
{
//get major and minor from inode
printk(KERN_INFO "(major=%d, minor=%d), %s : %s : %d\n",
imajor(inode), iminor(inode), __FILE__, __func__, __LINE__);
/*这个判断确保了设备首次打开不会报错,但不能被重复打开*/
if(!atomic_dec_and_test(&tv)){ //原子操作,atomic_dec_and_test减一并判断是否为0
atomic_inc(&tv); //原子操作,加一
return -EBUSY;
}
return 0;
}
//关闭设备
static int demo_release(struct inode *inode, struct file *filp)
{
//get major and minor from inode
printk(KERN_INFO "(major=%d, minor=%d), %s : %s : %d\n",
imajor(inode), iminor(inode), __FILE__, __func__, __LINE__);
atomic_inc(&tv); //因为打开时(见上)if条件判断中对其减了1,故这里要加1,才能确保为原来的初始值1
return 0;
}
//读设备
//ssize_t read(int fd, void *buf, size_t count)
static ssize_t demo_read(struct file *filp, char __user *buf, size_t size, loff_t *offset)
{
int err = 0;
struct inode *inode = filp->f_path.dentry->d_inode; //为imajor准备好参数
//get major and minor from inode
printk(KERN_INFO "(major=%d, minor=%d), %s : %s : %d\n",
imajor(inode), iminor(inode), __FILE__, __func__, __LINE__);
if(!counter){
if(filp->f_flags & O_NONBLOCK){ //filp->f_flags中有标志位可以知道是否为阻塞模式
return -EAGAIN; //如果无东西可读,且为非阻塞模式则拒绝本次读,下次再试,
//若不这样做他不会像普通read那样阻塞等,阻塞读时就无需如此
}
err = wait_event_interruptible(wq, (0 != counter));//若“counter不为0”这个条件成立,则返回值为0且往下走,否则阻塞在此处(但这种阻塞状态可以被中断)
if(err){
return err; //若counter为0,则上一条返回的是非零值,执行此语句,返回错误码
}
}
//...
counter = 0; //读完清零,模拟缓冲区已被读空
return 0;
}
//写设备
static ssize_t demo_write(struct file *filp, const char __user *buf, size_t size, loff_t *offset)
{
struct inode *inode = filp->f_path.dentry->d_inode;
//get major and minor from inode
printk(KERN_INFO "(major=%d, minor=%d), %s : %s : %d\n",
imajor(inode), iminor(inode), __FILE__, __func__, __LINE__);
counter = 10; //模拟“有东西写入了”这个事件
wake_up_interruptible(&wq);//唤醒等待队列,以便等待着醒来读取
return 0;
}
static struct file_operations fops = {
.owner = THIS_MODULE,
.open = demo_open,
.release= demo_release,
.read = demo_read,
.write = demo_write,
};
static int __init demo_init(void)
{
dev_t devnum;
int ret, i;
struct device *devp = NULL;
//get command and pid
printk(KERN_INFO "(%s:pid=%d), %s : %s : %d\n",
current->comm, current->pid, __FILE__, __func__, __LINE__);
//1. alloc cdev obj
demop = cdev_alloc();
if(NULL == demop){
return -ENOMEM;
}
//2. init cdev obj
cdev_init(demop, &fops);
ret = alloc_chrdev_region(&devnum, minor, count, DEVNAME);//动态申请设备号
if(ret){
goto ERR_STEP;
}
major = MAJOR(devnum);
//3. register cdev obj
ret = cdev_add(demop, devnum, count);
if(ret){
goto ERR_STEP1;
}
cls = class_create(THIS_MODULE, DEVNAME); //创建设备类,自动创建设备
if(IS_ERR(cls)){
ret = PTR_ERR(cls);
goto ERR_STEP1;
}
for(i = minor; i < (count+minor); i++){
devp = device_create(cls, NULL, MKDEV(major, i), NULL, "%s%d", DEVNAME, i);//创建设备
if(IS_ERR(devp)){
ret = PTR_ERR(devp);
goto ERR_STEP2;
}
}
// init atomic_t
atomic_set(&tv, 1); // 初始化原子操作,设置原子量的初值为1
init_waitqueue_head(&wq); //初始化等待队列
//get command and pid
printk(KERN_INFO "(%s:pid=%d), %s : %s : %d - ok.\n",
current->comm, current->pid, __FILE__, __func__, __LINE__);
return 0;
ERR_STEP2:
for(--i; i >= minor; i--){
device_destroy(cls, MKDEV(major, i));
}
class_destroy(cls);
ERR_STEP1:
unregister_chrdev_region(devnum, count);
ERR_STEP:
cdev_del(demop);
//get command and pid
printk(KERN_INFO "(%s:pid=%d), %s : %s : %d - fail.\n",
current->comm, current->pid, __FILE__, __func__, __LINE__);
return ret;
}
static void __exit demo_exit(void)
{
int i;
//get command and pid
printk(KERN_INFO "(%s:pid=%d), %s : %s : %d - leave.\n",
current->comm, current->pid, __FILE__, __func__, __LINE__);
for(i=minor; i < (count+minor); i++){
device_destroy(cls, MKDEV(major, i));
}
class_destroy(cls);
unregister_chrdev_region(MKDEV(major, minor), count);
cdev_del(demop);
}
module_init(demo_init);
module_exit(demo_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Farsight");
MODULE_DESCRIPTION("Demo for kernel module");
内核驱动源码中,支持多路复用机制的poll回调函数实现及等待队列实现请参考:
kernel_xilinx_v4.4/drivers/net/macvtap.c
与等待队列相关的非阻塞读机制(io多路复用)
见另外一篇博客,专讲select、poll、epoll,即与本博客对应的应用层:
io多路复用