内核睡眠机制和等待队列

内核睡眠机制:

进程通过睡眠机制释放处理器,使其能够处理其他线程。处理器睡眠的原因可能在于感知数据可用性,或等待资源释放
内核调度器管理要运行的任务列表,这被称为运行队列。睡眠进程不再被调度,因为已将它们从运行队列中移除了。除非改变状态(唤醒),否则睡眠进程将永远不会被执行。进程一旦进入等待状态,就可以释放处理器,一定要确保有条件或其他进程会唤醒它。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 中,我们停止了线程,并输出相应的消息。
 
 
posted @ 2024-03-24 20:24  lethe1203  阅读(18)  评论(0编辑  收藏  举报