深入理解linux设备驱动中的阻塞型I/O与非阻塞型I/O

来源:互联网 发布:fedora13 yum源 编辑:程序博客网 时间:2024/05/22 14:43
 

 转载请注明出处

    在linux驱动开发中,我们要讨论一个重要的问题,如果驱动程序无法及时满足进程的要求时,驱动程序应如何处理呢?例如,当数据不可以用时,用户可能调用read;或者进程试图写入数据,但因为输出缓冲区已经满了,设备还未准备接受数据。驱动程序该怎么处理这些情况呢?我们的驱动程序应该阻塞该进程,将此置入休眠状态,直到请求可以继续。

(一)休眠(sleep)的介绍

休眠(sleep对于进程而言,将进程标识为一种特殊的状态,并将其从调度器的运行队列中移走;直到某些情况下修改这个特殊状态,进程才回在任意CPU上调度,也即运行该进程。

linux驱动开发中,通过等待队列存放休眠的进程,当休眠进程被唤醒后,从等待队列中提取出休眠进程。一个等待队列通过一个等待队列头(wait queue head)来管理。实际上,等待队列就是进程链表,其中包含了等待某个特定事件的所有进程。

(1)    初始化等待队列头:

静态初始化:DECLARE_WAIT_QUEUE_HEAD(name);

动态初始化:wait_queue_head_t my_queue;

                      Init_waitqueue_head(&my_queue);

(2)    几种简单休眠的宏:

wait_event(queue, condition);

wait_event_interruptible(queue, condition);

wait_event_timeout(queue, conditon, timeout);

wait_event_interuptible_timeout(queue, conditon, timeout);

       上面所有形式中,queue是等待队列头,condition是休眠前后都要对其condition进行求值进行判断,如果条件满足,就立即退出休眠;interruptible形式的休眠是中断休眠;timeout是设置一种等待限定的时间,如果timeout达到时,立即唤醒进程。

(3)    唤醒休眠的函数:

void wake_up(wait_queue_head_t *queue);

void wake_up_interruptible(wait_queue_head_t *queue);

还有其它几种休眠唤醒函数,上面的唤醒函数已经够用了,其它几种形式可以google一下。

 

(二)休眠过程的具体分析

    首先列出wait_event(queue, condition)宏的代码,然后根据代码进行分析休眠过程。

#define wai_event(queue,condition)\

do{                                \

       if(condition) /*条件满足,立即返回*/   \ (1)

           break;                   \

    _wait_event(queue,condition);/*添加到等待队列*/ \

while0\

 

#define _wait_event(queue, conditon)     \

do{\

              DEFINE_WAIT(wait); /*建立并初始化一个等待队列*/  \(2)

       for(;;) \

{\

       prepare_to_wait(&queue, &wait, TASK_UINTERUPTIBLE);\(3)

              if(condition) /*条件满足,立即返回*/   \ (4)

           break;     \

    schedule();/*放弃CPU*/ \ (5)

}\

finish_wait(&queue, &wait); \ (6)

} while(0)\

 

void prepare_to_wait(wait_queue_head_t *queue, wait_queue_t *wait, int state)

{

       unsiged long flags;

       wait->flags &=~WQ_FLAG_EXCLUSIVE;(7)

       spin_lock_irqsave(&queue->lock, flags);(8)

       if(list_empty(&wait->task_list)) (9)

              _add_wait_queue(queue, wiat); /*加入等待队列*/(10)

       set_current_state(state); /*设置进程状态*/(11)

       spin_unlock_irqrestore(&queue->lock, flags);

 

void finish_wait(wait_queue_head_t *queue, wait_queue_t *wait) (12)

{

       unsigned long flags;

       _set_current_state(TASK_RUNNING);/*恢复进程状态TASK_RUNNING */

       if!list_empty_careful(&wait->task_list)

     {

            spin_lock_irqsave(&queue->lock, flags);

            list_del_init(&wait->task_list);

            spin_unlock_irqrestore(&queue->lock, flags);

      } 

 

休眠的具体操作过程

1.  首先要判断休眠条件是否满足,不满足立即返回,如标识(1)所示,在代码中你会看到多次判断休眠条件是否满足,因此这个判断条件中不能带来任何副作用。

2.  初始化一个等待队列wait_queue_t,并将其加入对应的等待队列中。这样不管谁负责唤醒他,都能找到正确的进程。在代码如(2)(3)。对应代码(3),就是调用prepare_to_wait函数,该函数不仅实现等待队列的添加而且实现了进程状态的设置,可以参考代码注释。prepare_to_wait函数中还有一个比较特殊的宏“WQ_FLAG_EXCLUSIVE”,设置标识位,主要防止爆发式唤醒和强夺资源,通过设置WQ_FLAG_EXCLUSIVE标识后,实现“独占等待”。而代码(7)没有设置了该宏的反向,也即没有实现“独占等待”。其中“独占等待”通过下面两个步骤实现的:(a)等待队列入口设置了“WQ_FLAG_EXCLUSIVE”后,将其加入等待队列的尾部,没有设置该标识的,加入头部。(b)唤醒wake_up时,唤醒第一设置WQ_FLAG_EXCLUSIVE的标识的进程,停止唤醒其它进程。

3.  让出CPU,如代码(5),该进程进入休眠状态。

4.  在代码(5)返回之后,完成一些清理工作,此时进程已经不在需要休眠,因此进程状态重新设置TASK_RUNNING.将进程从等待队列中移除。

 

(三)自己写休眠函数实现休眠操作

 

通过(二)的详细描述休眠过程,可以自己写休眠函数实现休眠操作。设置一个device,然后对device进行写操作,在此写操作中穿插休眠过程,可以自己尝试写该写操作的过程,并实现休眠过程。下面是我的写法,有不对之处,请指正(写操作的过程就省略了,主要实现休眠和唤醒过程)

 

static unsigned int device_write(struct file*file, const char *buffer, size_t count, loff_t *pos)

{

    ……

    ……

    DECLARE_WAITQUEUE(wait, current);/*定义等待队列*/

    add_wait_queue(&device_wait, &wait);/*添加等待队列*/

    while(!device_writeable(…))/*设备不可写*/

{

    __set_current_state(TASK_INTERRUPTIBLE);/改变进程状态/

    if(file->f_flags&O_NOBBLOCK)/*非阻塞I/O*/

{

    remove_wait_queue(&device&wait);/移除等待队列入口/

    set_current_state(TASK_RUNNING);/*恢复进程状态*/

    return –EAGAIN;

}

schedule();/放弃cpu/

if(signale_pending(current))

{

    remove_wait_queue(&device&wait);/移除等待队列入口/

    set_current_state(TASK_RUNNING);/*恢复进程状态*/

}

}

device_write(**);/进行设备写操作/

return count

 

 转载请注明出处

 

原创粉丝点击