Linux 内核等待队列(Wait Queue)

引言

Linux应用程序调用 read 系统调用时,由于内核未将数据准备好,造成应用程序阻塞,进入睡眠状态;当内核将数据准备好时,又会唤醒进程处理数据。

该流程的背后运行了什么操作呢?

在这之中,有一个关键角色,那就是内核的等待队列(Wait Queue)。

Wait Queue 简介

Linux内核提供了多种方式可以在不同场景或者目的下处理进程睡眠和唤醒。等待队列(Wait Queue)就是其中一种。

等待队列允许一个或多个进程等待某个条件成立,然后在条件满足时被唤醒,例如在设备驱动程序中等待设备状态的改变。

实际案例

gpio-input 中断驱动的实现。

  1. 应用程序打开 /dev/gpio-input 设备,调用 read 读取
  2. 驱动中 read 调用 wait_event_interruptible 阻塞,并让出 CPU
  3. 当 gpio 中断产生时,isr 调用 wake_up_interruptible 唤醒进程
  4. 驱动 read 继续执行,并返回到用户空间
  5. 应用程序 read 完成返回

内部原理

wait_event_interruptible

wait_event_interruptible() 调用 set_current_state(TASK_INTERRUPTIBLE) 将进程设置为可中断睡眠状态,并将当前进程添加到 wait->task_list,接着执行 schedule() 让出 CPU

c 复制代码
#define __wait_event_interruptible(wq_head, condition)				\
	___wait_event(wq_head, condition, TASK_INTERRUPTIBLE, 0, 0,		\
		      schedule())

condition

wait_event_interruptible() 内部是个死循环,如果 condition 不满足,即使被唤醒,也会重新执行睡眠

c 复制代码
#define ___wait_event(wq_head, condition, state, exclusive, ret, cmd)		\
({										\
	__label__ __out;							\
	struct wait_queue_entry __wq_entry;					\
	long __ret = ret;	/* explicit shadow */				\
										\
	init_wait_entry(&__wq_entry, exclusive ? WQ_FLAG_EXCLUSIVE : 0);	\
	for (;;) {								\
		long __int = prepare_to_wait_event(&wq_head, &__wq_entry, state);\
										\
		if (condition)							\
			break;							\
										\
		if (___wait_is_interruptible(state) && __int) {			\
			__ret = __int;						\
			goto __out;						\
		}								\
										\
		cmd;								\
	}									\
	finish_wait(&wq_head, &__wq_entry);					\
__out:	__ret;									\
})

wake_up_interruptible

wake_up_interruptible() 遍历 wait->task_list 链表,将进程状态设置为 TASK_RUNNING

c 复制代码
#define wake_up_interruptible(x)  __wake_up(x, TASK_INTERRUPTIBLE, 1, NULL)

void __wake_up(wait_queue_head_t *q, unsigned int mode, int nr_exclusive, void *key)
{
    unsigned long flags;
    
    spin_lock_irqsave(&q->lock, flags);  // 加锁保护
    __wake_up_common(q, mode, nr_exclusive, 0, key);  // 核心唤醒
    spin_unlock_irqrestore(&q->lock, flags);  // 解锁
}
c 复制代码
┌─────────────────────────────────────────────────────────────────┐
│                     wake_up_interruptible                       │
├─────────────────────────────────────────────────────────────────┤
│  1. spin_lock_irqsave(&q->lock, flags)                          │
│     └── 禁用本地中断,获取等待队列锁                              │
│                                                                  │
│  2. __wake_up_common()                                          │
│     └── 遍历 task_list 链表                                      │
│         └── 对每个 wait_queue_entry:                            │
│             ├── 检查进程状态是否为 TASK_INTERRUPTIBLE            │
│             ├── task->__state = TASK_RUNNING  ← 关键!          │
│             └── try_to_wake_up(task)                            │
│                 └── 将进程加入 CPU 就绪队列(runqueue)           │
│                                                                  │
│  3. spin_unlock_irqrestore(&q->lock, flags)                      │
│     └── 释放锁,恢复中断                                         │
└─────────────────────────────────────────────────────────────────┘
                              ↓
                              ▼
┌─────────────────────────────────────────────────────────────────┐
│                    调度器 (scheduler)                            │
├─────────────────────────────────────────────────────────────────┤
│  - 下一次调度时,pick_next_task() 选择被唤醒的进程                │
│  - context_switch() 切换到该进程执行                             │
└─────────────────────────────────────────────────────────────────┘

代码实现流程

1、初始化一个等待队列头

c 复制代码
init_waitqueue_head(&wait);

2、将进程休眠

将进程添加到等待队列,并给定一个 confition 条件变量,内部是个死循环,confition 不满足时执行 schedule(),进程睡眠;被唤醒后,继续检查 confition,满足时跳出 wait_event_interruptible

c 复制代码
condition = 0;
wait_event_interruptible(wait, condition);

3、唤醒等待队列中的进程

a) condition 置为1,目的是让 wait_event_interruptible() 跳出死循环,不然会再次触发进程睡眠;

b) 遍历等待队列,唤醒进程。

c 复制代码
condition = 1;
wake_up_interruptible(wait);

特殊情况分析

只赋值 condition 为 1,不执行 wake_up_interruptible(wait);

==》进程得不到调度

只执行 wake_up_interruptible(wait); 不赋值 condition 为 1

==》进程被唤醒后立马又执行睡眠,实际上啥事也没干

所以,condition 赋值 为 1 和 调用 wake_up_interruptible(wait); 两者需同时满足,进程才会被唤醒

相关推荐
字节高级特工1 小时前
【Linux】深入理解C语言命令行参数与环境变量
linux·c++·人工智能·后端
linux开发之路1 小时前
C++项目推荐:eBPF+调度器性能分析框架
linux·c++·ebpf·火焰图·调度器
humors2211 小时前
Windows运维与安全场景合集(不定期更新)
大数据·运维·服务器·程序人生·网络安全
SAP上海工博云署1 小时前
2026年中小企业SAP服务商选型技术解析
大数据·运维·数据库·人工智能·信息可视化·运维开发·信息与通信
愿天垂怜1 小时前
【C++脚手架】ffmpeg 库的介绍与使用
linux·服务器·开发语言·c++·ide·git·ffmpeg
WXDcsdn1 小时前
联想服务器使用RAID卡组建RAID(企业服务器解决方案)
运维·服务器
jimy12 小时前
Linux动态加载器,loader,dynamic linker
linux·运维·服务器
三十..2 小时前
华为云全栈:网络/存储/运维高能实战
运维·华为云
kongba0072 小时前
ttyd Web终端安装指南(OpenCloudOS 9)
linux·前端