评论

收藏

[Linux] 一文读懂Linux延时队列工作原理

服务系统 服务系统 发布于:2021-10-21 13:14 | 阅读数:403 | 评论:0

当进程要获取某些资源(例如从网卡读取数据)的时候,但资源并没有准备好(例如网卡还没接收到数据),这时候内核必须切换到其他进程运行,直到资源准备好再唤醒进程。
DSC0000.jpg

当进程要获取某些资源(例如从网卡读取数据)的时候,但资源并没有准备好(例如网卡还没接收到数据),这时候内核必须切换到其他进程运行,直到资源准备好再唤醒进程。
waitqueue (等待队列) 就是内核用于管理等待资源的进程,当某个进程获取的资源没有准备好的时候,可以通过调用 add_wait_queue() 函数把进程添加到 waitqueue 中,然后切换到其他进程继续执行。当资源准备好,由资源提供方通过调用 wake_up() 函数来唤醒等待的进程。
等待队列初始化
要使用 waitqueue 首先需要声明一个 wait_queue_head_t 结构的变量,wait_queue_head_t 结构定义如下:
struct__wait_queue_head{
spinlock_tlock;
structlist_headtask_list;
};
waitqueue 本质上是一个链表,而 wait_queue_head_t 结构是 waitqueue 的头部,lock 字段用于保护等待队列在多核环境下数据被破坏,而 task_list 字段用于保存等待资源的进程列表。
可以通过调用 init_waitqueue_head() 函数来初始化 wait_queue_head_t 结构,其实现如下:
voidinit_waitqueue_head(wait_queue_head_t*q)
{
spin_lock_init(&q->lock);
INIT_LIST_HEAD(&q->task_list);
}
初始化过程很简单,首先调用 spin_lock_init() 来初始化自旋锁 lock,然后调用 INIT_LIST_HEAD() 来初始化进程链表。
向等待队列添加等待进程
要向 waitqueue 添加等待进程,首先要声明一个 wait_queue_t 结构的变量,wait_queue_t 结构定义如下:
typedefint(*wait_queue_func_t)(wait_queue_t*wait,unsignedmode,intsync,void*key);

struct__wait_queue{
unsignedintflags;
void*private;
wait_queue_func_tfunc;
structlist_headtask_list;
};
下面说明一下各个成员的作用:
flags: 可以设置为 WQ_FLAG_EXCLUSIVE,表示等待的进程应该独占资源(解决惊群现象)。
private: 一般用于保存等待进程的进程描述符 task_struct。
func: 唤醒函数,一般设置为 default_wake_function() 函数,当然也可以设置为自定义的唤醒函数。
task_list: 用于连接其他等待资源的进程。
可以通过调用 init_waitqueue_entry() 函数来初始化 wait_queue_t 结构变量,其实现如下:
staticinlinevoidinit_waitqueue_entry(wait_queue_t*q,structtask_struct*p)
{
q->flags=0;
q->private=p;
q->func=default_wake_function;
}
也可以通过调用 init_waitqueue_func_entry() 函数来初始化为自定义的唤醒函数:
staticinlinevoidinit_waitqueue_func_entry(wait_queue_t*q,wait_queue_func_tfunc)
{
q->flags=0;
q->private=NULL;
q->func=func;
}
初始化完 wait_queue_t 结构变量后,可以通过调用 add_wait_queue() 函数把等待进程添加到等待队列,其实现如下:
voidadd_wait_queue(wait_queue_head_t*q,wait_queue_t*wait)
{
unsignedlongflags;

wait->flags&=~WQ_FLAG_EXCLUSIVE;
spin_lock_irqsave(&q->lock,flags);
__add_wait_queue(q,wait);
spin_unlock_irqrestore(&q->lock,flags);
}

staticinlinevoid__add_wait_queue(wait_queue_head_t*head,wait_queue_t*new)
{
list_add(&new->task_list,&head->task_list);
}
add_wait_queue() 函数的实现很简单,首先通过调用 spin_lock_irqsave() 上锁,然后调用 list_add() 函数把节点添加到等待队列即可。
wait_queue_head_t 结构与 wait_queue_t 结构之间的关系如下图:
DSC0001.jpg

waitqueue
休眠等待进程
当把进程添加到等待队列后,就可以休眠当前进程,让出CPU给其他进程运行,要休眠进程可以通过一下方式:
set_current_state(TASK_INTERRUPTIBLE);
schedule();
代码 set_current_state(TASK_INTERRUPTIBLE) 可以把当前进程运行状态设置为 可中断休眠 状态,调用 schedule() 函数可以使当前进程让出CPU,切换到其他进程执行。
唤醒等待队列
当资源准备好后,就可以唤醒等待队列中的进程,可以通过 wake_up() 函数来唤醒等待队列中的进程。wake_up() 最终会调用 __wake_up_common(),其实现如下:
staticvoid__wake_up_common(wait_queue_head_t*q,
unsignedintmode,intnr_exclusive,intsync,void*key)
{
wait_queue_t*curr,*next;

list_for_each_entry_safe(curr,next,&q->task_list,task_list){
unsignedflags=curr->flags;

if(curr->func(curr,mode,sync,key)&&
(flags&WQ_FLAG_EXCLUSIVE)&&!--nr_exclusive)
break;
}
}
可以看出,唤醒等待队列就是变量等待队列的等待进程,然后调用唤醒函数来唤醒它们。
原文链接:https://mp.weixin.qq.com/s/2W9TNepvJ041KU9LvfLvOg

关注下面的标签,发现更多相似文章