Waitqueue、Event及Semaphore的实现机制分析
Sailor_forever sailing_9806@163.com 转载请注明
【摘要】本文分析了内核同步及互斥的几种机制Waitqueue、Event及Semaphore的实现,详细分析了其实现流程。Event及Semaphore本质上都是基于Waitqueue和自旋锁实现的。总结了静态定义及动态初始化数据结构的相关规则,这对于自定义的数据类型具有重要的借鉴意义。
【关键词】Waitqueue;Event;Semaphore;Spinlock;XXX_INITIALIZER();DECLARE_XXX;静态定义;动态初始化
5.1 prepare_to_wait和finish_wait 4
5.4 wait_event_interruptible_timeout 8
---------------------------------------------------------------------------------------------------------
1 资源总览
内核版本: 2. 6. 19
主要源文件
/include/linux/wait.h
/kernel /wait.c
/arch/arm/kernel/semaphore.c
/include/asm-arm/ semaphore.h
_wait_queue_head {}
wait_queue_head_t
__WAITQUEUE_INITIALIZER()
DECLARE_WAITQUEUE()
__WAIT_QUEUE_HEAD_INITIALIZER()
DECLARE_WAIT_QUEUE_HEAD()
init_waitqueue_head(wait_queue_head_t *q)
init_waitqueue_entry(wait_queue_t *q,struct task_struct *p)
__wait_event(wq,condition)
wait_event(wq,condition)
__wait_event_timeout(wq,condition,timeout)
wait_event_timeout(wq,condition,timeout)
__wait_event_interruptible_timeout(wq,condition,ret)
wait_event_interruptible_timeout(wq,condition,timeout)
__down(struct semaphore * sem)
__down_interruptible(struct semaphore * sem)
down_interruptible(struct semaphore * sem)
__down_trylock(struct semaphore * sem)
---------------------------------------------------------------------------------------------------------
2 wait_queue_head_t
/include/linux/wait.h
struct __wait_queue_head {
spinlock_t lock;
struct list_head task_list;
};
typedef struct __wait_queue_head wait_queue_head_t;
关于自定义结构体的风格,若需要提供别名,则原始类型前面加”__”或者“tag_”,表示其为内部数据类型,对外是不可见的。typedef之后的类型为了和原始类型分开一般会在后面添加“_t”,表示是typedef的,对外使用。
#define __WAIT_QUEUE_HEAD_INITIALIZER(name) { /
.lock = __SPIN_LOCK_UNLOCKED(name.lock), /
.task_list = { &(name).task_list, &(name).task_list } /
}
因为Linux内核对于链表的遍历方式的问题,通常一个双向循环链表中有一个头节点,其与其他节点的结构不一样,并且通常无有效信息。此处的等待队列头有两个域:
1) 操作循环链表的互斥锁;
2) 嵌入到等待队列头中的链表头。
为了用.域的形式初始化成员不能采用单独的初始化锁和链表头部的宏,但可以采用声明一个结构体类型的宏,如__SPIN_LOCK_UNLOCKED(name.lock);.task_list的初始化应该采用LIST_HEAD_INIT宏的,这样提高了可移植性。定义等待队列头部的同时,初始化了其成员,尤其是链表头的初始化是添加后续等待队列的前提。
#define DECLARE_WAIT_QUEUE_HEAD(name) /
wait_queue_head_t name = __WAIT_QUEUE_HEAD_INITIALIZER(name)
定义一个等待队列头同时分配内存并进行初始化。对外的接口。
extern void init_waitqueue_head(wait_queue_head_t *q);
void init_waitqueue_head(wait_queue_head_t *q)
{
spin_lock_init(&q->lock);
INIT_LIST_HEAD(&q->task_list);
}
动态初始化一个已经分配了内存的wait_queue_head_t结构。当wait_queue_head_t类型成员内嵌到其他结构体中时需要采用此方法,而不能采用DECLARE_WAIT_QUEUE_HEAD全局或在栈中定义初始化一个wait_queue_head_t结构。
3 wait_queue_t
struct __wait_queue {
unsigned int flags;
#define WQ_FLAG_EXCLUSIVE 0x01
void *private;
wait_queue_func_t func;
struct list_head task_list; //与该等待队列对应的链表
};
/*Macros for declaration and initialisaton of the datatypes*/
#define __WAITQUEUE_INITIALIZER(name, tsk) { /
.private = tsk, /
.func = default_wake_function, /
.task_list = { NULL, NULL } /
}
GNU语法中对于结构体成员赋初值采用了域的形式,如“.private =”,其好处在于:
a) 可以选择性的对部分成员赋值。当结构体成员变量较多而大部分无须初始值时,此方法显得尤为重要,因此减少了不必要的赋值。
b) 赋值顺序与数据结构定义中成员的顺序无关,因此若结构体成员顺序变化,初始化部分不会受到任何影响。
#define DECLARE_WAITQUEUE(name, tsk) /
wait_queue_t name = __WAITQUEUE_INITIALIZER(name, tsk)
全局或者在栈中定义一个wait_queue_t类型变量的同时对其初始化,这保证了系统的可靠性,避免因用户忘记初始化时导致的问题。分两步:
1) 内部宏__WAITQUEUE_INITIALIZER初始化相应成员;当wq内嵌在别的结构体中时,此宏很重要,提高了可移植性;
2) 提供给外部的接口,定义一个变量,并将第一步的结果赋值给该变量。
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;
}
动态初始化一个等待队列入口项,将其和当前进程关联起来,以便唤醒当前进程。
4 数据结构设计规则
今后凡遇到新设计一类结构体,若此类结构体变量必须初始化且有相对集中的操作,则应提供以下两个操作接口:
a) 定义新建一个结构体变量,并初始化之;
b) 动态初始化一个已经分配内存的该类变量
为了适应在堆栈及全局等任意地方分配的该变量,其应该接收指向该类变量的指针。
5 等待事件event
5.1 prepare_to_wait和finish_wait
/*
* Used to distinguish between sync and async io wait context:
* sync i/o typically specifies a NULL wait queue entry or a wait
* queue entry bound to a task (current task) to wake up.
* aio specifies a wait queue entry with an async notification
* callback routine, not associated with any task.
*/
#define is_sync_wait(wait) (!(wait) || ((wait)->private))
同步io等待将唤醒当前进程,异步io等待和当前进程无关,时间到后执行安装的回调函数
void fastcall
prepare_to_wait(wait_queue_head_t *q, wait_queue_t *wait, int state)
{
unsigned long flags;
wait->flags &= ~WQ_FLAG_EXCLUSIVE;
spin_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 finish_wait(wait_queue_head_t *q, wait_queue_t *wait)
{
unsigned long flags;
__set_current_state(TASK_RUNNING);
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);
}
}
5.2 wait_event
/include/linux/wait.h
#define __wait_event(wq,condition) /
do { /
DEFINE_WAIT(__wait); /
/
for(;;) { /
prepare_to_wait(&wq,&__wait,TASK_UNINTERRUPTIBLE); /
/// 添加到等待队列中,同时更改进程状态
if (condition) /
break; /
schedule(); //何时返回呢??? /
} /
finish_wait(&wq,&__wait); /
} while (0)
// “__”表示内部函数,默认为condition不满足,添加至等待队列,调度
注意prepare_to_wait和finish_wait的匹配关系
#define wait_event(wq,condition) /
do { /
if(condition) /
break; /
__wait_event(wq,condition); /
}while (0)
//对外的接口函数,需要判断condition,若假则等待
--------------------------------------------------------------------------------------------------------------
等待系列函数架构设计:
5.3 wait_event_timeout
#define __wait_event_timeout(wq, condition, ret) /
do { /
DEFINE_WAIT(__wait); /
/
for (;;) { /
prepare_to_wait(&wq, &__wait, TASK_UNINTERRUPTIBLE); /
if (condition) /
break; /
ret = schedule_timeout(ret); /
if (!ret) /
break; //延时到,退出 /
} /
finish_wait(&wq, &__wait); /
} while (0)
#define wait_event_timeout(wq,condition,timeout) /
({ /
long __ret=timeout; /
if( !(condition) ) /
__wait_event_timeout( wq,condition,__ret); /
__ret; /
})
5.4 wait_event_interruptible_timeout
#define __wait_event_interruptible_timeout(wq,condition,ret) /
do { /
DEFINE_WAIT(__wait); /
/
for (;;) { /
prepare_to_wait(&wq,&__wait,TASK_INTERRUPTIBLE); /
if (condition) /
break; /
if(!signal_pending(current)) { /
// 当前进程无信号需要处理
ret = schedule_timeout(ret); /
if(!ret) /
break; //时间片用完唤醒 /
continue; / .
} /
ret = _ERESTARTSYS; //被信号唤醒 /
break; /
} /
finish_wait(&wq,&__wait); /
} while (0)
#define wait_event_interruptible_timeout(wq,condition,timeout) /
( { /
long__ret = timeout; /
if(!(condition)) /
__wait_event_interruptible_timeout(wq,condition,__ret); /
__ret; /
})
wait_event_interruptible_timeout()类架构:
6 基于等待队列的semaphore
struct semaphore {
atomic_t count;
int sleepers;
wait_queue_head_t wait;
};
Count该信号量表示的可以资源数目,其必须支持原子操作;
Sleepers,睡眠在该信号量上的进程数目,等于等待队列中的表项数;
Wait内嵌的等待队列头结构,信号量本质上是一种资源,资源不可用时,睡眠的进程通过等待队列和该信号量相关联,以便资源可用时从等待队列中唤醒相应进程。
信号量本身的互斥操作是由wait中的互斥锁提供的。
功能: 获取信号灯进行临界资源操作,如果获得失败则睡眠,睡眠为深度睡眠,不可中断
fastcall void__sched __down(struct semaphore * sem)
{
struct task_struct *tsk = current;
DECLARE_WAITQUEUE(wait,tsk);
//这个wait为等待结点,其和当前进程关联,若资源不可用则将该进程添加到等待队列中
unsigned long flags;
tsk->state = TASK_UNINTERRUPTIBLE; //将当前进程置为不可中断状态
spin_lock_irqsave(&sem->wait.lock,flags);
add_wait_queue_exclusive_locked(&sem->wait,&wait); //将进程添加到等待队列中
sem->sleepers++;
for (;;) {
int sleepers = sem->sleepers;
if(!atomic_add_negative(sleepers – 1,&sem->count)) {
sem->sleepers = 0;
break;
}
sem->sleepers = 1;
// 休眠前先解开锁,否则无人可以唤醒之
spin_unlock_irqrestore(&sem->waitlock,flags);
//由于无可用资源,当前进程对应的wait_queue_t{}结点插入该sem{}信号等待队列中,然后通过schedule()让出cpu,让cpu去调度执行其他进程,而当前进程睡眠去了
schedule();
// 当schedule返回时,说明当前进程被唤醒了
spin_lock_irqsave(&sem->wait.lock,flags);
tsk-<state = TASK_UNINTERRUPTIBLE;
//回到for,再次检查资源是否可用
}
// 当前资源可用,将进程从等待队列中移除
remove_wait_queue_locked(&sem->wait,&wait);
wake_up_locked(&sem->wait;
spin_unlock_irqrestore(&sem->wait.lock,flags);
tsk->state = TASK_RUNNING;
}
当该临界资源一直未被释放时, 所有等待该临界资源的进程均在等待队列中sleep,不断的用schedule()让出cpu调度其他进程运行直至sem->count增加后至少变为0或正数后,当前进程直接从队列末尾出来进入执行,同时还唤醒了等待队列中第一个等待进程,不过由于sem->sleepers被赋为0,sem->count= =0,此进程刚唤醒,又要去sleep,除非sem->count又增加了,此唤醒进程才能执行
针对上面示意图分析: atomic_add_negative(sleepers-1,&sem->count)
__down( )函数流程分析:
定义一个等待队列结点wait,将其private指 针指向当前进程,并设定为深度睡眠标志. |
对等待队列进行加锁保存EFLAGS
将该结点wait插入等待队列未尾 |
调整count,sleepers值. |
当当前进程
count + = sleepers – 1后小于0否? |
yes NO
此时 (count = = -1) 情况1:count = = 0 情况2: count> 0
sleepers = 1 |
让出cpu,调度运行其他进程,当 前进程为等待队列中sleep去了. |
sleepers = 0 跳出循环 |
当前进程出队列准备运行 |
唤醒等待队列中第一个等待进程. |
等待队列解锁,恢复标志. |
__down()函数架构分析:
当前信号量上睡眠着三个进程。
count sleepers sleepers-1 atomic_add_negative()
-3 3 2 -1
fastcall int__sched __down_interruptible(struct semaphore * sem)
{
功能: :获得信号灯,成功获得进入临界资源运行,获得失败,则睡眠,但为浅度睡眠,可被唤醒中断,但需一个返回值,指明是否获得信号灯,新建 wait这个wait_queue_t{}等待队列结点,并初始化之,private指针指向当前进程
int retval = 0;
struct task_struct *tsk=current;
DECLARE_WAITQUEUE(wait,tsk);
unsigned long flags;
tsk->state = TASK_INTERRUPTIBLE;
spin_lock_irqsave(&sem->wait.lock,flags);
add_wait_queue_exclusive_locked(&sem->wait,&wait);
sem->sleepers++;
for(;;) {
int sleepers = sem->sleepers;
if(signal_pending(current)){ //当前进程从浅度睡眠中被中断唤醒出循环.
retval = _EINTR;
sem->sleepers = 0;
atomic_add(sleepers,&sem->count);
break;
}
if(!atomic_add_negative(sleepers – 1,&sem->count)) {
sem->sleepers = 0;
break; //当sem->count> = 1时资源空闲,跳出循环.
}
// sem->count<=0无空闲资源
sem->sleepers = 1; /*us-see-1 above */
spin_unlock_irqrestore(&sem->wait.lock,flags); //便于其他进程释放资源,增加count值.
schedule();
spin_lock_irqsave(&sem->wait.lock,flags);
tsk->state = YASK_INTERRUPTIBLE;
}
remove_wait_queue_locked(&sem->wait,&wait);
wake_up_locked(&sem->wait);
spin_unlock_irqrestore(&sem->wait.lock,flags);
//在信号量sern的等待队列中,删除指向当前进程wait结点.唤醒该信号灯sern的等待队列中的第一个结点.释放该信号灯的等待队列首部的自旋锁,并恢复EFLAGS
tsk->state = TASK_RUNNING;
return retval; //0 获得信号灯
//-EINTR: 没有获得信号灯,中断返回,不再睡眠等待信号灯
}
--------------------------------------------------------------------------------------------------------
―――――――――――――――――――――――――――――――――――
static inline int down_interruptible(struct semaphore * sem)
{
int result;
might_sleep();
_asm_ volatile_(
“#atomic interruptible down operation/n/t”
LOCK “decl %1/n/t” /*- -sem->count */
“js 2f /n/t”
“xorl %0,%0/n”
“1:/n”
LOCK_SECTION_START(“”)
“2:/tlea %1,%%eax/n/t”
“call__down _interruptible/n/t”
“gmp lb/n”
LOCK_SECTION_END
:“=a”(result),“=m”(sem->count)
:
:“memory”);
return result;
-------------------------------------------------------------------------------------------------------
down_interruptible()架构分析:
down_interruptible() |
先对其他需要调度的
might_sleep() |
锁定内存总线后 信号灯计数器减1. |
信号灯指示的可 用资源还有否? |
YES NO
返回0 |
按fastcall,将参数放入eax中,并保存edx ,ecx值调用函数 |
__down_interruptible() |
住下运行 |
获取信号量成功否?即可用资源有么? |
没有 有
当前进程浅度睡眠 |
返回0 |
往下运行 |
有中断来唤醒否? |
有 否
返回-EINTR |
轮到该进程,又获得cpu. |
往下走 |
fastcall int __down_trylock(struct semaphore * sem)
//若临界资源现在变为空闲,则唤醒等待队列中第一个结点,自己结束返回.若临界资源仍忙,则结束返回,不阻塞也不唤醒等待队列中的结点进程.
{
int sleepers;
unsigned long flags;
spin_lock_irqsave(&sem->wait.lock,flags);
sleepers = sem->sleepers + 1;
sem->sleepers = 0;
if(!atomic_add_negative(sleepers,&sem->count)){
wake_up_locked(&sem->wait);
}
spin_unlock_irqrestore(&sem->wait.lock,flags);
return 1;
}
_ _ down_try_lock() sem->sleepers
sem->count原始值 sem->count- - sleepers原始值 sleepeers+ + atomic_add_negative
(sleepers,$sem->count.)
3 2 0 1 3
2 1 0 1 2
1 0 0 1 1
0 --1 0 1 0
--1 --2 1 2 0
--2 --3 2 3 0
--3 --4 3 4 0
+
参考资料:
信号灯的相关源代码分析 余旭 yuxu 9710108@163.com.