Linux设备驱动之阻塞与非阻塞---等待队列

废话一堆

以前由于项目需要,临时做过windows的虚拟串口驱动,当时只实现了write,没有实现read(不需要read)。当时不知道如何实现read操作,因为不知道设备什么时候才会有数据,我驱动中该什么时机读呢?难道我read程序中要写一个while循环,一直去读取吗?
现在才明白,可以使用操作系统中的阻塞和非阻塞原理解决上面的问题。

开始正题

这篇文章主要介绍阻塞的驱动实现。

这里写图片描述

如上图,在write和read等IO操作中,有两种实现方法,阻塞与非阻塞:

阻塞

在进行IO操作时,如果获取不到相关的资源,当前的进程会被挂起,进入睡眠,当资源可以被使用时,程序被唤醒,继续IO操作。一般是在中断中唤醒被挂起的进程。

非阻塞

在进行IO操作时,如果获取不到相关的资源,程序会立刻返回,返回值为-EAGAIN。

阻塞的实现

在Linux中,主要通过等待队列实现阻塞的IO操作。

等待队列的使用
  1. 定义初始化等待队列
wait_queue_head_t my_queue;      //定义等待队列头
init_waitqueue_head(&my_queue);  //初始化等待队列头

DECLARE_WAIT_QUEUE_HEAD(my_queue);  //快捷方式,定义并且初始化等待队列头
  1. 触发等待事件
wait_event(queue, condition)  //不可被中断打断
wait_event_interruptible(queue, condition)   //可被中断打断
wait_event_timeout(queue, condition, timeout)  //如果一段时间后没有被唤醒,就自动唤醒,也就是超时机制。
wait_event_interruptible_timeout(queue, condition, timeout)
  1. 唤醒
void wake_up(wait_queue_head_t *queue);
void wake_up_interruptible(wait_queue_head_t *queue);

需要说明一点,即使wake_up唤醒休眠的进程,如果condition为false,依然会继续阻塞。

等待队列示例

此示例是在按键中断驱动的基础上修改而来,在驱动初始化结束后,开始阻塞,进程被挂起,当按下按键,中断程序触发,设置condition为true,并且唤醒进程。一共10次循环。

#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/delay.h>
#include <linux/time.h>
#include <linux/input.h>
#include <linux/init.h>
#include <linux/gpio.h>

#include <linux/sched.h>

#include <asm/io.h>
#include <asm/irq.h>

#define IMX_GPIO_NR(bank, nr)               (((bank) - 1) * 32 + (nr))  //平台相关
#define CYNO_GPIO_BEEP_NUM                  IMX_GPIO_NR(6,10)  //自己使用引脚,由beep引脚改的,所以名称叫做beep

DECLARE_WAIT_QUEUE_HEAD(wq);     //定义并且初始化等待队列头
static int condition = 0; 

//定义gpio引脚的结构体
static struct pin_desc{
    int irq;
    unsigned char *name;
    unsigned int pin;
};

//实例化一个具体的引脚
static struct pin_desc beep_desc = {
    0,
    "beep_num",
    CYNO_GPIO_BEEP_NUM
};

//中断触发函数
static irqreturn_t beep_interrupt_handler(int irq, void *dev_id)
{
    printk("%s\n", __func__);
    condition = 1;   //设置唤醒条件为真
    wake_up_interruptible(&wq);     //唤醒被挂起的进程
    return IRQ_HANDLED;
}



static int wait_queue_init(void)
{
    int ret, count;

    printk(KERN_INFO "%s\n", __func__);
    //请求gpio
    if(gpio_request(beep_desc.pin ,beep_desc.name)){
        printk(KERN_ERR "%s : request gpio %d error\n", __func__, beep_desc.pin);
        goto err_gpio_request;
    }
    //设置按键为输入
    gpio_direction_input(beep_desc.pin);
    //动态获取irq中断号
    beep_desc.irq = gpio_to_irq(beep_desc.pin);
    printk(KERN_INFO "%s : the irq num is %d\n", __func__, beep_desc.irq);
    //申请终端,并且设置中断触发函数,中断触发方式,私有数据等
    ret = request_irq(beep_desc.irq, beep_interrupt_handler , IRQF_TRIGGER_FALLING  | IRQF_ONESHOT, beep_desc.name , &beep_desc);
    if(ret){
        printk(KERN_ERR "%s : request_irq is error\n", __func__);
        goto err_request_irq;
    }
    printk("%s : init end\n", __func__);

    count = 10;

    while(count--){
        wait_event_interruptible(wq,condition);     //开始阻塞
        printk(KERN_INFO "wake up count = %d\n", 10 - count);
        condition = 0;
    }

    return 0;

err_request_irq:
    free_irq(beep_desc.irq, &beep_desc);

err_gpio_request:
    gpio_free(beep_desc.pin);
    return -1;

    return 0;
}

static void wait_queue_exit(void)
{
    printk(KERN_INFO "%s\n", __func__);
    free_irq(beep_desc.irq, &beep_desc);
    gpio_free(beep_desc.pin);
}


module_init(wait_queue_init);
module_exit(wait_queue_exit);

MODULE_AUTHOR("xiaolei");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("wait_queue example");

结果如下:
这里写图片描述

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要让字符设备驱动支持阻塞阻塞IO模型,需要在驱动程序中使用select/poll机制,同时使用文件操作的O_NONBLOCK标志。下面以read操作为例,简要说明如下: 1. 在设备驱动程序中,使用`file_operations`结构体中的`read`函数实现读操作,同时在该函数中添加对阻塞阻塞IO模型的支持。 2. 对于阻塞IO模型,可以直接在`read`函数中调用`wait_event_interruptible`函数使进程进入睡眠状态,等待数据就绪后再唤醒进程。当然在等待数据就绪的过程中,如果进程接收到了信号,则需要立即返回`-ERESTARTSYS`。 3. 对于阻塞IO模型,需要在`read`函数中使用`O_NONBLOCK`标志进行判断。如果该标志被设置,则可以直接调用`poll_wait`函数等待数据就绪,如果数据没有准备好,则直接返回`-EAGAIN`。 4. 在`poll`函数中,需要添加对于设备文件的监控,以便在数据就绪时通知进程。这可以通过在驱动程序中添加`poll`函数来实现。在该函数中,需要使用`poll_wait`函数将当前进程添加到等待队列中,并在数据就绪时唤醒进程。 下面是一个简单的代码示例: ```c static DECLARE_WAIT_QUEUE_HEAD(read_wait_queue); static unsigned int mydevice_poll(struct file *file, poll_table *wait) { unsigned int mask = 0; struct mydevice_data *data = file->private_data; poll_wait(file, &read_wait_queue, wait); if (data->data_ready) { mask |= POLLIN | POLLRDNORM; } return mask; } static ssize_t mydevice_read(struct file *file, char __user *buf, size_t count, loff_t *ppos) { struct mydevice_data *data = file->private_data; ssize_t ret = 0; if (file->f_flags & O_NONBLOCK) { /* 阻塞IO模型 */ if (!data->data_ready) { return -EAGAIN; } } else { /* 阻塞IO模型 */ wait_event_interruptible(read_wait_queue, data->data_ready); if (signal_pending(current)) { return -ERESTARTSYS; } } /* 读取数据 */ if (copy_to_user(buf, data->buffer, data->size)) { ret = -EFAULT; } else { ret = data->size; data->data_ready = 0; } return ret; } ``` 在上面的代码示例中,`mydevice_read`函数是`read`操作对应的函数,`mydevice_poll`函数是`poll`操作对应的函数。其中,`wait_event_interruptible`函数用于阻塞进程,等待数据就绪;`poll_wait`函数用于将进程添加到等待队列中,等待数据就绪时唤醒进程。同时,根据文件操作的O_NONBLOCK标志,判断当前使用的是阻塞IO模型还是阻塞IO模型。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值