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

本文详细介绍了Linux内核中的等待队列机制,包括其基本原理、核心数据结构及其实现方式。通过实例展示了如何使用等待队列来实现进程间的同步与通信。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >



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构建的队列中,最后把这个队列加入队列头。并且多个等待队列可以造成链表加入头中形成双向循环链表,便于删除和构建等待队列。

大概的流程图

<think>好的,我现在需要帮助用户分析wait_queue_head_t在用户态下的实现细节。首先,我得回忆一下内核中的wait_queue_head_t结构。根据引用内容,内核中的wait_queue_head_t是一个包含自旋锁任务链表的结构体,用于管理等待队列。例如,引用1提到它的结构有spinlock_tlist_head,而引用4说明它通过双循环链表与进程调度结合,实现异步事件通知。 用户的问题是关于用户态下的实现,但用户态内核态的环境有很大不同。用户态没有自旋锁、任务链表这些内核特有的机制,所以需要找到替代方法。可能需要使用互斥锁或条件变量来模拟等待队列的行为。比如,条件变量可以用于线程间的等待通知,类似于内核等待队列机制。 接下来,用户态实现的关键点可能包括:同步机制的选择(如互斥锁条件变量)、队列管理的数据结构(如链表)、线程的阻塞唤醒机制。例如,在用户态,可以使用pthread库中的pthread_cond_waitpthread_cond_signal来实现等待唤醒,类似内核中的wait_eventwake_up函数。 此外,引用5提到了add_wait_queue函数,其中使用了自旋锁链表操作。在用户态,可能需要用互斥锁保护队列的访问,避免竞争条件。同时,队列的管理可以使用链表结构,比如glib的GList或者自己实现的链表。 还要考虑超时机制,比如用户态下的pthread_cond_timedwait可以设置超时时间,类似于内核wait_event_interruptible_timeout。这需要在等待队列的实现中加入超时处理逻辑。 最后,用户可能需要一个示例代码来理解具体实现。可以编写一个简单的结构体,包含互斥锁、条件变量任务链表,然后实现入队、等待唤醒的函数。这样用户能直观看到如何将内核机制迁移到用户态。 总结一下,用户态的实现需要替换内核特有的同步原语,使用用户态可用的库函数数据结构,同时保持类似的逻辑结构。需要注意线程安全性能问题,确保实现的正确性效率。</think>### wait_queue_head_t 用户态实现分析 #### 1. 内核与用户态的核心差异 在内核中,$wait\_queue\_head\_t$ 是用于进程/线程同步的核心数据结构,依赖内核特性如: - **自旋锁(spinlock_t)**:用于短期锁保护(引用1、引用5)[^1][^5] - **调度机制**:进程休眠(TASK_INTERRUPTIBLE)与唤醒(wake_up) - **双循环链表**:管理等待队列项(引用4)[^4] 用户态无法直接使用这些内核特性,需通过以下方式模拟: - **互斥锁(mutex)** 替代自旋锁 - **条件变量(condition variable)** 实现阻塞/唤醒 - **链表结构** 管理等待项 --- #### 2. 用户态实现方案 ##### 2.1 数据结构定义 ```c #include <pthread.h> #include <glib.h> // 使用GLib链表 typedef struct { pthread_mutex_t lock; // 替代spinlock_t pthread_cond_t cond; // 替代内核唤醒机制 GList *task_list; // 用户态链表管理等待项 } user_wait_queue_head_t; ``` ##### 2.2 关键操作实现 **初始化队列** ```c void user_init_waitqueue(user_wait_queue_head_t *q) { pthread_mutex_init(&q->lock, NULL); pthread_cond_init(&q->cond, NULL); q->task_list = NULL; } ``` **添加等待项** ```c void user_add_wait_queue(user_wait_queue_head_t *q, void *task) { pthread_mutex_lock(&q->lock); q->task_list = g_list_append(q->task_list, task); // GLib链表操作 pthread_mutex_unlock(&q->lock); } ``` **阻塞等待(模拟wait_event)** ```c void user_wait_event(user_wait_queue_head_t *q, int (*condition)(void)) { pthread_mutex_lock(&q->lock); while (!condition()) { pthread_cond_wait(&q->cond, &q->lock); // 使用条件变量实现阻塞 } pthread_mutex_unlock(&q->lock); } ``` **唤醒操作(模拟wake_up)** ```c void user_wake_up(user_wait_queue_head_t *q) { pthread_mutex_lock(&q->lock); pthread_cond_broadcast(&q->cond); // 唤醒所有等待者 pthread_mutex_unlock(&q->lock); } ``` --- #### 3. 与内核实现的对比 | **特性** | **内核态实现** | **用户态模拟** | |----------------|--------------------------|----------------------------| | 同步原语 | 自旋锁(spinlock_t) | 互斥锁(pthread_mutex_t) | | 阻塞/唤醒 | 调度器TASK状态切换 | 条件变量(pthread_cond_t) | | 队列数据结构 | 内核双链表(list_head) | GLib链表(GList) | | 超时处理 | wait_event_timeout() | pthread_cond_timedwait() | --- #### 4. 典型应用场景 1. **线程池任务调度**:主线程通过条件变量唤醒工作线程 2. **生产者-消费者模型**:缓冲队列空/满时的阻塞等待 3. **事件驱动架构**:异步I/O完成时的通知机制 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值