Linux设备驱动中的阻塞和非阻塞IO

这篇文章我们来了解下Linux设备驱动中阻塞和非阻塞。

阻塞:阻塞是指执行设备操作时,如果不能获得设备资源,则挂起进程,是进程进入休眠模式,直到设备资源可以获取。

非阻塞:非阻塞是在不能获取设备资源时,要么放弃获取,要么一直不停的查询,直到可以获取资源。

这两种操作能够为为应用程序提供这样的能力:

(1)当应用程序对设备资源进行read(), write()操作时,如果设备资源不能获取,用户以阻塞的方式进行访问,则驱动程序在设备驱动的xxx_read(), xxx_write()等操作中将进程阻塞直到资源可以获取,之后应用程序的read(), write()的调用才能返回。

(2)如果应用程序进行上述操作时,用户以非阻塞的方式进行访问时,设备资源不能获取时,设备驱动的xxx_read(),xxx_write()等操作立即返回,应用程序的read(),write()等系统调用立即返回,应用程序受到-EAGAIN 返回值。

下面这幅图是Linux开发详解中的图,可以加深理解。

 使用例子:

fd = open("/dev/ttyS1", O_RDWR);  阻塞的形式;

fd = open("/dev/ttyS1", O_RDWR| O_NONBLOCK);  非阻塞的形式;

那么在Linux驱动程序中,是如何实现阻塞进程的唤醒呢?下面有几种方法使用:

(1)等待队列 ( wait queue),等待队列是以队列为基础数据结构实现的。

           1)使用方法:

首先定义等待队列的头部: wait_queue_head_t  my_queue;

初始化等待队列的头部:init_waitqueue_head(&my_queue); DECLARE_WAIT_QUEUE_HEAD() 宏是定义并初始化等待队列头部的快捷方式。

定义等待队列元素:DECLARE_WAITQUEUE(name, tsk) ,该宏用于定义并初始化一个名为name的等待队列元素。

添加/移除等待队列:void add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);
                                 void remove_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);

add_wait_queue()用于将等待队列元素wait添加到等待队列头部q指向的双向链表中。remove 函数执行移除操作。

等待事件:

     wait_event(queue, condition); 等待第一个参数queue作为等待队列头部的队列被唤醒,而且第二个参数condition必须满足,否则继续阻塞。
     wait_event_interruptible(queue, condition); 可被信号中断
     wait_event_timeout(queue, condition, timeout)   加了等待超时时间,超时时间到不论condition是否满足均返回。
    wait_event_interruptible_timeout(queue, condition, timeout)

 唤醒队列:void wake_up(wait_queue_head_t *queue);
                   void wake_up_interruptible(wait_queue_head_t *queue);

唤醒以queue作为等待队列头部的队列中所有的进程。

在等待队列上睡眠:

         sleep_on(wait_queue_head_t *q );  将目前进程的状态置成TASK_UNINTERRUPTIBLE,并定义一个等待队列元素,之后把它挂到等待队列头部q指向的双向链表,直到资源可获得,q队列指向链接的进程被唤醒。
         interruptible_sleep_on(wait_queue_head_t *q );目前进程的状态置成TASK_INTERRUPTIBLE,并定义一个等待队列
元素,之后把它附属到q指向的队列,直到资源可获得(q指引的等待队列被唤醒)或者进程收到信号。

有了前面的基本概念知识后,我们通过这段书上的代码来理解:

static ssize_t xxx_write(struct file *file, const char *buffer, size_t count,
 loff_t *ppos)
{

     DECLARE_WAITQUEUE(wait, current); /* 定义等待队列元素 */
     add_wait_queue(&xxx_wait, &wait); /* 添加元素到等待队列 */

 /* 等待设备缓冲区可写 */
     do {
         avail = device_writable(...);
         if (avail < 0) {
             if (file->f_flags &O_NONBLOCK) { /* 非阻塞 */
                 ret = -EAGAIN;
                 goto out;
             }
         __set_current_state(TASK_INTERRUPTIBLE); /* 改变进程状态 */
         schedule(); /* 调度其他进程执行 */
         if (signal_pending(current)) { /* 如果是因为信号唤醒 */
             ret = -ERESTARTSYS;
             goto out;
         }
    } while (avail < 0);

/* 写设备缓冲区 */
     device_write(...);
     out:
     remove_wait_queue(&xxx_wait, &wait); /* 将元素移出 xxx_wait 指引的队列 */
     set_current_state(TASK_RUNNING); /* 设置进程状态为 TASK_RUNNING */
     return ret;
}

1)如果是非阻塞访问(O_NONBLOCK被设置),设备忙时,直接返回“-EAGAIN”。
2)对于阻塞访问,会调用__set_current_state(TASK_INTERRUPTIBLE)进行进程状态切换并显示通过“schedule()”调度其他进程执行。
3)醒来的时候要注意,由于调度出去的时候,进程状态是TASK_INTERRUPTIBLE,即浅度睡眠,所以唤醒它的有可能是信号,因此,我们首先通过signal_pending(current)了解是不是信号唤醒的,如果是,立即返回“-ERESTARTSYS”。
非阻塞的实现有: select() 和poll() 。

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值