Linux内核的等待机制
1. 引言
内核的等待机制主要是用与让某个当前运行的程序流让出CPU,然后等待某些条件成立或者发生后再次执行的手段。内核主要有等待队列和完成量两种机制。
2. 等待机制的基本概念
当一个进程需要等待某个条件满足时,它会从“运行态”或“就绪态”进入“阻塞态”(或“等待态”)。内核负责将此进程放入相应的等待队列,并记录其等待的具体事件。一旦事件发生(例如,数据到达、锁被释放),内核会唤醒等待队列中的一个或多个进程,将其状态变回“就绪态”,等待调度器再次分配CPU时间片。
关键组件包括:
- 等待队列(Wait Queue):内核数据结构,用于链接所有等待同一事件的进程描述符。
- 等待事件(Wait Event):进程所等待的具体条件,如文件可读、信号量值大于0、定时器超时等。
- 唤醒操作(Wake-up):当事件发生时,内核遍历等待队列,将符合条件的进程标记为可运行。
3. 内核常见的等待机制
3.1 等待队列
等待队列是操作系统内核中一个核心的数据结构,它是一个由正在等待某个特定事件或资源的进程组成的链表。你可以把它想象成一个“停车场”,进程在这里暂时休息,直到它们等待的事情发生。
3.1.1 等待队列初始化
等待队列有两种初始化的方式,分别是静态初始化和动态初始化。无论是哪一种,等待队列使用前都需要初始化。
// 静态初始化
DECLARE_WAIT_QUEUE_HEAD(my_wait_queue);
// 动态初始化
wait_queue_head_t my_wait_queue; // 1. 先声明
init_waitqueue_head(&my_wait_queue); // 2. 后初始化
3.1.2 等待端
以下是常见的等待函数
|
函数 |
退出条件 |
备注 |
|
wait_event(wq_head, condition) |
condition成立 |
改函数不受信号打断,如果condition不成立则一直等待 |
|
wait_event_interruptible(wq_head, condition) |
condition成立 被信号打断 |
可被信号打断 |
|
wait_event_timeout(wq_head, condition, timeout) |
condition成立 超过timeout等待时间 |
返回0超时,>=0 返回剩余的时间 |
|
wait_event_interruptible_timeout(wq_head, condition, timeout) |
condition成立 timeout等待时间 被信号打断 |
3.1.3 唤醒端
|
函数 |
备注 |
|
wake_up(&wq) |
唤醒等待队列 |
|
wake_up_interruptible(&wq) |
唤醒被wait_event_interruptible休眠的队列 |
|
wake_up_all(&wq) |
唤醒所有被wake_up休眠等待队列 |
|
wake_up_interruptible_all(&wq) |
唤醒所有被wake_up_interruptible_all休眠等待队列 |
3.1.4 例子
wait_queue_head_t wait; // 定义一个等待队列
static u8 flag = 0;
void main(void)
{
init_waitqueue_head(&wait); // 初始化等待队列
wait_event_interruptible_timeout(wait,
flag == 3,
1000); // 进程休眠,可以被信号唤醒,超时唤醒,必须满足flag等于3才正常唤醒
}
void isr0(void)
{
flag |= (1 << 0);
wake_up_interruptible(&wait);
}
void isr1(void)
{
flag |= (1 << 1);
wake_up_interruptible(&wait);
}
上面的例子就是一个等待的典型用法,主进程等待事件的发生,中断程序则将事件置位。
┌─────────────────────────────────────────────────────────────────┐
│ 系统启动 │
└────────────────────────┬──────────────────────────────────────┘
↓
┌──────────────────────┐
│ 主程序/内核线程启动 │
│ flag = 0 │
└──────────┬───────────┘
↓
┌──────────────────────┐
│ 调用 │
│ wait_event_ │
│ interruptible_timeout│
└──────────┬───────────┘
↓
┌──────────────────────┐
│ 检查条件 flag == 3? │
└─────┬─────────┬──────┘
│ │
┌────────┘ └────────┐
↓ 否(flag≠3) ↓ 是(flag==3)
┌──────────────────┐ ┌──────────────────┐
│ 进入睡眠状态 │ │ 唤醒成功 │
│ 进程让出CPU │ │ 继续执行后续代码 │
└────────┬─────────┘ └──────────────────┘
↓
┌──────────────────┐
│ 等待以下事件之一 │
│ ① 中断发生 │
│ ② 超时(1000jiffies)│
│ ③ 信号中断 │
└────────┬─────────┘
↓
等待事件触发...
↓
┌──────────────────┐
│ 事件触发 │
└────────┬─────────┘
↓
┌──────────────────┐
│ 进程被唤醒 │
│ 重新检查条件 │
└────────┬─────────┘
↓
回到条件检查处(循环)
3.2 完成量
完成量可以理解成是简化版的等待队列,它与等待队列的区别是等待队列可以等待多个条件发生,而完成量只能等待一个条件。
3.2.1 完成量初始化
完成量初始化也有静态初始化和动态初始化,也是要初始化后才可以使用。
// 静态初始化
static DECLARE_COMPLETION(my_comp);
// 动态初始化
struct completion my_comp; // 动态分配(如在栈上或堆中)
/* 动态初始化一个完成量 */
init_completion(&my_comp);
3.2.2 等待端
|
函数 |
退出条件 |
|
wait_for_completion(&comp) |
被complete唤醒 |
|
wait_for_completion_interruptible(&comp) |
被complete唤醒 信号打断 |
|
wait_for_completion_timeout(&comp, timeout) |
被complete唤醒 timeout超时 |
|
wait_for_completion_interruptible_timeout(&comp, timeout) |
被complete唤醒 timeout超时 信号打断 |
3.2.3 唤醒端
|
函数 |
备注 |
|
complete(&comp) |
唤醒等待的线程 |
|
complete_all(&comp) |
唤醒全部等待的线程 |
3.2.4 例子
struct completion my_comp; // 动态分配(如在栈上或堆中)
void main(void)
{
/* 动态初始化一个完成量 */
init_completion(&my_comp);
wait_for_completion_interruptible_timeout(&my_comp,1000); // 可被信号唤醒,超时唤醒
}
void isr(void)
{
complete(&my_comp); // 唤醒完成量
}
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐

所有评论(0)