Linux 阻塞IO(等待队列)原理及架构
一. 阻塞操作阻塞操作是指在执行折本操作时,若不能获得自愿,则挂起进程直到满足可操作性的条件后在进行操作。被挂起的进程进入休眠状态,被从调度器的运行队列移走,直到等待的条件被满足。假设recvfrom函数是一个系统调用:阻塞不是低效率,如果设备驱动不阻塞,用户想获取设备资源只能不断查询,消耗CPU资源,阻塞访问时,不能获取资源的进程将进入休眠,将CPU资源让给其他资源。二. 等待队列在...
一. 阻塞操作
阻塞操作是指在执行折本操作时,若不能获得自愿,则挂起进程直到满足可操作性的条件后在进行操作。被挂起的进程进入休眠状态,被从调度器的运行队列移走,直到等待的条件被满足。
假设recvfrom函数是一个系统调用:
阻塞不是低效率,如果设备驱动不阻塞,用户想获取设备资源只能不断查询,消耗CPU资源,阻塞访问时,不能获取资源的进程将进入休眠,将CPU资源让给其他资源。
因为阻塞的进程会进入休眠状态,因此,必须确保有一个地方能够唤醒休眠的进程。最大可能发生在中断函数里。
二. 等待队列
在Linux驱动程序中,可以使用等待队列(wait queue)来实现阻塞进程的唤醒。
Linux内核的等待队列是以双循环链表为基础数据结构,与进程调度机制紧密结合,能够用于实现核心的异步事件通知机制,也可以用来同步对系统资源的访问。
等待队列有两种数据结构:等待队列头 (wait_queue_head_t)和等待队列项(wait_queue_t)。
等待队列头和等待队列项中都包含一个list_head类型的域作为”连接件”。它通过一个双链表和把等待task的头,和等待的进程列表链接起来。
struct __wait_queue_head {
spinlock_t lock; /* 保护等待队列的原子锁 (自旋锁),在对task_list与操作的过程中,使用该锁实现对等待队列的互斥访问*/
struct list_head task_list; /* 等待队列,双向循环链表,存放等待的进程 */
};
struct __wait_queue {
unsigned int flags; /* 其中flags域指明该等待的进程是互斥进程还是非互斥进程。其中0是非互斥进程,WQ_FLAG_EXCLUSIVE(0×01)是互斥进程 */
void *private; /* 通常指向当前任务控制块 */
wait_queue_func_t func;
struct list_head task_list; /* 挂入wait_queue_head的挂载点 */
};
typedef struct __wait_queue wait_queue_t;
等待队列的使用分为以下两部分:
(1)为使当前进程在一个等待队列中睡眠,需要调用wait_event(或某个等价函数),此后,进程进入睡眠,将控制权交给调度器。
(2)相对应的,是当数据到达后,必须调用wake_up函数(或某个等价函数)来唤醒等待队列中睡眠的进程
- 定义等待队列头
wait_queue_head_t my_queue;
- 初始化等待队列头
init_waitqueue_head(&my_queue);
-->__init_waitqueue_head((q), #q, &__key)
--> INIT_LIST_HEAD(&q->task_list);
--> static inline void INIT_LIST_HEAD(struct list_head *list) //将双循环链表的指针指向队列头部
{
list->next = list;
list->prev = list;
}
定义并初始化等待队列头
DECLARE_WAIT_QUEUE_HEAD(name)
-->wait_queue_head_t name = __WAIT_QUEUE_HEAD_INITIALZER(name)
-->#define __WAIT_QUEUE_HEAD_INITIALZER(name){
...
.task_list = {&(name).task_list, &(name).task_list} ==> INIT_LIST_HEAD(struct list_head *list)
}
- 定义等待队列
静态定义:
DECLARE_WAITQUEUE(name, tsk)
-->#define DECLARE_WAITQUEUE(name, tsk)
wait_queue_t name = __WAITQUEUE_INITIALZER(name, tsk)
-->#define __WAITQUEUE_INITIALZER(name, tsk){
.private = current,
.func = default_wake_function==> try_to_wake_up
.task_list = {NULL, NULL}
}
静态定义并初始化等待队列(即2+3):
DEFINE_WAIT(name)
动态声明和初始化一个等待队列
struct wait_queue_t myqueue;
init_waitqueue_entry(&myqueue, tsk);
-->static inline void init_waitqueue_entry(wait_queue_t *q, struct task_struct *p)
{
q->flags = 0;
q->private = p;
q->func = default_wake_function;
}
- 添加/移除等待队列
//设置等待的进程为非互斥进程,并将其添加进等待队列头(q)的队头中。
void add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait)
{
unsigned long flags;
wait->flags &= ~WQ_FLAG_EXCLUSIVE;
spin_lock_irqsave(&q->lock, flags);
__add_wait_queue(q, wait);
-->list_add(&new->task_list, &head->task_list);
-->void __list_add(struct list_head *new, struct list_head *prev, struct list_head next)
{
next->prev = new;
new->next = next;
new->prev = prev;
prev->next = new;
}
spin_unlock_irqrestore(&q->lock, flags);
}
EXPORT_SYMBOL(add_wait_queue);
//下面函数也和add_wait_queue()函数功能基本一样,只不过它是将等待的进程(wait)设置为互斥进程。
void add_wait_queue_exclusive(wait_queue_head_t *q, wait_queue_t *wait)
{
unsigned long flags;
wait->flags |= WQ_FLAG_EXCLUSIVE;
spin_lock_irqsave(&q->lock, flags);
__add_wait_queue_tail(q, wait);
-->list_add_tail(&new->task_list, &head->task_list);
-->__list_add(new, head->prv, head);
spin_unlock_irqrestore(&q->lock, flags);
}
EXPORT_SYMBOL(add_wait_queue_exclusive);
//在等待的资源或事件满足时,进程被唤醒,使用该函数被从等待头中删除。
void remove_wait_queue(wait_queue_head_t *q, wait_queue_t *wait)
{
unsigned long flags;
spin_lock_irqsave(&q->lock, flags);
__remove_wait_queue(q, wait);
-->list_del(&old->task_list);
-->static inline void list_del(struct liset_head *entry)
{
__list_del(entry->prev, entry->next);
-->static inline void __list_del(struct list_head *prev, struct list_head *next)
{
next->prev = prev;
prev->next = next;
}
entry->next = LIST_POISON1; ==>0x00100100
entry->prev = LIST_POISON2; ==>0x00200200
}
spin_unlock_irqrestore(&q->lock, flags);
}
EXPORT_SYMBOL(remove_wait_queue);
- 等待事件
#define wait_event(wq, condition)
do {
if (condition)
break;
__wait_event(wq, condition);
-->(void) __wait_event(wq, condition, TASK_UNINTERRUPTIBLE, 0, 0, schedule())
-->for(;;)
{
if(condition)
break;
prepare_to_wait_event(wait_queue_head_t *q, wait_queue_head_t *wait, int state); /* 主要是将进程状态改变为state */
cmd; /* 执行schedule()==>进程调度 */
}
} while (0)
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 *q, unsigned int mode,
int nr_exclusive, void *key)
{
unsigned long flags;
spin_lock_irqsave(&q->lock, flags);
__wake_up_common(q, mode, nr_exclusive, 0, key);
-->try_to_wake_up() /* 最后调用到这个函数唤醒进程 */
spin_unlock_irqrestore(&q->lock, flags);
}
EXPORT_SYMBOL(__wake_up);
//唤醒等待队列.可唤醒处于TASK_INTERRUPTIBLE和TASK_UNINTERUPTIBLE状态的进程,和wait_event/wait_event_timeout成对使用
#define wake_up_interruptible(x) __wake_up(x, TASK_INTERRUPTIBLE, 1, NULL)
//和wake_up()唯一的区别是它只能唤醒TASK_INTERRUPTIBLE状态的进程.,与wait_event_interruptible
wait_event_interruptible_timeout
wait_event_interruptible_exclusive成对使用
#define wake_up_all(x) __wake_up(x, TASK_NORMAL, 0, NULL)
#define wake_up_interruptible_nr(x, nr) __wake_up(x, TASK_INTERRUPTIBLE, nr, NULL)
#define wake_up_interruptible_all(x) __wake_up(x, TASK_INTERRUPTIBLE, 0, NULL)
//这些也基本都和wake_up/wake_up_interruptible一样
wait_event()宏:
在等待会列中睡眠直到condition为真。在等待的期间,进程会被置为TASK_UNINTERRUPTIBLE进入睡眠,直到condition变量变为真。每次进程被唤醒的时候都会检查condition的值.
wait_event_interruptible()函数:
和wait_event()的区别是调用该宏在等待的过程中当前进程会被设置为TASK_INTERRUPTIBLE状态.在每次被唤醒的时候,首先检查 condition是否为真,如果为真则返回,否则检查如果进程是被信号唤醒,会返回-ERESTARTSYS错误码.如果是condition为真,则 返回0.
wait_event_timeout()宏:
也与wait_event()类似.不过如果所给的睡眠时间为负数则立即返回.如果在睡眠期间被唤醒,且condition为真则返回剩余的睡眠时间,否则继续睡眠直到到达或超过给定的睡眠时间,然后返回0.
wait_event_interruptible_timeout()宏
与wait_event_timeout()类似,不过如果在睡眠期间被信号打断则返回ERESTARTSYS错误码.
wait_event_interruptible_exclusive()宏
同样和wait_event_interruptible()一样,不过该睡眠的进程是一个互斥进程.
7. 在等待队列上睡眠
void __sched sleep_on(wait_queue_head_t *q)
{
sleep_on_common(q, TASK_UNINTERRUPTIBLE, MAX_SCHEDULE_TIMEOUT);
}
static long __sched
sleep_on_common(wait_queue_head_t *q, int state, long timeout)
{
unsigned long flags;
wait_queue_t wait;
init_waitqueue_entry(&wait, current);
__set_current_state(state);
spin_lock_irqsave(&q->lock, flags);
__add_wait_queue(q, &wait);
spin_unlock(&q->lock);
timeout = schedule_timeout(timeout);
spin_lock_irq(&q->lock);
__remove_wait_queue(q, &wait);
spin_unlock_irqrestore(&q->lock, flags);
return timeout;
}
该函数的作用是定义一个等待队列(wait),并将当前进程添加到等待队列中(wait),然后将当前进程的状态置为 TASK_UNINTERRUPTIBLE,并将等待队列(wait)添加到等待队列头(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);//添加等待队列
ret = count;
//等待设备缓冲区可写
do{
avail = device_writeable(...);
if(avail < 0)
__set_current_state(TASK_INTERRUPTIBLE);
if(avail < 0)
{
if(file->f_flags & O_NONBLOCK) //非阻塞
{
if(!ret)
ret = - EAGAIN;
goto out;
}
schedule();//调度其他进程执行
if(signal_pending(current))//如果是因为信号唤醒
{
if(!ret)
ret = - ERESTARTSYS;
goto out;
}
}
}while(avail < 0);
//写设备缓冲区
device_write(...);
out:
remove_wait_queue(&xxx_wait, &wait);//将等待队列移出等待队列头
set_current_state(TASK_RUNNING);//设置进程状态为TASK_RUNNING
}
更多推荐
所有评论(0)