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); // 唤醒完成量
}

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐