news 2026/4/22 11:06:38

Linux 内核等待队列

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Linux 内核等待队列

让进程在条件不满足时 “休眠等待”,条件满足后被 “唤醒继续运行”。

典型场景:

  • 读数据时没有数据 → 休眠
  • 设备没准备好 → 休眠
  • 中断来了 → 唤醒
  • 资源可用了 → 唤醒

它是 Linux 内核进程同步 / 阻塞最基础、最常用的机制。

等待队列只有两个核心结构体:

1. 等待队列头wait_queue_head_t

  • 相当于 “队列管理器”
  • 管理一整个等待的进程列表
  • 必须先初始化它

2. 等待队列项wait_queue_entry_t

  • 相当于 “正在等待的那个进程”
  • 每个要休眠的进程,都要创建一个队列项
  • 挂到队列头里排队等待

等待队列本质

等待队列 =链表 + 进程休眠机制 + 唤醒回调

作用:让进程主动放弃 CPU 进入休眠,直到某个事件发生,再被重新唤醒并调度

它依赖三个内核基础:

  1. task_struct进程描述符
  2. 进程状态:TASK_INTERRUPTIBLE/TASK_UNINTERRUPTIBLE
  3. 调度器:schedule()

核心数据结构

1. 等待队列头

struct wait_queue_head { spinlock_t lock; struct list_head head; };
  • 一个双向链表,挂所有等待的进程
  • 自旋锁保护链表并发操作

2. 等待队列项

struct wait_queue_entry { unsigned int flags; void *private; // 一般 = current wait_queue_func_t func; // 唤醒函数 struct list_head entry; // 链入队列 };
  • private:指向要休眠的进程task_struct
  • func:唤醒时调用的函数,默认是
    • default_wake_function
    • 常用autoremove_wake_function(唤醒后自动摘链)

最常用 API

定义 + 初始化队列头

// 静态定义+初始化(全局/静态变量最常用) DECLARE_WAIT_QUEUE_HEAD(my_wq); // 或者动态初始化(先定义,再运行时初始化) wait_queue_head_t my_wq; init_waitqueue_head(&my_wq);

进程休眠等待

1. 可中断休眠(推荐 99% 场景)

wait_event_interruptible(wq, condition);
  • 休眠直到condition 为真
  • 可以被信号唤醒
  • 返回 0:正常唤醒;返回 -ERESTARTSYS:被信号打断

2. 不可中断休眠

wait_event(wq, condition);
  • 不响应信号
  • 常用于 IO、磁盘操作

3. 带超时(常用)

wait_event_timeout(wq, condition, timeout);
  • 时间到了自动醒
  • 返回值:0 超时,>0 剩余时间

4. 可中断 + 超时

wait_event_interruptible_timeout(wq, condition, timeout);

唤醒等待的进程

1. 唤醒队列上所有等待进程(最常用)

wake_up(&wq);

2. 唤醒可中断的等待

wake_up_interruptible(&wq);

3. 只唤醒一个等待进程

wake_up_one(&wq);

wake_up_one是 Linux 等待队列里只唤醒队列中一个等待进程的 API,专门用来解决惊群效应(thundering herd)

核心作用

  • wake_up:唤醒所有等待在该队列上的进程
  • wake_up_one:只唤醒一个等待进程

适用场景:

  • 资源一次只允许一个进程使用(如单个缓冲区、单个硬件资源)
  • 不想唤醒一堆进程抢资源,减少调度开销

内部原理:

  1. 加自旋锁保护等待队列
  2. 遍历等待队列链表
  3. 找到第一个非独占 / 合适的等待项
  4. 调用其唤醒函数,把进程设为TASK_RUNNING
  5. 立即停止遍历,只唤醒这一个
  6. 解锁返回

关键要点:

  • 唤醒的是队列里最早等待的那一个(FIFO 顺序)
  • 不保证唤醒哪个,只保证只唤醒一个
  • 仍然要配合条件判断,防止虚假唤醒
  • 进程如果是EXCLUSIVE等待,也会被正确识别

4. 中断上下文里唤醒(必须用这个)

wake_up_interruptible(&wq);

等待队列项 (底层手动控制)

1. 定义一个等待项(绑定当前进程)

DEFINE_WAIT(wait);

2. 初始化等待项

init_wait(&wait);

3. 加入等待队列

add_wait_queue(&wq, &wait);

4. 从队列移除

remove_wait_queue(&wq, &wait);

核心原理流程

进程休眠的底层逻辑(wait_event 内部做了什么)

简化伪代码:

while (!condition) { // 1. 设置进程状态为可中断休眠 set_current_state(TASK_INTERRUPTIBLE); // 2. 把当前进程包装成 wait_queue_entry,加入等待队列 add_wait_queue(&wq_head, &wait); // 3. 主动让出 CPU,调度器切换其他进程 schedule(); // 4. 被唤醒后回到这里,先摘链/清理,再重新检查条件 } // 5. 条件满足,恢复为运行态 set_current_state(TASK_RUNNING); remove_wait_queue(&wq_head, &wait);

关键点:

  • schedule () 一调用,当前进程立刻休眠
  • 进程不再被调度,直到被唤醒
  • 醒来后必须重新检查条件,防止惊群效应

唤醒的底层逻辑(wake_up 做了什么)

spin_lock(&wq_head.lock); // 遍历整个等待队列链表 list_for_each_entry_safe(wait, next, &wq_head.head, entry) { // 调用唤醒函数:把进程状态设回 TASK_RUNNING wait->func(wait, TASK_NORMAL, 0, NULL); } spin_unlock(&wq_head.lock);

唤醒函数default_wake_function做两件事:

  1. private取出task_struct
  2. try_to_wake_up(p)把进程状态改为TASK_RUNNING
  3. 把进程扔回运行队列,等待下一次调度

为什么要 “循环检查条件”?(核心原理)

两个原因:

  1. 惊群效应(thundering herd),wake_up 会唤醒所有等待进程,但只有一个能抢到资源,其他必须继续睡。

  2. 虚假唤醒,进程可能被信号、超时等意外唤醒,条件未必真满足。

所以等待队列内部永远是:

while (!cond) { 休眠; }

进程状态原理

  • TASK_RUNNING:可运行 / 正在运行
  • TASK_INTERRUPTIBLE:可被信号唤醒的休眠
  • TASK_UNINTERRUPTIBLE:不可被信号唤醒(常见于 IO、disk 等待)

等待队列就是把进程从 RUNNING 变成休眠态,再变回来

工作流程

  1. 定义并初始化等待队列头
  2. 进程检查条件
  3. 条件不满足 → 调用 wait_event 休眠
  4. 其他地方(中断 / 驱动 / 内核线程)条件满足 → 调用 wake_up
  5. 进程被唤醒,继续执行

等待队列完整原理流程图

┌─────────────────────────────────────────────────────────────┐ │ 进程 A 开始执行 │ └───────────────────────┬─────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────────┐ │ 检查等待条件 condition 是否成立? │ └───┬─────────────────────────┬───────────────────────────────┘ │ 是 │ 否 ↓ ↓ ┌─────────────────┐ ┌───────────────────────────────────────┐ │ 条件满足,直接 │ │ 1. 初始化等待队列项 wait_entry │ │ 继续往下执行 │ │ 2. 设置进程状态: │ └─────────────────┘ │ TASK_INTERRUPTIBLE / UNINTERRUPTIBLE│ └─────────────┬───────────────────────────┘ ↓ ┌───────────────────────────────────────────┐ │ 将当前进程加入等待队列链表 │ │ add_wait_queue(&wq_head, &wait) │ └───────────────┬───────────────────────────┘ ↓ ┌───────────────────────────────────────────┐ │ 主动调用 schedule() 让出 CPU │ │ ↓ 进程进入休眠,不再被调度 │ └───────────────┬───────────────────────────┘ │ ┌────────────────────┴────────────────────┐ │ │ ↓ ↓ ┌────────────────────┐ ┌─────────────────────────────┐ │ 进程 A 处于休眠 │ │ 其他进程/中断/内核线程 B │ │ 不占用 CPU │ │ 执行并触发事件: │ │ │ │ 1. 条件 condition = 1 │ └────────────────────┘ │ 2. 调用 wake_up(&wq_head) │ └──────────┬────────────────┘ │ ┌────────────────────────┴──────────────────┐ │ wake_up 内部流程: │ │ 1. 加自旋锁保护队列 │ │ 2. 遍历等待队列链表 │ │ 3. 对每个等待项调用唤醒函数 func() │ │ → try_to_wake_up(p) │ │ → 进程状态设为 TASK_RUNNING │ │ → 加入运行队列,等待调度器调度 │ │ 4. 解锁 │ └───────────────┬───────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────┐ │ 调度器下次选中进程 A 恢复运行 │ └───────────────┬─────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────────┐ │ 进程 A 从 schedule() 返回,继续执行 wait_event 内部循环 │ └─────────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────────┐ │ 1. 将进程状态恢复为 TASK_RUNNING │ │ 2. 将自己从等待队列中移除 remove_wait_queue │ └─────────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────────┐ │ 再次检查 condition 是否成立 │ └───────────┬───────────────────────────────────┬─────────────┘ │ 是 │ 否 ↓ ↓ ┌─────────────────────────┐ ┌─────────────────────────────────┐ │ 退出循环,条件满足 │ │ 重新入队 → 再次休眠 → 等待唤醒 │ │ 继续执行业务逻辑 │ └─────────────────────────────────┘ └─────────────────────────┘

完整可运行内核示例

#include <linux/module.h> #include <linux/wait.h> #include <linux/delay.h> #include <linux/kthread.h> // 1. 定义等待队列头 DECLARE_WAIT_QUEUE_HEAD(my_wq); // 条件标志 int cond = 0; // 等待线程:会休眠直到 cond=1 static int wait_thread(void *arg) { printk("等待线程:开始等待...\n"); // 2. 休眠,直到 cond == 1 wait_event_interruptible(my_wq, cond == 1); printk("等待线程:被唤醒啦!条件满足\n"); return 0; } // 唤醒线程:2秒后设置条件并唤醒 static int wake_thread(void *arg) { msleep(2000); // 延时2秒 cond = 1; // 3. 条件满足 wake_up(&my_wq);// 4. 唤醒等待队列 printk("唤醒线程:已发送唤醒\n"); return 0; } static int __init test_init(void) { kthread_run(wait_thread, NULL, "wait_thread"); kthread_run(wake_thread, NULL, "wake_thread"); return 0; } static void __exit test_exit(void) { printk("模块退出\n"); } module_init(test_init); module_exit(test_exit); MODULE_LICENSE("GPL");

关键要点

  1. 队列头 = 管理者,队列项 = 等待者
  2. 不初始化队列头 → 内核崩溃
  3. wait_event自动阻塞 + 自动检查条件,不用自己写循环
  4. wake_up只是唤醒,真正醒来后会再检查一次条件
  5. 中断里唤醒必须用:wake_up_interruptible()
  6. 等待队列:进程主动休眠 + 显式唤醒

等待队列未初始化使用= 内核必崩

直接内核崩溃(panic / OOPS)

进程直接死掉,系统卡死 / 重启。

等待队列头wait_queue_head_t内部有两个关键成员:

struct wait_queue_head { spinlock_t lock; // 自旋锁 struct list_head head; // 链表 };

未初始化 = 这两个成员都是随机值 / 0

  1. lock 未初始化
    • 锁状态非法
    • 加锁 / 解锁直接触发内核 BUG
  2. head 链表未初始化
    • 链表指针是野指针
    • 执行add_wait_queue时直接非法访问内存

只要你调用:

  • wait_event
  • wake_up
  • add_wait_queue

立刻内核崩溃!

总结

等待队列 =睡觉 + 叫醒

  • wait_event睡觉
  • wake_up叫醒
  • wait_queue_head_t睡觉的房间
  • 等待 = 入队列 + 设休眠 + 让出 CPU
  • 唤醒 = 遍历队列 + 改状态为 RUNNING + 扔回运行队列
  • 醒来第一件事:重新检查条件,防止虚假唤醒 / 惊群
  • 整个机制完全基于进程状态 + 链表 + 调度器实现,没有黑魔法
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/22 11:04:36

网盘下载限速终结者:8大平台直链一键获取,解放你的生产力!

网盘下载限速终结者&#xff1a;8大平台直链一键获取&#xff0c;解放你的生产力&#xff01; 【免费下载链接】Online-disk-direct-link-download-assistant 一个基于 JavaScript 的网盘文件下载地址获取工具。基于【网盘直链下载助手】修改 &#xff0c;支持 百度网盘 / 阿里…

作者头像 李华
网站建设 2026/4/22 11:01:29

2026安全左移再进化:从代码扫描到智能体护栏,悬镜灵境AIDR的DevOps全流程集成实践

摘要“安全左移”已提出多年&#xff0c;但在AI智能体开发场景下面临全新挑战。智能体的“源码”不仅包括代码&#xff0c;还包括提示词、模型依赖和工具定义。传统SAST/DAST无法理解这些新型资产。本文基于悬镜灵境AIDR在IDE插件、CI流水线、运行时护栏三个环节的集成实践&…

作者头像 李华