linux驱动---等待队列

概念与术语

内核里面的一个队列,可以将对象加入到队列中,
可以选择条件休眠和无条件休眠,一般情况下是条件休眠用得较多。
作用:阻塞读。当运用程序读数据但是现在还无数据可读时,往往会先挂起,
让其他线程先走,等有数据可读时再唤醒。
那么什么时候唤醒呢?怎样唤醒呢?
这里等待队列就派上用场了。读的时候若无数据可读则加入到等待队列中,
写入的时候唤醒这个队列。
可以类比运用程序编程中的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多路复用

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

xxgui1992

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值