您的位置:首页 > 运维架构 > Linux

Linux内核等待队列探究-wait_queue_t-wait_queue_head_t

2016-11-01 13:54 531 查看

Linux内核等待队列探究-wait_queue_t-wait_queue_head_t

相关源码版本:
LINUX内核源码版本:linux-3.0.86
UBOOT版本:uboot-2010.12.
Android系统源码版本:Android-5.0.2】
 
等待队列是LINUX内核实现阻塞访问的方式之一,同时LINUX内核的信号量和完成量都是用等待队列来实现的低层方式。正如我们知道的LINUX内核中实现阻赛的方式有很多种,如旋转锁,原子操作,信号量,RCU机制,等待队列。区别是除了信号量和等待队列在资源不可访问时会让出CPU资源,转而调用其它进程(LINUX内核只有进程概念,没全线程概念),而其它其中方式对大段代码的保护会严得影响CPU性能(具体原理去看相关知识)。LINUX内核中对于等待队列的实现框架已是设计好了的,对于驱动开发者只需调用相关宏或者函数就能完成资源的保护既等待。我们下面只分析常用等待队列的方式,不会去细分等待的扩展。
如下用等待队列实现功能:当某条件不满足时当前进程就阻塞既休眠,当这个条件满足之后要去唤醒前面休眠的进程。为了实现此功能,代码块如下(假使下面函数块都在同一个文件中,便于理解):
 
DECLARE_WAIT_QUEUE_HEAD(A);//定义一个等队列头
Condition = FALSE;
 
//进程1函数1

wait_event(A,condition);//条件condition不满足时当前进程1就休眠
一直等待到这个条件满足,然后继续执行进程1这个函数后面的代码。它有多个衍生版本如下列出部分:
//wait_event_timeout
//wait_event_interruptible
//wait_event_interruptible_timeout
//wait_event_interruptible_exclusive......
........
Condition=FALSE;//执行完之后要把条件清掉,为了下次调用函数1时又能实现阻寒功能

 
//进程2函数2

Condition=TRUE;//条件先置起来,这样才能再唤醒之后去执行休眠后的代码。等待函数里面有判断这个条件,如果不满足就继续休眠
wake_up(A);//唤醒进程1,下面列出对应进程1用到的不同版本的唤醒函数:
//wake_up_interruptible
//.....

对于驱动开发工程来说,只要大概按上面框架就能正确的实现阻塞功能,既实现当条件不满足时进程就阻塞,当条件满足时就去唤醒对应进程。在实际工程中,阻塞部分一般放在资源读取函数中,当资源不可利用时就阻塞。唤醒函数一般放在中断,或接收,既能够让资源进入可以利用状态。下面接着分析我们上面列出的框架的各个函数的内部原理。我们只分析主脉络,不分析衍生版本。
分析DECLARE_WAIT_QUEUE_HEAD(A);//定义一个等队列头,并且初始化
 
#define DECLARE_WAIT_QUEUE_HEAD(name) \
wait_queue_head_t name = __WAIT_QUEUE_HEAD_INITIALIZER(name)
 
#define __WAIT_QUEUE_HEAD_INITIALIZER(name) {\
.lock=
__SPIN_LOCK_UNLOCKED(name.lock),\
.task_list= { &(name).task_list,
&(name).task_list } }
 


上面初始化函数就是把上图中的部分原素初始化,初始化情况为
.task_list= { &(name).task_list, &(name).task_list }//等待队列头的任务列表指向的上一个和下一个分别初始化指向自身。
.raw_lock = __ARCH_SPIN_LOCK_UNLOCKED//待待队列的自己旋锁实始化为解锁
 
分析wait_event(A,condition);//条件condition不满足时当前进程1就休眠
一直等待到这个条件满足,然后继续执行进程1这个函数后面的代码。
首先根据源代码列出此函数的功能和参数介绍:wait_event休眠直到condition变成有效。Wq用于等待的等待队列,既等待队列头。Conditon是一个C表达式是一个事件为了等待的。
这个进程是放入休眠(TAKS_UNINTERRUPTIBLE进程进入状态为不可以中断唤醒,既此进程收到中断信号也不会被唤醒,具体意思对于一个进程如果在由调度器调到到休眠之前如果设置了它的状态为这个,则此进程不可以被重新调度由中断信号,只能对应的唤醒函数唤醒。进程的状态还有几种分别如下:
TASK_RUNNING:运行态,进程运行状态处于TASK_RUNNING它,调度器可以调度它运行。周期性,根据调度策略。
TASK_INTERRUPTIBLE:可中断状态。进程运行状态处于TASK_INTERRUPTIBLE,则此进程收到中断信号则可以唤醒从等待队列中。
)当条件满足后这个等待队列将被唤醒。为了唤醒此函数  wake_up不得不被调用,当condition发生变化。
#define wait_event(wq, condition)\
do {\
if (condition) \//哪果条件满足则直接退出
break;\
__wait_event(wq, condition);\
} while (0)
#define __wait_event(wq, condition)\
do {\
DEFINE_WAIT(__wait);\
//定义等待队列,根据当前进程,并且定义一个默认的唤醒函数,唤醒后如果没有传入自定函数,就用下面默认的唤醒函数来执行唤醒功能




#define DEFINE_WAIT(name) DEFINE_WAIT_FUNC(name, autoremove_wake_function)
//.task_list= LIST_HEAD_INIT((name).task_list)

for (;;) {\
prepare_to_wait(&wq, &__wait, TASK_UNINTERRUPTIBLE);\
[
//等待准备函数 此函数完成功能是:把flage设为~WQ_FLAG_EXCLUSIVE非互斥,唤醒的时候如果当前休眠进程是互斥的,则一次只能唤醒一个互斥进程,非互斥的进程可以一次全部唤醒。判断当前进程是否已加入等待队列,如果未加入则通过函数__add_wait_queue加入A等待队列头中。同时把当前进程状态设置为TASK_UNINTERRUPTIBLE非可中断的唤醒的状态。此过程完成进程等待既休眠的准备工作。这样此进程调度唤出后就需要唤醒函数才能唤醒了。
]
if (condition)\
break;\
schedule();//让出CPU
  手动调度,当当前进程换出调度队列,直到当前进程满足前准备条件设置的状态唤醒 由于前设置了TASK_UNINTERRUPTIBLE状态,所以内核进程调度不能主动调用它了,TASK_RUNNING状态的进程才能由调度策略调度进入执行态。
}\
finish_wait(&wq, &__wait);\//当当前进程被唤醒之后接着就是执行此函数
 它做的动作和prepare_to_wait是相反的,它需要把进程状态设为可运行,同时把当前进程从等待队列头中删除。
} while (0)
 
分析wake_up(A);//唤醒进程1,下面列出对应进程1用到的不同版本的唤醒函数:
__wake_up(x, TASK_NORMAL, 1, NULL)
#define TASK_NORMAL(TASK_INTERRUPTIBLE
| TASK_UNINTERRUPTIBLE)
__wake_up_common(q, mode, nr_exclusive, 0, key);
static void __wake_up_common(wait_queue_head_t *q, unsigned int mode,
int nr_exclusive, int wake_flags,
void *key)
{
wait_queue_t *curr, *next;
list_for_each_entry_safe(curr, next, &q->task_list, task_list) {//唤醒等待队头上的等待队列,里面是一个FOR循环能遍历到队列头上的所有队列。
unsigned flags = curr->flags;
 
if (curr->func(curr, mode, wake_flags, key) &&
(flags
& WQ_FLAG_EXCLUSIVE) && !--nr_exclusive)
break;//执行唤醒函数,如果是互斥的进程,则只能唤醒nr_exclusive个数进程。如果休眠时是指定的非互斥的进程则可以唤醒所有的非互斥的。Wake_up此函数最少可以唤醒一个互斥进程
,可以唤醒全部非互斥进程。调用上面定义队列时指定的唤醒函数来唤醒我们加入等待队列中的等待进程。执行之后进入wait_event继续执行。
}
}
等待队列的核心数据结构关系是:等待队列头wait_queue_head_t作为结构结构头,然后所有需要等待的进程加入wait_queue_t构建的队列中,最后把这个队列加入队列头。并且多个等待队列可以造成链表加入头中形成双向循环链表,便于删除和构建等待队列。


大概的流程图
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: