目录

1、等待队列结构和 API

1.1、等待队列头

1.1.1、定义等待队列头

1.1.2、初始化等待队列头

1.1.3、定义等待队元素

1.1.4、添加/移除等待队列

1.1.5、等待事件并睡眠

1.1.6、唤醒队列

2、等待队列(头)用法

2.1、 睡眠

2.1.1、方式一

2.1.2、方式二

2.2、 唤醒

3、等待队列相关源码分析

3.1、睡眠

3.2、唤醒


 

等待队列在内核中有着极为广泛的应用,它主要和进程休眠以及进程调度强耦合;首先需要了解何为睡眠和唤醒;

一个进程(task)有几种状态(详见Linux进程描述符task_struct结构体详解--Linux进程的管理与调度(一)(转)),最为常见的是 RUNNING,和睡眠,处于RUNNING 状态的进程,能够被调度器调度运行,而处于休眠状态的进程将不会被调度运行;而睡眠状态下的进程又细分为两类:

TASK_INTERRUPTIBLE:能够收到信号唤醒,也能够在条件满足的时候唤醒

TASK_UNINTERRUPTIBLE:不接受信号,只能够被条件唤醒

等待队列的作用就是将进程进入睡眠和唤醒的一种手段。

对应到用户空间的行为,体现在阻塞和非阻塞的操作,比如,你期望读 1024 Bytes 的串口数据,如果当前串口收到的 buffer 中,不足 1024 Bytes 的数据,此刻的 read 存在两种逻辑:

1、立即返回 — 非阻塞

2、不返回,等数据够了在返回 — 阻塞

这种操作也和等待队列相关。

这里需要注意的一点是,阻塞并不代表低效,当获取不到资源的时候,阻塞,其实是其挂起进程,让其睡眠,从调度器的运行队列中移走,暂时不得到 CPU 的调度,待条件满足后被唤醒,挂入运行队列,被 CPU 调用继续运行。

 

1、等待队列结构和 API

内核中,提供对进程的睡眠和唤醒的相关操作,使用等待队列来进行表征,它常常在驱动程序中被使用到,它以队列作为基础的数据结构,与进程调度部分紧密结合;

 

1.1、等待队列头

在 Linux 中, 一个等待队列由一个"等待队列头"来管理, 一个 wait_queue_head_t 类型的结构, 定义在<linux/wait.h>中. 一个等待队列头可被定义和初始化!

 

1.1.1、定义等待队列头

wait_queue_head_t  my_queue;

 

1.1.2、初始化等待队列头

init_waitqueue_head(&my_queue);

也可以使用 Kernel 定义的快捷方式来定义并初始化一个等待队列头:

DECLARE_WAIT_QUEUE_HEAD(name);

 

1.1.3、定义等待队元素

DECLARE_WAITQUEUE(name, tsk);

tsk一般为当前进行current. 这个宏定义并初始化一个名为name的等待队列.

 

1.1.4、添加/移除等待队列

void add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);
void remove_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);

第一个将等待队列元素 wait 添加到等待队列头部 q 指向的双向链表

后者用于将等待队列元素 wait 从 q 中移除

 

1.1.5、等待事件并睡眠

Linux 内核中睡眠的最简单方式是一个宏定义, 称为 wait_event(有几个变体); 它结合了处理睡眠的细节和进程在等待的条件的检查. wait_event 的形式是:

wait_event(queue, condition)
wait_event_interruptible(queue, condition)
wait_event_timeout(queue, condition, timeout)
wait_event_interruptible_timeout(queue, condition, timeout)

queue 是等待队列头,condition 是条件,如果调用 wait_event 前 condition == 0 ,则调用 wait_event 之后,当前进程就会休眠;

wait_event:将当前进程的状态设置为 TASK_UNINTERRUPTIBLE,然后 schedule()
wait_event_interruptible:将当前进程的状态设置为 TASK_INTERRUPTIBLE,然后 schedule()
wait_event_timeout:将当前进程的状态设置为 TASK_UNINTERRUPTIBLE,然后 schedule_timeout()
wait_event_interruptible_timeout:将当前进程的状态设置为 TASK_INTERRUPTIBLE, 然后 schedule_timeout()

TASK_INTERRUPTIBLE 与 TASK_UNINTERRUPTIBLE 区别在于,它的休眠是否会被信号打断,别的进程发来一个信号比如 kill ,TASK_INTERRUPTIBLE 就会醒来去处理。然而 TASK_UNINTERRUPTIBLE 不会。schedule(),进程调度,而schedule_timeout()进行调度之后,一定时间后自动唤醒。

 

1.1.6、唤醒队列

对于已经睡眠的的进程,可以调用接口去唤醒他们:

void wake_up(wait_queue_head_t *queue);
void wake_up_interruptible(wait_queue_head_t *queue);

唤醒操作会唤醒以 queue 为等待队列头的所有进程;

wake_up 应该与 wait_event 或者 wait_event_timeout 配对使用,同样的,wake_up_interruptible 应该与 wait_event_interruptible  和 wait_event_interruptible_timeout 配对使用;

如果调用 wake_up 去唤醒一个使用 wait_event 等,进入休眠的进程,唤醒之后,它会判断 condition 是否为真,如果还是假的继续睡眠。

 

2、等待队列(头)用法

下面来叙述使用上述的 APIs 和结构,来达成睡眠和唤醒的调用方式和流程:

2.1、 睡眠

根据前面的 APIs 的介绍,使用等待队列实现睡眠,需要先定义个等待队列头,然后将等待队列成员挂上去:

 

2.1.1、方式一

自动档版本

// 先定义等待队列头
wait_queue_head_t xxx_wait_head;
init_waitqueue_head(&xxx_wait_head);

.....

// 定义等待队列,current 是当前进程
DECLARE_WAITQUEUE(xxx_wait, current);
add_wait_queue(&xxx_wait_head, &xxx_wait);

.....

// 如果条件 xxx_condition 为 1 就不睡了,否则 xxx_wait_head 挂接的等待队列进入睡眠
wait_event_interruptible(&xxx_wait_head, xxx_condition)

 

2.1.2、方式二

手动档版本

// 先定义等待队列头
wait_queue_head_t xxx_wait_head;
init_waitqueue_head(&xxx_wait_head);

.....

// 定义等待队列,current 是当前进程
DECLARE_WAITQUEUE(xxx_wait, current);
add_wait_queue(&xxx_wait_head, &xxx_wait);

.....

// 设置当前进程为可打断的睡眠 TASK_INTERRUPTIBLE
set_current_state(TASK_INTERRUPTIBLE)

.....

// 使 CPU 调度其他进程
schedule() [ 或者 schedule_timeout() ]

 

2.2、 唤醒

唤醒操作没有那么多唧唧歪歪,直接调用接口:

wake_up(&wq_head)

wake_up_interruptible(&wq_head)

即可唤醒等待队列头上的所有进程。不再多说了。

 

3、等待队列相关源码分析

3.1、睡眠

我们以 wait_event 作为参考,来分析具体的代码实现,其他的都差不多的套路:

#define wait_event(wq, condition) 					\
do {									\
	if (condition)	 						\
		break;							\
	__wait_event(wq, condition);					\
} while (0)

条件满足,即 条件不为0,则不睡眠,否则进入 __wait_event

#define __wait_event(wq, condition) 					\
do {									\
	DEFINE_WAIT(__wait);						\
									\
	for (;;) {							\
		prepare_to_wait(&wq, &__wait, TASK_UNINTERRUPTIBLE);	\
		if (condition)						\
			break;						\
		schedule();						\
	}								\
	finish_wait(&wq, &__wait);					\
} while (0)

先通过 DEFINE_WAIT 来定义一个等待队列元素, private 元素指向了当前进程的 current 指针,并进行必要的初始化:

#define DEFINE_WAIT(name)						\
	wait_queue_t name = {						\
		.private	= current,				\
		.func		= autoremove_wake_function,		\
		.task_list	= LIST_HEAD_INIT((name).task_list),	\
	}

然后调用到 prepare_to_wait

void fastcall
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);
	/*
	 * don't alter the task state if this is just going to
	 * queue an async wait queue callback
	 */
	if (is_sync_wait(wait))
		set_current_state(state);
	spin_unlock_irqrestore(&q->lock, flags);
}

等待队列加入到等待队列头,设置当前进程为 TASK_UNINTERRUPTIBLE

然后后面调用到了 schedule() 进行 CPU 调度。

最后调用到了 finish_wait,当调用到这里来的时候,其实说明进程已经醒了,并满足了条件:

void fastcall finish_wait(wait_queue_head_t *q, wait_queue_t *wait)
{
	unsigned long flags;
 
	__set_current_state(TASK_RUNNING);
 
	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);
	}
}

所以这里设置他为 RUNNING

 

3.2、唤醒

唤醒接口会调用到 __wake_up

void fastcall __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);
}

走到 __wake_up_common

static void __wake_up_common(wait_queue_head_t *q, unsigned int mode,
			     int nr_exclusive, int sync, void *key)
{
	struct list_head *tmp, *next;
 
	list_for_each_safe(tmp, next, &q->task_list) {
		wait_queue_t *curr = list_entry(tmp, wait_queue_t, task_list);
		unsigned flags = curr->flags;
 
		if (curr->func(curr, mode, sync, key) &&
				(flags & WQ_FLAG_EXCLUSIVE) && !--nr_exclusive)
			break;
	}
}

这个时候去遍历链表,并调用睡眠时候注册挂接上去的那个 func,也就是 autoremove_wake_function 

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;
}

走到 default_wake_function

int default_wake_function(wait_queue_t *curr, unsigned mode, int sync,
			  void *key)
{
	return try_to_wake_up(curr->private, mode, sync);
}

调用 try_to_wake_up 去执行唤醒

 

参考文献

https://blog.csdn.net/lizuobin2/article/details/51785812

Logo

CSDN联合极客时间,共同打造面向开发者的精品内容学习社区,助力成长!

更多推荐