曲径通幽论坛

 找回密码
 立即注册
搜索
查看: 8985|回复: 4
打印 上一主题 下一主题

阻塞型输入输出

[复制链接]

4918

主题

5880

帖子

3万

积分

GROAD

曲径通幽,安觅芳踪。

Rank: 6Rank: 6

积分
34397
跳转到指定楼层
楼主
发表于 2009-10-23 23:45:03 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
阻塞型输入输出是指硬件没有结束处理时,暂停进程并进入等待的过程。为了处理这种情况,设备驱动程序引入了“等待队列”(waiting queue)的概念。

使应用程序进入睡眠的简单方法是使用 sleep() 函数,另外使用 select() 或 poll() 函数在一定时间内,或直到感兴趣的事件发生,进程也会保持睡眠状态。

除了上述强行使进程进入睡眠的方法外,进程自动等待外部状态变化的情况称为阻塞型输入输出。在运行阻塞型输入输出时,读/写设备文件的设备驱动程序应使进程进入睡眠。

为了提高处理器的使用效率,我们会结合中断来对输入输出进行处理,因此中断是一种使输入输出同步的一种处理机制。当在应用程序里读取硬件时,而硬件却还没准备好相应的数据,也就是说如果在这个设备上利用了中断机制,那么这个中断还未到来;从设备驱动程序的角度上看,是还没有发生相应的输入或输出条件,这时就要使处理器进入睡眠状态。

读取设备文件时,因为读不到数据而产生进程中止的现象称为“阻塞化”(blocking)。如果在应用程序中的 open() 函数里附加使用了 O_NDELAY 属性,那么在没有可读数据时,进程不会被阻塞。这样的事情好比如,我在回家的路上被塞车了,我可以选择继续坐在车里等车流顺畅(阻塞),或者是因为我性子比较急我要下车走路回去(O_NDELAY,非阻塞)。

4918

主题

5880

帖子

3万

积分

GROAD

曲径通幽,安觅芳踪。

Rank: 6Rank: 6

积分
34397
沙发
 楼主| 发表于 2009-10-24 00:57:21 | 只看该作者

阻塞模式和进程的处理过程

设备驱动程序控制进程睡眠状态的过程如下:

( 1 )  进程使用不包含 O_NDELAY 属性的 open() 函数打开设备文件。

( 2 )  进程通过系统调用来映射设备文件 ( 如使用 read() 来读取数据 ) 。

( 3 ) 进程调出设备驱动程序后,首先检查是否存在可读数据(假设用read()),若存在则将数据传递到进程空间,若不存在则由硬件控制进入睡眠,直到有可读的数据为止(中断发生)。但是,唤醒因设备驱动程序而进入的睡眠的进程,并不一定能立即运行该进程!

( 4 ) 唤醒进程,但不能立即运行的一个很最大的原因是,当前正在运行的进程还没消耗完系统分配给它的时间。进程调度程序要要把时间分配到其他进程上,那么就需要进行调度。此时,通过优先顺序,运行因设备驱动程序而进入睡眠的进程。

( 5 ) 现在,因为 read() 而睡眠的进程被唤醒并可以运行了,它开始处理后面相应的事务。

4918

主题

5880

帖子

3万

积分

GROAD

曲径通幽,安觅芳踪。

Rank: 6Rank: 6

积分
34397
板凳
 楼主| 发表于 2009-11-3 10:37:46 | 只看该作者

等待队列与 wait_queue_head_t 结构体

为了管理睡眠和唤醒的条件,并管理进程的映射,设备驱动使用了等待队列 ( waiting queue ) ,等待队列里面的主体是进程。

等待队列的变量由结构体 wait_queue_head_t 结构体定义,wait_queue_head_t 结构体在 linux/wait.h 头文件中定义,具体如下:
struct __wait_queue_head {
        spinlock_t lock;
        struct list_head task_list;
};
typedef struct __wait_queue_head wait_queue_head_t;
lock 是个自旋锁;
strcut list_head 结构体在 linux/list.h 中有定义:
struct list_head {
       struct list_head *next, *prev;
  };

等待队列结构如下图所示:


使用结构体变量有两种方式:
方式一
wait_queue_head_t   wait_queue;          /*定义等待队列变量*/
init_waitqueue_head (&wait_queue);    /*初始化等待丢列*/

方式二
DECLARE_WAIT_QUEUE_HEAD(wait_queue);

通常,使用全局变量定义等待队列。由什么样的事件确定创建什么样的等待队列。因此,设备驱动程序显示与读写想关的阻塞模型时,就要分别定义两种队列:
DECLARE_WAIT_QUEUE_HEAD(ReadWaitQueue);
DECLARE_WAIT_QUEUE_HEAD(WriteWaitQueue);

定义了上面两个等待队列,如果想使读取进程进入睡眠状态,就使用 ReadWaitQueue 变量,要想使写入进程进入睡眠状态,就使用 WriteWqitQueue 变量。唤醒过程也是如此。

使用 wait_queue_head_t 以及想关宏需要包含 #include <linux/wait.h> 头文件。

init_waitqueue_head() 函数见:http://www.groad.net/bbs/read.php?tid-1332.html

4918

主题

5880

帖子

3万

积分

GROAD

曲径通幽,安觅芳踪。

Rank: 6Rank: 6

积分
34397
地板
 楼主| 发表于 2009-11-3 14:56:13 | 只看该作者

进程的睡眠与唤醒

设备驱动程序为了控制进程进入睡眠状态使用 interruptible_sleep_on() 函数,该函数的变量是等待队列的变量。使进程进入睡眠状态的结构如下:
DECLARE_WAIT_QUEUE_HEAD(WaitQueue_Read);   /*定义等待队列变量*/
.....
interruptible_sleep_on(&WaitQueue_Read);

等待队列变量通常会定义为全局变量,设备驱动程序使用 DECLARE_WAIT_QUEUE_HEAD 宏来对其声明定义。

interruptible_sleep_on() 函数在等待队列变量中注册当前进程的信息,从而使进程进入睡眠状态。然后,调度程序立即向其他进程分配处理器。

唤醒因 interruptible_sleep_on() 进入睡眠状态的进程时,使用 wake_up_interruptible() 函数,该函数的参数和 interruptible_sleep_on() 的参数一样:
wake_up_interruptible(&WaitQueue_Read);

上面,需要注意的是,wake_up_interruptible() 把 WaitQueue_Read 中注册的进程作为下一个时间表中运行的对象,但这并不意味着运行了函数进程就马上被唤醒,这还得依赖于调度程序。另外,若 WaitQueue_Read 中并没有注册进程,那也不会影响该函数的运行,所以不必考虑条件,可自由使用该函数。

还需要注意的是,设备驱动程序并不是可以调用 interruptible_sleep_on() 随意让进程进入睡眠,这还得需要检查两个条件:
( 1 ) 是否需要等待;
( 2 ) 在等待时,设备文件是否以非阻塞模式(nonblocking) 打开。若是,则不能使用 interruptible_sleep_on() 让进程进入睡眠,此时返回 EAGAIN ,并向应用程序请求重新运行。
if (IsWait()) {
    if (!(filp->f_flags & O_NONBLOCK)) {
         interruptible_sleep_on(&WaitQueue);
    } else {
                    return (-EAGAIN);
    }
}
上面,IsWait() 是自定义一个函数,不是内核提供的函数。

interruptible_sleep_on() 可能会使进程进入无休止的睡眠状态。有时,有必要对其进行超时处理,这是用 interruptible_sleep_on_timeout() 来代替 interruptible_sleep_on()。

用法如下:
interruptible_sleep_on_timeout (&WaitQueue, 10);

其中 10 表示给定的睡眠时间,单位为 Hz 。在 2.6 内核中,定 1s 的 Hz 为 1000,即单位时间为 0.001s ,睡眠时间应该是 0.01s 。

关于 interruptible_sleep_on() , interruptible_sleep_on_timeout(), wake_up_interrutible() 的说明见:
http://www.groad.net/bbs/read.php?tid-1333.html
http://www.groad.net/bbs/read.php?tid-1335.html
http://www.groad.net/bbs/read.php?tid-1336.html

4918

主题

5880

帖子

3万

积分

GROAD

曲径通幽,安觅芳踪。

Rank: 6Rank: 6

积分
34397
5#
 楼主| 发表于 2009-11-7 16:46:56 | 只看该作者
sleep_on() , sleep_on_timeout() 以及 interruptible_sleep_on() 几个接口,尽管在 2.6 内核中仍得到支持,但在将来的 2.7 内核里会被移除,而用 wait_event*() 来取代 sleep_on*() 。

wait_event*() 接口包含:
wait_event(),   wait_event_interruptible() ,   wait_event_interruptible_timeout() 。

下图是几个函数的调用示意图:

要使用 wait_event_interruptible() 宏,在 2.6 内核里要包含 <linux/wait.h> 头文件;在 2.4 内核里要包含 <linux/sched.h> 头文件。wait_event_interruptible() 的格式为:
wait_event_interruptible(wq, condition);
wq 为等待队列;
condition 表示释放 wait_event_interruptible() 的条件。注意,由于宏是替换性结构,所以 conditon 不是变量,可以是个逻辑表达式,这由 wait_event_interruptible() 的定义也可以看到:
#define wait_event_timeout(wq, condition, timeout)                      \\
({                                                                      \\
        long __ret = timeout;                                           \\
        if (!(condition))                                               \\
                __wait_event_timeout(wq, condition, __ret);             \\
        __ret;                                                          \\
})
如下应用:
DECLARE_WAIT_QUEUE_HEAD(WaitQueue_Read);
... ...
wait_event_interruptible(WaitQueue_Read, count>30);
上面,把当前进程注册到 WaitQueue_Read 等待队列上,并进入睡眠状态,然后,中断服务函数使用 wake_up_interruptible() 唤醒 &WaitQueue_Read 等待队列上注册的进程,若 count 的值小于 30 ,就会重新进入睡眠状态。由 __wait_event_timeout() 可知,它是一个无限循环 ( for ( ; ; ) ),一旦检测到 condition 为真,才会 break 出循环,停止等待。

此外,还有一个 wait_event_interruptible_timeout() ,其结构为:
wait_event_interruptible_timeout(wq, condition, timeout);
最后一个参数 timeout 是超时设定。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Archiver|曲径通幽 ( 琼ICP备11001422号-1|公安备案:46900502000207 )

GMT+8, 2025-6-18 00:04 , Processed in 0.070154 second(s), 22 queries .

Powered by Discuz! X3.2

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表