等待队列

2015-7-30 chenhui 进程管理

等待队列是 Linux 中十分重要的一个机制,我们可以把一个睡眠状态进程和一个回调函数并封装成一个等待对象,再把这个等待对象放入某个对象(如某文件)的等待队列,使他在该对象可用时,该对象从等待队列中取出一个等待对象,并调用这个对象的回调函数唤醒该进程。


要注意的是,等待对象不一定非要和某个进程关联,因为唤醒进程不是等待对象的职责,等待对象的职责是调用设置的回调函数,所以他实际上是起到监听目标对象的某些状态的效果。


我们可以使用 struct __wait_queue_head 来描述一个等待队列:


struct __wait_queue_head {
 
	spinlock_t lock;
	struct list_head task_list;
};


lock :保存等待队列的锁

task_list :等待队列上的等待对象


task_list 这个字段是一个链表字段,他保存了等待队列上所有等待对象。

一个等待对象使用 struct __wait_queue 来描述。


struct __wait_queue {

	unsigned int flags;
	struct task_struct * task;
	wait_queue_func_t func;
	struct list_head task_list;
};


flags :对象关联的进程是否是互斥访问他等待的对象。如果是互斥,那么只会唤醒他一个,以保证他单独访问等待的对象;如果不是互斥,那么就会从等待队列中继续取出等待对象并调用他的回调函数。

task :关联的进程。可以为空。

func :回调函数。

task_list :链入等待队列。


我们可以用两种方法来初始化一个等待对象。

一. 静态声明并初始化


#define DEFINE_WAIT(name)						\
	wait_queue_t name = {						\
		.task		= current,				\
		.func		= autoremove_wake_function,		\
		.task_list	= {	.next = &(name).task_list,	\
					.prev = &(name).task_list,	\
				},					\
	}


二、静态声明变量,再动态初始化


static inline void init_waitqueue_entry(wait_queue_t *q, 
                                    struct task_struct *p)
{
	q->flags = 0;
	q->task = p;
	q->func = default_wake_function;
}


这两种初始化方法的主要区别如下:

  • 前者是声明变量,同时初始化;后者需要先声明,再调用他初始化。
  • 前者默认关联当前进程;后者关联的进程可以随便指定,也可以不关联
  • 其他字段的初始化。

我们可以调用 add_wait_queue() 和 remove_wait_queue() 把一个等待对象插入一个对待队列:


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

void fastcall remove_wait_queue(wait_queue_head_t *q, wait_queue_t *wait)
{
	unsigned long flags;

	spin_lock_irqsave(&q->lock, flags); 
	__remove_wait_queue(q, wait);
	spin_unlock_irqrestore(&q->lock, flags);
}


__add_wait_queue() 和 __remove_wait_queue() 只是纯粹的链表操作而已。

至于这个 add_wait_queue(),我们能看到他清除了 WQ_FLAG_EXCLUSIVE 位(互斥位),这说明他插入的等待对象不是互斥的;如果想插入互斥的,可以使用 add_wait_queue_exclusive() 函数,他会设置 WQ_FLAG_EXCLUSIVE 位。


既然有把等待对象插入等待队列的函数,那就有从等待队列中唤醒等待对象的函数,这个函数就算是 wake_up() 系列函数:


#define wake_up(x)			__wake_up(x, TASK_UNINTERRUPTIBLE | TASK_INTERRUPTIBLE, 1, NULL)
#define wake_up_nr(x, nr)		__wake_up(x, TASK_UNINTERRUPTIBLE | TASK_INTERRUPTIBLE, nr, NULL)
#define wake_up_all(x)			__wake_up(x, TASK_UNINTERRUPTIBLE | TASK_INTERRUPTIBLE, 0, NULL)
#define wake_up_interruptible(x)	__wake_up(x, TASK_INTERRUPTIBLE, 1, NULL)
#define wake_up_interruptible_nr(x, nr)	__wake_up(x, TASK_INTERRUPTIBLE, nr, NULL)
#define wake_up_interruptible_all(x)	__wake_up(x, TASK_INTERRUPTIBLE, 0, NULL)


他们不约而同地调用了 __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() 取得等待队列的锁后,就开始唤醒了:


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;
		unsigned flags;
		
		//取出当前等待对象
		curr = list_entry(tmp, wait_queue_t, task_list);
		
		//取出flag( 这个成员为1时,表示该进程是互斥进程 )
		flags = curr->flags; 
		
		if (curr->func(curr, mode, sync, key) &&
			//调用等待对象的唤醒函数
		    (flags & WQ_FLAG_EXCLUSIVE) && 
			//若唤醒函数返回1,就表示唤醒成功,那么再判断他是不是一个互斥进程
		    !--nr_exclusive)
			//如果当前等待对象是一个互斥进程,那么 --nr_exclusive
			//如果nr_exclusive为0了,就表示已完成指定数量的等待对象的唤醒
		    
			break;//既然完成了指定数量的等待对象的唤醒,那么就退出函数
	}
}


从 __wake_up_common() 可以看出,等待对象本身并不是专门用来唤醒进程的,只是一般这个回调函数是唤醒一个进程而已。




发表评论:

Copyright ©2015-2016 freehui All rights reserved