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

相关推荐
A小辣椒7 小时前
TShark:Wireshark CLI 功能
linux
A小辣椒11 小时前
TShark:基础知识
linux
AlfredZhao13 小时前
OCI 明明分配了 200G 系统盘,为什么 df 只看到 30G?
linux·oci
AlfredZhao1 天前
vi 删除指定范围的行,不用再反复按 dd
linux·vi
用户9718356334661 天前
银河麒麟 KY10 申威(SW64) 安装 nginx-1.16.1-2.p01.ky10.sw_64.rpm 详细步骤
linux
猪脚踏浪1 天前
linux 拷贝文件或目录到指定的位置
linux
大树882 天前
金刚石散热越强,管路越先见顶
大数据·运维·服务器·人工智能·ai
摇滚侠2 天前
Linux CentOS7 rpm 安装 MySQL 5.7
linux·运维·mysql
霸道流氓气质2 天前
领域驱动设计(DDD)在 Spring Boot 微服务中的实践指南
运维·spring boot·微服务
bush42 天前
嵌入式linux学习记录十四、术语
linux·嵌入式