当前位置: 首页 > news >正文

内核等待队列以及用户态的类似机制

内核态-等待队列

参考链接:https://www.cnblogs.com/xinghuo123/p/13347964.html

参考链接:https://mp.weixin.qq.com/s/GZ8ekqzLK_zXEwR5p7bLeA

咱们今天来唠唠 Linux 内核里的 “等待队列”—— 这东西说白了,就是内核里的 “候诊室”,专门管那些暂时 “没活儿干” 的进程。你想啊,要是进程们都跟没头苍蝇似的,明明条件不满足还死盯着 CPU 要干活,那系统不得乱成一锅粥?等待队列就是那个穿白大褂的护士,柔声说:“您先坐着等,叫到号再进来。”

概括一下:“等待队列”用于解决进程等待某个条件的问题,是同步机制,涉及进程阻塞与唤醒。

一、啥是等待队列?先整个接地气的类比

想象你去奶茶店买喝的:

  • 你(进程)想要一杯 “珍珠奶茶”(某个资源,比如锁、数据就绪)
  • 店员说:“珍珠还在煮,您先在旁边等会儿,好了叫您”(条件不满足,进入等待队列)
  • 你找个椅子坐下刷手机(进程睡眠,释放 CPU)
  • 珍珠煮好了,店员喊:“等珍珠奶茶的来拿!”(条件满足,唤醒等待队列里的进程)

这就是等待队列的核心逻辑:让进程在条件不满足时乖乖睡觉,条件满足时再叫醒,避免无效占用 CPU。要是没这机制,进程就得像个愣头青似的,每隔 100ms 就跑过去问 “好了没”(忙轮询),纯属浪费感情(CPU 资源)。

二、核心 “工具”:数据结构 和 函数

内核里管理这个 “候诊室” 得有套规矩,咱们看看关键的 “道具”:

1. 等待队列头(wait_queue_head_t)

这玩意儿就是 “候诊室门口的签到台”,所有要等的进程都得来这儿排队登记。它本质是个链表头。当多个进程等待同一个事件时,这些进程就会被组织成一个等待队列。等待队列头长这样:

/* include/linux/wait.h */
struct wait_queue_head {spinlock_t lock;          // 保护队列的锁(防止多人同时改签到表)struct list_head head;    // 进程链表(排队的人)
};
typedef struct wait_queue_head wait_queue_head_t;

初始化这个 “签到台” 得用init_waitqueue_head()

wait_queue_head_t wq;
init_waitqueue_head(&wq);  // 相当于摆好签到台,准备迎接排队的人

或者你嫌弃上面两个步骤太繁琐,直接用下面这个宏:

#define DECLARE_WAIT_QUEUE_HEAD(name) \struct wait_queue_head name = __WAIT_QUEUE_HEAD_INITIALIZER(name)

既定义了等待队列头同时也完成了初始化,你只需要给他命个名就完事儿了。内核总是熟练的让人省心。

2. 等待队列项(wait_queue_entry_t)

每个来排队的进程都得填写一张“挂号单”,记录自己是谁,等哪个医师。定义简化版长这样:

struct wait_queue_entry {unsigned int            flags;void                    *private;wait_queue_func_t       func; struct list_head        entry; // 链表节点(把自己挂到签到台的链表上)
};/* include/linux/wait.h */
typedef struct wait_queue_entry wait_queue_entry_t; 

动态初始化挂号单(等待队列项)用init_waitqueue_entry()

//例如:
wait_queue_entry_t wait;
init_waitqueue_entry(&wait, current);  // current是当前进程,相当于"我叫张三,来排队"// init_waitqueue_entry函数实现如下
static inline void init_waitqueue_entry(struct wait_queue_entry *wq_entry, struct task_struct *p)
{wq_entry->flags         = 0;wq_entry->private       = p;wq_entry->func          = default_wake_function;
}

,还有种静态初始化等待队列项目方法:DEFINE_WAIT(name)

#define DEFINE_WAIT_FUNC(name, function)                                        \struct wait_queue_entry name = {                                        \.private        = current,                                      \.func           = function,                                     \.entry          = LIST_HEAD_INIT((name).entry),                 \}#define DEFINE_WAIT(name) DEFINE_WAIT_FUNC(name, autoremove_wake_function)

等待队列头(管理节点)与 等待队列项(个体节点)通过链表结构串联起来,关系如下图:
在这里插入图片描述

3. 核心"操作":“坐下等” 和 “叫号”
(1)进程 “坐下等”:wait_event 系列宏

进程主动说: “我要等,条件不满足别叫我” 的操作,最常用的是wait_event()wait_event_interruptible()

  • wait_event(wq_head, condition) 位于 include/linux/wait.h

    wq_head:为等待队列头

    condition:期望的条件

    翻译一下:“我在wq_head这个候诊室等候,条件 condition 满足了再叫我。期间就算有人喊我(发信号),我也不搭理!"((不可中断睡眠,适合必须等到结果的场景))

  • wait_event_interruptible(wq_head, condition) 位于 include/linux/wait.h

    翻译一下:“我情况跟wait_event差不多,但是器件如果有紧急电话(信号)找我,我可能要先走。”(可中断睡眠,返回 0 表示正常唤醒,-ERESTARTSYS 表示被信号打断)

类比场景:wait_event就像死等奶茶,老婆打电话说家里着火了也得等。而wait_event_interruptible是能被信号劝走的等。

wait_event的底层逻辑其实是个循环(防止 “虚假唤醒”,比如护士喊错人):

/* 伪代码理解 */
#define wait_event(wq, condition) \
{struct wait_queue_entry __wq_entry; //创建等待队列项 \init_wait_entry(&__wq_entry, exclusive ? WQ_FLAG_EXCLUSIVE : 0); //初始化等待队列项 \for (;;) { \if (condition) break;  // 条件满足,直接走 \// 利用刚创建的等待队列项,把进程自身挂到等待队列 \add_wait_queue(&__wq_entry, &wq_entry); \// 睡过去,释放CPU \schedule(); \} 
}
(2)唤醒进程:wake_up 系列函数

当条件满足时(比如珍珠煮好了),就得叫醒排队的人,常用的有:

  • wake_up(&wq)

    当前“签到台”的所有人,都起来看看条件是不是满足了,会唤醒wait_eventwait_event_interruptible所在的进程。

  • wake_up_interruptible(&wq)

    只能唤醒当前“签到台”中这些wait_event_interruptible能被劝走的,wait_event这种死等的继续睡。

类比场景

三、注意事项:别在候诊室干蠢事

1. 条件判断必须用循环

内核里可能出现 “虚假唤醒”(比如别的进程误操作唤醒了你),所以wait_event宏本身就是循环。

2. 锁和等待队列要配合好

当操作等待队列头挂的等待队列时,要用好等待队列头wait_queue_head_t里的lock自旋锁,避免竞态。

3. 中断里唤醒要小心

可以在中断里使用wake_up,但绝不能使用wait_event.

4. 别让队列变成 “死胡同”

要是唤醒后条件还是不满足,进程会重新睡过去 —— 但如果没人再唤醒它,就会 “永眠”(死锁)。比如奶茶店店员忘了喊人,你就永远坐在那儿等。所以一定要确保:唤醒操作和条件满足是绑定的

5. wake_up函数本身创建并初始化等待队列项

查看内核代码,你会发现,wake_up本身就创建一个 struct wait_queue_entry 类型的等待队列项。

四、总结(记住关键工具)

等待队列是内核中的智能候诊室,让该等待的进程好好睡,该干活的进程不耽误,CPU资源不浪费,记住几个关键词:

  • 签到台:等待队列头(wait_queue_head_t),所有客人都得来这排队。
  • 挂号单:等待队列项(struct wait_queue_entry),客人排队需要用到的挂号单(虽然这部分代码隐藏在wait_event函数内)。
  • 客人坐下等:wait_event 系列宏,客人拿着挂号单去签到台排队。
  • 服务员叫号:wake_up系列函数,服务员去特定的签到台队伍叫号。

五、拓展(类似功能函数)

其他与进程等待wait_event类似的函数:

wait_event_interrupable()//使得进程处于可中断(TASK_INTERRUPTIBLE)状态,从而睡眠进程可以通过接收信号被唤醒;
wait_event_timeout()//等待满足指定的条件,但是如果等待时间超过指定的超时限制则停止睡眠,可以防止进程永远睡眠;
wait_event_interruptible_timeout() //使得进程睡眠,不但可以通过接收信号被唤醒,也具有超时限制。

与进程唤醒wake_up类似的函数:

#define wake_up(x)                      __wake_up(x, TASK_NORMAL, 1, NULL)
#define wake_up_nr(x, nr)               __wake_up(x, TASK_NORMAL, nr, NULL)
#define wake_up_all(x)                  __wake_up(x, TASK_NORMAL, 0, NULL)
#define wake_up_locked(x)               __wake_up_locked((x), TASK_NORMAL, 1)
#define wake_up_all_locked(x)           __wake_up_locked((x), TASK_NORMAL, 0)
#define wake_up_sync(x)                 __wake_up_sync(x, TASK_NORMAL)#define wake_up_interruptible(x)        __wake_up(x, TASK_INTERRUPTIBLE, 1, 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)
#define wake_up_interruptible_sync(x)   __wake_up_sync((x), TASK_INTERRUPTIBLE)

六、代码举例

/*a simple wait_queue demo*task_1,task_2 added into the wait_queue, if condition is 0.*task_3 change condition to 1, and task_1 task_2 will be wake up*/#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/sched.h>
#include <linux/kthread.h>
#include <linux/delay.h>MODULE_LICENSE("GPL");
MODULE_AUTHOR("cengku@gmail.com");static int condition; // 共享条件
static struct task_struct *task_1;
static struct task_struct *task_2;
static struct task_struct *task_3;DECLARE_WAIT_QUEUE_HEAD(wq); //定义并初始化等待队列头,wqstatic int thread_func_1(void *data)
{int i = 0;while (i++ < 100) {wait_event(wq, condition == 1); //该函数内定义并初始化等待队列项,条件不满足,则将其挂到wqmsleep(1000);printk(">>>>>this task 1\n");}return 0;
}static int thread_func_2(void *data)
{int i = 0;while (i++ < 100) {wait_event(wq, condition == 1);msleep(1000);printk(">>>>>this task 2\n");}return 0;
}
static int thread_func_3(void *data)
{int i = 0;while (i++ < 10) {condition = 0;msleep(2000);printk(">>>>>this task 3\n");condition = 1;wake_up(&wq);msleep(2000);}return 0;
}static int __init mod_init(void)
{printk("=====mod set up===\n");condition = 0;task_1 = kthread_run(thread_func_1, NULL, "thread%d", 1);if (IS_ERR(task_1))printk("**********create thread 1 failed\n");elseprintk("======success create thread 1\n");task_2 = kthread_run(thread_func_2, NULL, "thread%d", 2);if (IS_ERR(task_2))printk("**********create thread 2 failed\n");elseprintk("======success create thread 2\n");task_3 = kthread_run(thread_func_3, NULL, "thread%d", 3);if (IS_ERR(task_3))printk("**********create thread 3 failed\n");elseprintk("======success create thread 3\n");return 0;
}static void __exit mod_exit(void)
{int ret;if (!IS_ERR(task_1)) {ret = kthread_stop(task_1);if (ret > 0)printk("<<<<<<<<, ret);}if (!IS_ERR(task_2)) {ret = kthread_stop(task_2);if (ret > 0)printk("<<<<<<<<, ret);}if (!IS_ERR(task_3)) {ret = kthread_stop(task_3);if (ret > 0)printk("<<<<<<<<, ret);}
}
module_init(mod_init);
module_exit(mod_exit);

用户态-等待队列

用户态代码也有类似内核等待队列的机制,不就是让线程满足条件唤醒,不满足休眠嘛。使用pthread_cond_waitpthread_cond_broadcast 这两个用户态代码实现就行了。如果理解了内核等待队列,这个也类似,且用起来更简单.涉及到的知识点 条件变量pthread_cond_t,唤醒过程使用的信号并非系统信号,注意区分。

一、核心"工具"与用法

两个函数是条件变量机制的核心接口,用于实现线程间的”等待-通知“同步,举例子,当一些线程需要等待某个条件满足时,通过pthread_cond_wait 阻塞,当条件满足时,其他线程可以调用pthread_cond_broadcast(或 pthread_cond_signal)将这些等待的线程唤醒。

基本用法
  • 前提:条件变量必须与互斥锁mutex)配合,确保对"条件"的修改是原子操作
  • 流程
    1. 线程加锁(pthread_mutex_lock)。
    2. 检查条件是否满足:
      • 若不满足,调用 pthread_cond_wait 阻塞等待(此时会自动释放互斥锁,允许其他线程修改条件)。
      • 若满足,执行后续操作。
    3. 其他线程修改条件后,通过 pthread_cond_broadcast 唤醒所有等待的线程(或 pthread_cond_signal 唤醒一个)。
    4. 被唤醒的线程重新获取互斥锁,再次检查条件(防止虚假唤醒),执行后续操作并解锁。
代码举例
#include <pthread.h>
#include <stdio.h>pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; //定义互斥锁变量并静态初始化,比动态初始化更简洁
pthread_cond_t cond = PTHREAD_COND_INITIALIZER; //定义条件变量并静态初始化,用于线程间等待-通知机制
int condition = 0;  // 共享条件// 等待条件的线程
void *waiter(void *arg) {pthread_mutex_lock(&mutex);// 循环检查条件(防止虚假唤醒)while (condition == 0) {printf("等待条件满足...\n");// 阻塞等待,自动释放mutex,被唤醒后重新获取mutexpthread_cond_wait(&cond, &mutex);}printf("条件已满足,执行操作...\n");pthread_mutex_unlock(&mutex);return NULL;
}// 唤醒等待的线程
void *notifier(void *arg) {pthread_mutex_lock(&mutex);condition = 1;  // 修改条件printf("条件已修改,广播唤醒所有等待线程...\n");pthread_cond_broadcast(&cond);  // 唤醒所有等待的线程pthread_mutex_unlock(&mutex);return NULL;
}int main() {pthread_t t1, t2, t3;pthread_create(&t1, NULL, waiter, NULL);pthread_create(&t2, NULL, waiter, NULL);pthread_create(&t3, NULL, notifier, NULL);pthread_join(t1, NULL);pthread_join(t2, NULL);pthread_join(t3, NULL);pthread_mutex_destroy(&mutex);pthread_cond_destroy(&cond);return 0;
}

运行结果:

等待条件满足...
等待条件满足...
条件已修改,广播唤醒所有等待线程...
条件已满足,执行操作...
条件已满足,执行操作...

二、功能类比

  • 内核等待队列:内核态中,进程 / 线程通过 wait_event 等宏等待事件,通过 wake_up 等函数唤醒,用于内核内部同步。
  • pthread 条件变量:用户态中,线程通过 pthread_cond_wait 等待条件,通过 pthread_cond_broadcast 唤醒,用于用户态线程同步。
    两者本质都是 “等待 - 通知” 机制,只是处于不同的运行态(内核态 vs 用户态)。但是用户态的接口更简单,使用起来省心多了,但是本质上也依赖等待队列机制。

三、总结

  • pthread_cond_waitpthread_cond_broadcast 是用户态 pthread 库的函数,用于用户态线程同步。
  • 用法上需配合互斥锁,通过 “等待 - 广播” 机制实现线程间的条件同步。
  • 它们的底层实现依赖内核的等待队列(或类似机制),是内核态同步原语在用户态的封装。
http://www.xdnf.cn/news/1410751.html

相关文章:

  • 基于Spring Cloud Sleuth与Zipkin的分布式链路追踪实战指南
  • 机器学习基础-day01-机器学习介绍
  • syn与quote的简单使用——实现debug
  • 萌宝喂养日志-我用AI做喂养记录小程序1-原型设计
  • 中科大少年班记
  • 编程与数学 03-004 数据库系统概论 10_数据库的实施
  • 【GaussDB】排查应用高可用切换出现数据库整体卡顿及报错自治事务无法创建的问题
  • 基于JavaScript的智能合约平台(Agoric)
  • join怎么用
  • Spring Boot单体项目整合Nacos
  • STAR法则
  • C/C++ 高阶数据结构 —— 二叉搜索树(二叉排序树)
  • 【Linux】系统部分——ELF文件格式与动态库加载
  • 【系统分析师】高分论文:论大数据架构的应用
  • Linux系统比较两个​​已排序文件​​的实用工具之comm
  • 混合润滑表面接触刚度和接触阻尼模型
  • 计算机视觉与深度学习 | 低照度图像处理算法综述:发展、技术与趋势
  • ESP32_实验12_基于光敏传感器的停车场车辆计数系统
  • LeetCode 1855.下标对中的最大距离
  • 基于Python的OCR文字识别系统
  • More Effective C++ 条款19:理解临时对象的来源(Understand the Origin of Temporary Objects)
  • 地信/测绘/遥感就业岗位合集
  • Vue2 与 Vue3 路由钩子的区别及用法详解
  • 事件驱动架构新范式:FastEvent 让领域事件开发变得优雅
  • UVM APB 验证 VIP Agent 逻辑架构与数据流图
  • audioLDM模型代码阅读(三)——变分自编码器VAE
  • LeetCode100-160相交链表【链表介绍】
  • 基于AI的大模型在S2B2C商城小程序中的应用与定价策略自我评估
  • USBX移植(X是eXtended的意思)
  • 【python]变量及简单数据类型