内核睡眠机制和等待队列
内核睡眠机制:
进程通过睡眠机制释放处理器,使其能够处理其他线程。处理器睡眠的原因可能在于感知数据可用性,或等待资源释放
内核调度器管理要运行的任务列表,这被称为运行队列。睡眠进程不再被调度,因为已将它们从运行队列中移除了。除非改变状态(唤醒),否则睡眠进程将永远不会被执行。进程一旦进入等待状态,就可以释放处理器,一定要确保有条件或其他进程会唤醒它。linux内核提供一组函数和数据结构来简化睡眠机制的实现
等待队列:
等待队列实际上用于处理被阻塞的I/O,以等待特定条件成立,并感知数据或资源可用性
等待队列的数据结构如下:
struct __wait_queue { unsigned int flags; // 用于存储等待队列的标志信息 void *private; // 指向私有数据的指针,可以用于存储与等待队列相关的特定信息 wait_queue_func_t func; // 等待队列的回调函数指针,用于唤醒等待队列中的任务 struct list_head task_list; // 用于将等待队列项连接到等待队列头部的链表中 };
注意task_list字段,正如所看到的,它是一个链表。想要其入睡的每个进程都在该链表中排队(因此被称为等待队列)并进入睡眠状态,直到条件变为真。等待队列可被看做简单的进程链表和锁
处理等待队列,常用到的函数如下:
DECLARE_WAIT_QUEUE_HEAD(name) // 静态声明 wait_queue_head_t my_wait_queue; // 动态声明 init_waitqueue_head(&my_wait_queue); int wait_event_interruptible(wait_queue_head_t q, CONDITION); // 阻塞,如果条件为假,则阻塞等待队列中的当前任务(进程) void wake_up_interruptible(wait_queue_head_t *q); // 解除阻塞,如果上述条件为true,则唤醒在等待队列中休眠的进程
wait_event_interruptible不会持续轮询,而只是在被调用时评估条件。如果条件为假,则将进程进入TASK_INTERRUPTIBLE状态并从运行队列中删除。之后,当每次在队列中调用wake_up_interruptible时,都会重新检查条件。如果wake_up_interruptible运行时发现条件为真,则等待队列中的进程将被唤醒,并将其状态设置为TASK_RUNNING。进程按照它们进入睡眠时候的顺序被唤醒。要唤醒在等待队列中等待的所有进程,应该使用wake_up_interruptible_all。
如果调用了wake_up或wake_up_interruptible,并且条件为FLASE,则什么都不会发生。如果没有调用wake_up或者wake_up_interruptible,进程将永远不会被唤醒
等待队列测试demo:
#include <linux/module.h> #include <linux/kernel.h> #include <linux/init.h> #include <linux/kthread.h> #include <linux/delay.h> #include <linux/sched.h> #include <linux/wait.h> static struct task_struct *my_thread; DECLARE_WAIT_QUEUE_HEAD(my_queue); int condition = 0; // 线程函数 static int my_thread_func(void *data) { while (!kthread_should_stop()) { // 检查条件是否满足 if (condition != 0) { printk(KERN_INFO "Condition is met. Thread wakes up.\n"); break; } // 等待条件满足 printk(KERN_INFO "Waiting for condition to be met...\n"); wait_event_interruptible(my_queue, condition != 0); } printk(KERN_INFO "Thread exits.\n"); return 0; } // 初始化模块 static int __init my_module_init(void) { printk(KERN_INFO "Module initialized.\n"); // 创建线程 my_thread = kthread_run(my_thread_func, NULL, "my_thread"); // 模拟条件变更 msleep(5000); condition = 1; wake_up(&my_queue); return 0; } // 清理模块 static void __exit my_module_exit(void) { // 停止线程 if (my_thread) { kthread_stop(my_thread); } printk(KERN_INFO "Module exited.\n"); } module_init(my_module_init); module_exit(my_module_exit); MODULE_LICENSE("GPL");
在上述示例中,我们首先定义了一个全局等待队列头部 my_queue,和一个条件变量 condition。然后,我们创建了一个内核线程 my_thread,该线程在等待队列上等待条件的满足。线程函数 my_thread_func 中通过循环检查条件是否满足。若条件已满足,则打印消息并跳出循环。否则,使用 wait_event_interruptible() 函数等待条件变量的改变,将该线程阻塞,并设置为可中断等待,以便能够响应内核的信号。在模块初始化函数 my_module_init 中,我们首先创建了 my_thread,然后休眠一段时间,模拟条件的变更。在条件变更后,我们设置 condition 为非零值,并使用 wake_up() 函数唤醒等待在 my_queue 上的线程。最后,在模块退出函数 my_module_exit 中,我们停止了线程,并输出相应的消息。