阻塞与非阻塞IO
阻塞操作是指在执行设备操作时若不能获得资源,则挂起进程,直到满足可操作的条件后再进行操作。被挂起的进程进入休眠状态,被从调度器的运行队列移走,直到等待条件被满足。而非阻塞操作的进程不能进行设备时并不挂起,它或者放弃,或者不停地查询,直到可以进行操作为止。
阻塞的进程会进入睡眠状态,必须有一个地方能够唤醒休眠的进程。唤醒进程的地方最大可能发生在中断里面,因为硬件资源获得的同时往往伴随一个中断。
等待队列
可以使用等待队列(wait queue)来唤醒阻塞进程。wait queue很早就作为一个基本的功能单位出现在linux内核里了,它以队列为基础数据结构,与进程调度机制紧密结合,能够用于实现内核中的异步通知机制。等待队列可以用来同步对系统资源的访问,信号量在内核中也依赖等待队列来实现。
1.定义等待队列头
wait_queue_head my_queue;
2. 初始化等待队列头
init_waitqueue_head(&my_queue);
DELCARE_WAIT_QUEUE_HEAD(name)/定义并初始化
3. 定义等待队列
DECLARE_WAITQUEUE(name,tsk)
4. 添加/移除等待队列
void fastcall
add_wait_queue(wait_queue_head_t *q,wait_queue_t *wait);
void fastcall
remove_wait_queue(wait_queue_head_t *q,wait_queue_t *wait);
5. 等待事件
wait_event(queue,condition);
wait_event_interruptible(queue,condition);
wait_event_timeout(queue,timeout);
wait_event_interruptible_timeout(queue,condition,timeout);
wait_event()代码:
#define
wait_event(wq,condition)
do {
if
(condition)
break;
__wait_event(wq,condition);//添加到等待队列
} while (0)
#define __wait_event(wq,condition)
do {
DEFINE_WAIT(__wait);
for
(;;) {
prepare_to_wait(&wq,&__wait,TASK_UNINTERRUPTIBLE);
if
(condition)
break;
schedule();//放弃cpu
}
finsh_wait(&wq,&__wait);
} while(0)
void fastcall
prepare_to_wait(wait_queue_head_t *q,wait_queue_t *wait,int state)
{
unsigned long flags;
wait->flags
&= ~WQ_FLAG_EXCLUSIVE;
sping_lock_irqsave(&q->lock,flags);
if
(list_empty(&wait->task_list))
__add_wait_queue(q,wait);//添加等待队列
if
(is_sync_wait(wait))
set_current_state(state);//改变当前进程的状态为休眠
spin_unlock_irqrestore(&q->lock,flags);
}
void
fastcall finsh_wait(wait_queue_head_t *q,wait_queue_t *wait)
{
unsigned long flags;
__set_current_state(TASK_RUNNING);//恢复当前进程的状态为TASK_RUNING
if
(!list_empty_careful(&wait->task_list)) {
spin_lock_irqsave(&q->lock,flags);
list_del_init(&wait->task_list);
spin_unlock_irqrestore(&q->lock,flags);
}
6. 唤醒队列
void wake_up(wait_queue_head_t *queue);
void wake_up_interruptible(wait_queue_head_t *queue);
wake_up()唤醒wait_event()或wait_event_timeout(),wake_up_interruptible()或wake_up_interruptible_timeout().wake_up()可唤醒处于TASK_INTERRUPTIBLE和TASK_UNINTERRUPTIBLE的进程,而wake_up_interruptibel()只能唤醒处于TASK_INTERRUPTIBLE的进程。
7.在等待队列上睡眠
sleep_on(wait_queue_head_t *q);
interruptible_sleep_on(wait_queue_head_t *q);
sleep_on()将目前进程的状态置成TASK_UNINTERRUPTIBLE,并定义一个等待队列,之后把它附属到等待队列头q,直到资源可获得,q引导的等待队列被唤醒。
interruptible_sleep_on()与sleep()类似,其作用是将目前进程的状态置成TASK_INTERRUPTIBLE,并定义一个等待队列,之后把它附属到等待队列头q,直到资源可获得的,q引导的等待队列被唤醒或者收到信号。
sleep_on()与wake_up()成对使用,interruptible_sleep_on()与wake_up_interruptible()成对使用。
sleep_on():
void fastcall __sched
sleep_on(wait_queue_head_t *q)
{
SLEEP_ON_VAR
/*
#define
SLEEP_ONVAR
unsigned
long flags;
wait_queue_t
wait;
init_waitqueue_entry(&wait,current);*/
current->state
= TASK_UNINTERRUPTIBLE
SLEEP_ON_HEAD
/*
#define SLEEP_ONHEAD
spin_lock_irqsave(&q->lock,flags);
__add_wait_queue(q,&wait);
sping_unlock(&q->lock,flags);*/
schedule();
SLEEP_ON_TALL
/*
#define SLEEP_ON_TALL
spin_lock_irq(&q->lock);
__remove_wait_queue(q,&wait);*/
spin_unlock_irqrestore(&q->lock,flags);*/
}
interruptible_sleep_on():
void fastcall __sched
interruptible_sleep_on(wait_queue_head_t *q)
{
SLEEP_ON_VAR
current->state
= TASK_INTERRUPTIBLE;
SLEEP_ON_HEAD
schedule();
SLEEP_ON_TALL
}
note:在内核中使用set_current_state()或__add_current_queue()函数来实现目前进程状态的改变,直接采用current->state = TASK_INTERRUPTIBLE类似的赋值语句也是可行。通常而言,set_current_state()在任何环境下都可以使用,不会存在并发问题,但是效率要低于__add_current_queue().因此,许多设备驱动并不调用sleep_on()或interruptible_sleep_on(),而是直接进程状态改变和切换。
轮询操作
轮询
使用非阻塞I/O的应用程序通常会使用select()和poll()系统调用查询设备是否可对设备进行无阻塞的访问。selecct()和poll()系统调用最终会引发设备驱动中的poll()函数被执行。
应用程序中的轮询编程
应用程序最广泛用到select()系统调用。
int select(int numfds,fd_set *reafds, fd_set
*writefds, fd_set *exceptfds, struct timeval *timeout);
struct timeval
{
int
tv_sec;//second
int
tv_usec;//1/1000 second
}
FD_ZERO(fd_set *set);//清零set
FD_SET(int fd, fd_set *set);//加fd到set
FD_CLR(int fd, fd_set *set);//清除fd从set
FD_ISSET(int fd, fd_set *set);//判断fd是否被置位
设备驱动中的轮询编程
设备驱动中poll()函数:
unsigned int (*poll)(struct file *filp,
struct poll_table *watit);
struct poll_table 轮询表。
此函数,对可能引起设备文件状态变化的等待队列调用poo_wait()函数,将对应的等待队列头添加到poll_table; 返回表示是是否能对设备进行无阻塞读写访问的掩码。
poll_wait()注册等待队列:
void poll_wait(struct file *filp,
wait_queue_head_t *queue, poll_table *wait);
只是把当前进程加入到wait等待队列表中。
驱动程序poll()返回设备资源可获取状态,即POLLIN,POLLOUT,POLLPRI,POLLERR,POLLNVAL等宏位“或”的结果。
阻塞与非阻塞访问是IO访问的两种I/O操作暂时不可进行时会让进程睡眠。
在设备驱动中阻塞IO一般基于等待队列,等待队列可用于同步驱动中事件发生的先后顺序。使用非阻塞IO的应用程序也可借助轮询函数来查询设备是否能立即被访问,用户空间调用select()和poll()接口,设备驱动提供poll()函数,设备驱动的poll()本身不会阻塞,但是poll()和select系统调用则会阻塞地等待文件描述集合中的至少一个可访问或超时。
文章评论(0条评论)
登录后参与讨论