Linux等待队列原理与实现
当进程要获取某些资源(例如从网卡读取数据)的时候,但资源并没有准备好(例如网卡还没接收到数据),这时候内核必须切换到其他进程运行,直到资源准备好再唤醒进程。
waitqueue (等待队列)
就是内核用于管理等待资源的进程,当某个进程获取的资源没有准备好的时候,可以通过调用 add_wait_queue()
函数把进程添加到 waitqueue
中,然后切换到其他进程继续执行。当资源准备好,由资源提供方通过调用 wake_up()
函数来唤醒等待的进程。
等待队列初始化
要使用 waitqueue
首先需要声明一个 wait_queue_head_t
结构的变量,wait_queue_head_t
结构定义如下:
struct __wait_queue_head {
spinlock_t lock;
struct list_head task_list;
};
waitqueue
本质上是一个链表,而 wait_queue_head_t
结构是 waitqueue
的头部,lock
字段用于保护等待队列在多核环境下数据被破坏,而 task_list
字段用于保存等待资源的进程列表。
可以通过调用 init_waitqueue_head()
函数来初始化 wait_queue_head_t
结构,其实现如下:
void init_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
结构定义如下:
typedef int (*wait_queue_func_t)(wait_queue_t *wait, unsigned mode, int sync, void *key);
struct __wait_queue {
unsigned int flags;
void *private;
wait_queue_func_t func;
struct list_head task_list;
};
下面说明一下各个成员的作用:
-
flags
: 可以设置为WQ_FLAG_EXCLUSIVE
,表示等待的进程应该独占资源(解决惊群现象)。 -
private
: 一般用于保存等待进程的进程描述符task_struct
。 -
func
: 唤醒函数,一般设置为default_wake_function()
函数,当然也可以设置为自定义的唤醒函数。 -
task_list
: 用于连接其他等待资源的进程。
可以通过调用 init_waitqueue_entry()
函数来初始化 wait_queue_t
结构变量,其实现如下:
static inline void init_waitqueue_entry(wait_queue_t *q, struct task_struct *p)
{
q->flags = 0;
q->private = p;
q->func = default_wake_function;
}
也可以通过调用 init_waitqueue_func_entry()
函数来初始化为自定义的唤醒函数:
static inline void init_waitqueue_func_entry(wait_queue_t *q, wait_queue_func_t func)
{
q->flags = 0;
q->private = NULL;
q->func = func;
}
初始化完 wait_queue_t
结构变量后,可以通过调用 add_wait_queue()
函数把等待进程添加到等待队列,其实现如下:
void add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait)
{
unsigned long flags;
wait->flags &= ~WQ_FLAG_EXCLUSIVE;
spin_lock_irqsave(&q->lock, flags);
__add_wait_queue(q, wait);
spin_unlock_irqrestore(&q->lock, flags);
}
static inline void __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
结构之间的关系如下图:
休眠等待进程
当把进程添加到等待队列后,就可以休眠当前进程,让出CPU给其他进程运行,要休眠进程可以通过以下方式:
set_current_state(TASK_INTERRUPTIBLE);
schedule();
代码 set_current_state(TASK_INTERRUPTIBLE)
可以把当前进程运行状态设置为 可中断休眠
状态,调用 schedule()
函数可以使当前进程让出CPU,切换到其他进程执行。
唤醒等待队列
当资源准备好后,就可以唤醒等待队列中的进程,可以通过 wake_up()
函数来唤醒等待队列中的进程。wake_up()
最终会调用 __wake_up_common()
,其实现如下:
static void __wake_up_common(wait_queue_head_t *q,
unsigned int mode, int nr_exclusive, int sync, void *key)
{
wait_queue_t *curr, *next;
list_for_each_entry_safe(curr, next, &q->task_list, task_list) {
unsigned flags = curr->flags;
if (curr->func(curr, mode, sync, key) &&
(flags & WQ_FLAG_EXCLUSIVE) && !--nr_exclusive)
break;
}
}
可以看出,唤醒等待队列就是变量等待队列的等待进程,然后调用唤醒函数来唤醒它们。
- HDUOJ---1867 A + B for you again
- HDUOJ--------1420Prepared for New Acmer
- PowerVM虚拟化环境下 CPU 利用率的监控与探究
- 虚函数中构造函数的调用顺序
- HDUOJ-----4512吉哥系列故事——完美队形I(LCIS)
- go语言mongdb管道使用(二)
- HDUOJ--4565 So Easy!
- Go 语言Map(集合)
- 简单的java实验,涉及到 类继承以及接口问题,方法体的重写(区别于重载)
- java 快速求素数
- 狄斯奎诺(dijkstra 模板)
- HDUOJ---汉洛塔IX
- 小错误系列
- HDUOJ-----4510 小Q系列故事——为什么时光不能倒流
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法