文章正文
kernel中信号量实现(二)
上一篇介绍了信号量可以使用waitqueue实现,这一篇主要讲waitqueue的实现,waitqueue故名思义就是等待队列。waitqueue在linux中的实现主要在下面文件中:
include/linux/wait.h kernel/sched/wait.c
一、先看数据结构
struct __wait_queue_head {
spinlock_t lock;
struct list_head task_list;
};
typedef struct __wait_queue_head wait_queue_head_t;从数据结构上看,这个实现比较简单,就是一个自旋锁和一个任务列表。这个自旋锁用于保护列表的读取修改。
二、init_waitqueue_head(&_wait); //变量初始化
#define init_waitqueue_head(wq_head) \
do { \
static struct lock_class_key __key; \
\
__init_waitqueue_head((wq_head), #wq_head, &__key); \
} while (0)这个函数由宏定义实现,这个宏定义中包含两个语句,定义了一个struct lock_class_key结构体以及调用了
__init_waitqueue_head函数初始化这个结构体。下面是这个函数的实现:
void __init_waitqueue_head(wait_queue_head_t *q, const char *name, struct lock_class_key *key)
{
spin_lock_init(&q->lock);
lockdep_set_class_and_name(&q->lock, key, name);
INIT_LIST_HEAD(&q->task_list);
}这个函数先初始化了结构体中的自旋锁,然后设置了lock_class_key结构体,这个结构体用于死锁检测的,最后初始化了waitqueue结构体的列表。
三、挂起等待的实现
DEFINE_WAIT(wait); //定义临时变量 prepare_to_wait(&pgdat->kswapd_wait, &wait, TASK_INTERRUPTIBLE); //设置线程的状态 schedule(); //让出CPU //在获取到信号后从这里运行 finish_wait(&pgdat->kswapd_wait, &wait); //唤醒后清理上次的信号
介绍到这里需要看下另外一个结构体
struct __wait_queue {
unsigned int flags;
void *private;
wait_queue_func_t func; //回调函数
struct list_head task_list; //双向列表的实现,为了挂在wait_queue_head后
};
typedef struct __wait_queue wait_queue_t;这个结构体是表示的是等待队列的一个节点,对应的task会挂在这个结构体上,一般private所指的就是挂起的任务的结构体。在流程的开头出首先定义了临时的变量,这个定义的方式也是我们熟悉的宏方式,我们可以看到,这里的定义就是初始化了一个wait_queue的节点。这里需要注意的一点是这个回调函数,这个回调函数在唤醒的时候会调用,其作用就是将task任务重新挂在到调度器中,使得等待的任务可以继续运行。
typedef int (*wait_queue_func_t)(wait_queue_t *wait, unsigned mode, int flags, void *key);
int default_wake_function(wait_queue_t *wait, unsigned mode, int flags, void *key);
int default_wake_function(wait_queue_t *curr, unsigned mode, int wake_flags,
void *key)
{
return try_to_wake_up(curr->private, mode, wake_flags);
}
int autoremove_wake_function(wait_queue_t *wait, unsigned mode, int sync, void *key)
{
int ret = default_wake_function(wait, mode, sync, key);
if (ret)
list_del_init(&wait->task_list);
return ret;
}
int autoremove_wake_function(wait_queue_t *wait, unsigned mode, int sync, void *key);
#define DEFINE_WAIT_FUNC(name, function) \
wait_queue_t name = { \
.private = current, \
.func = function, \
.task_list = LIST_HEAD_INIT((name).task_list), \
}
#define DEFINE_WAIT(name) DEFINE_WAIT_FUNC(name, autoremove_wake_function)下面我们看下prepare_to_wait的实现
prepare_to_wait(wait_queue_head_t *q, wait_queue_t *wait, int state)
{
unsigned long flags;
wait->flags &= ~WQ_FLAG_EXCLUSIVE;
spin_lock_irqsave(&q->lock, flags);
if (list_empty(&wait->task_list))
__add_wait_queue(q, wait);
set_current_state(state);
spin_unlock_irqrestore(&q->lock, flags);
}
EXPORT_SYMBOL(prepare_to_wait);这个函数就是简单的将临时定义的wait_queue挂在原来定义好的(非临时的,其他线程可以获得)wait_queue_head上。下面就是使用schedule方法将当前的线程的CPU让出去,进入等待的时间,等待其他线程的唤醒。
void finish_wait(wait_queue_head_t *q, wait_queue_t *wait)
{
unsigned long flags;
__set_current_state(TASK_RUNNING);
/*
* We can check for list emptiness outside the lock
* IFF:
* - we use the "careful" check that verifies both
* the next and prev pointers, so that there cannot
* be any half-pending updates in progress on other
* CPU's that we haven't seen yet (and that might
* still change the stack area.
* and
* - all other users take the lock (ie we can only
* have _one_ other CPU that looks at or modifies
* the list).
*/
if (!list_empty_careful(&wait->task_list)) {
spin_lock_irqsave(&q->lock, flags);
list_del_init(&wait->task_list);
spin_unlock_irqrestore(&q->lock, flags);
}
}这个函数执行的时候表示当前线程已经被唤醒了,这个函数做的动作就是清理,这里的动作与回调函数基本一致,至于为什么这么做,就是为了防止多核的影响。
四、触发原理实现
上一篇中使用了wake_up_interruptible函数唤醒kswad,这个函数也是由宏实现:
#define wake_up_interruptible(x) __wake_up(x, TASK_INTERRUPTIBLE, 1, NULL)
//这里1表示唤醒队列中的第一个任务。从实现代码中就可以看出来。
void __wake_up(wait_queue_head_t *q, unsigned int mode,
int nr_exclusive, void *key)
{
unsigned long flags;
spin_lock_irqsave(&q->lock, flags);
__wake_up_common(q, mode, nr_exclusive, 0, key);
spin_unlock_irqrestore(&q->lock, flags);
}
static void __wake_up_common(wait_queue_head_t *q, unsigned int mode,
int nr_exclusive, int wake_flags, 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, wake_flags, key) &&
(flags & WQ_FLAG_EXCLUSIVE) && !--nr_exclusive)
break;
}
}前面已经说了,在函数指针中,最终调用了sched核心中的try_to_wake_up,唤醒挂在队列上的任务。
最后我们总结一下,用一幅图示意出来,应该比较好理解的

Dec. 5, 2018, 2:55 a.m. 作者:zachary 分类:Linux相关 阅读(3519) 评论(0)