阻塞型IO
主要内容有:
进程睡眠和唤醒方法
阻塞型I/O的实现方法
Select系统调用的实现方法
1、
休眠的意义
从调度器的运行队列à某个等待队列
直到等到某个事件发生,在从等待队列返回到运行队列。
2、
如何将进程安全地进入休眠状态
不能再原子上下文进行休眠
休眠时,对外界一无所知,进程必修重新检测等待条件
进程只有确保会被其他进程唤醒,才能进入休眠
3、
Linux的休眠机制
进程经常由于某种条件没有得到满足而不得进入睡眠状态,然后等待条件得到满足的时候再继续运行,进入运行状态。这种需求需要等待队列机制的支持。
(等待队列就是一个进程链表,其中包含了等待某个特定时间的所有进程)
4、
等待队列。
4.1、在Linux驱动程序设计中,可以使用等待队列来实现进程的阻塞,等待队列卡看作是保持进程的容器,在阻塞进程时,将进程放入等待队列,当唤醒进程时,从等待队列中取出进程。
4.2、每个等待任务都会抽象成一个wait_queue,并且挂载到wait_queue_head上。
4.3、Linux中等待队列的实现思想:当一个任务需要在某个wait_queue_head上睡眠时,将自己的进程控制块信息分装到wait_queue中,然后挂载到wait_queue_head的链表中,执行调度睡眠。当某些事情发生后,另一个任务(进程)会唤醒wait_queue_head上的某个或者所有任务,唤醒工作也就是将等待队列中的任务设置为可调度的状态,并且从队列中删除。
4.4、等待队列通过“等待队列头”来管理
4.4.1类型:struct wait_queue_head_t
4.4.2 定义在<linux/wait.h>中
4.4.3 定义等待队列
wait_queue_head_t xxx_queue
4.4.4初始化等待队列:init_waitqueue_head(&xxx_queue);
4.4.5静态定义并初始化等待队列
DECLARE_WAIT_QUEUE_HEAD(xxx_queue);
4.5等待事件:
4.5.1当进程休眠时,等待被另外进程唤醒,同时检查进程等待的条件是否为真,若为真,则被调度执行;反之继续休眠。
4.5.2等待事件,即为进程等待的条件(condition)。
4.6休眠函数。
wait_event(queue,condition); //queue:等待队列头,通过值传递,
condition休眠前后都要对该表达式求值;在条件为真之前,进程保持休眠。
wait_enent_interruptible(queue,condition);
wait_event_timeout(queue,condition,timeout);//等待限定的jiffs时间到达后返回0值,无论condition为何值。
wait_envent_interruptible_timeout(queue,condition,timeout);
4.7唤醒函数
wake_up(wait_queue_head_t* queue); //唤醒所有的等待进程。
wake_up_interruptible(wait_queue_head_t* queue);
wake_up_nr(wait_queue_head_t* queue,int nr); //唤醒nr个独占等待进程,而不是一个,当nr=0时,唤醒所有的独占等待进程。
wake_up_interuptible_nr(wait_queue_head_t* queue,nr);
wake_up_interuptible_sync(wait_queue_head_t* queue); //被唤醒后强制调度执行原休眠进程。
4.8实例
休眠函数
唤醒函数
4.9上面实例中存在一个概率很小的竞态条件。
A、B进程都在等在wq的队列上,C进程调用wake_up_interruptible.A进程别唤醒,检查条件flag!=0成立。而此时,调度到B进程,B进程也检查到flag!=0成立。这样,一个事件唤醒了两个进程,可能产生竞态。
4.10可以用原子操作防止这种情况。
4.11设置进程休眠的内部细节
4.11.1分配并初始化一个wait_queue_t结构(包含休眠进程的信息,以及期望被唤醒的相关细节)
4.11.2设置进程的状态,并将其标记为休眠状态(TASK_INTERRUTIBLE、、可中断,TASK_UNINTERRUPTIBLE//不可中断)
void set_current_state(int new_state);//手动设置进程的状态
current->state=TASK_INTERRUPTIBLE;//老内核版本设置方式
4.11.3让出处理器
If(!condition)
Schedule();//执行调度,让出处理器。
4.12.手工休眠
4.13唤醒等待队列可能发生的情况
4.13.1当唤醒wake_up时,所有等待在该队列上的进程都被唤醒,并进入可运行状态。
4.13.2如果只有一个进程可获得资源,此时,其他的进程又将再次进入休眠。
4.13.3如果数量很大,被称为“疯狂兽群”
4.14独占等待
4.14.1与普通休眠的不同
等待队列入口设置了WQ_FLAG_EXCLUSIVE标志时,则会被添加到等待队列的尾部。而没有这个标志的入口会被添加到头部。
在某个等待队列上调用wake_up时,它会在唤醒第一具有WQ_FLAG_EXCLUSIVE的标志的的进程之后停止唤醒其他独占进程。
4.14.2使进程进入独占等待函数:
void prepare_to_wait_exclusive(wait_queue_head_t* queue,wait_queue_t *wait,int state);
注:使用wait_event的变种读无法使用独占等待。
阻塞方式:
5、
在阻塞型驱动程序中,read实现方式如下:如果进程调用read,但是设备没有数据或数据不足,进程阻塞。当新数据到底后,唤醒被阻塞进程。
6、
在阻塞型驱动程序中,write实现方式如下:如果进程调用write,但是设备没有足够的空间供其写入数据,进程阻塞。当设备中的数据被读走后,缓冲区中空出部分空间,则唤醒被阻塞进程。
阻塞型I/O实例:
声明:本文非原创,整理自申嵌