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

相关推荐
阿黎02143 分钟前
Linux驱动
linux
tjjingpan7 分钟前
HCIP-Datacom Core Technology V1.0_20 IPv6概述
linux·服务器·网络
云泽80824 分钟前
Linux基础开发工具(三):Vim高级配置、Swap恢复机制与Sudo权限白名单详解
linux·运维·vim
Urbano29 分钟前
服装厂入局自动化开袋设备:市场前景、机型选型与落地实效科普
运维·自动化
CodexDave30 分钟前
Python 自动化接单实战(九):Windows 免环境交付如何打包与诊断
windows·python·自动化·python自动化·pyinstaller·软件交付·windows打包
tju233332 分钟前
Gitee Test是什么:从测试资产管理到自动化执行的工程化实践
运维·gitee·自动化
名字还没想好☜38 分钟前
Kubernetes Pod 调度实战:nodeSelector、亲和性与 taint/toleration 把 Pod 放到指定节点
运维·云原生·容器·kubernetes·调度
qetfw43 分钟前
CentOS 7 vsftpd.conf 配置文件详解:监听、用户、权限、被动模式与 TLS
linux·运维·centos·ftp·vsftpd
DB哥讲数据库1 小时前
【windows安装sql server教程】25步图文详解(附安装包)
windows
风曦Kisaki1 小时前
Kubernetes(K8s)笔记Day04:控制器(ReplicaSet 与Deployment),滚动更新及回滚,滚动更新策略,Pod 的 DNS 策略
linux·运维·笔记·docker·容器·kubernetes