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); 两者需同时满足,进程才会被唤醒

相关推荐
1234567890@world1 小时前
正点原子 ATK-DLRK3568 嵌入式Linux系统开发手册V1.3 详细总结
linux·stm32·单片机
sun_weitao2 小时前
极简版 MCP 工具实现方案
java·linux·服务器
发量惊人的中年网工2 小时前
抗DDoS服务哪家比较靠谱?选DDoS防护服务重点看哪几点?
运维·ddos
Meta Luo3 小时前
页面改版后自动化用例大量失败,应该怎么维护?
运维·功能测试·测试工具·自动化
爱和冰阔落3 小时前
【Linux】线程安全进阶:单例模式、可重入函数与死锁排查
linux·运维·安全·单例模式
dk_tjy4 小时前
迪康网络运维故障自愈的自动化闭环技术路径分析
运维·网络·自动化
先知后行。4 小时前
pimpl
linux
吉普赛的歌4 小时前
如何设置服务器固定时间点自动重启一次
运维·服务器
帷幕落秋4 小时前
SSH远程管理服务
运维·ssh
一世繁华行4 小时前
CAPL浏览器(翻译自官方文档)
运维·服务器·数据库