Linux等待队列分析


   在Linux驱动程序中,可以使用等待队列(Wait Queue)来实现阻塞进程的唤醒。等待队列很早就作为一个基本的功能单位出现在Linux内核里了,它以队列为基础数据结构,与进程调度机制紧密结合,可以用来同步对系统资源的访问。

说明

kernel版本:4.14.111

数据结构

struct wait_queue_head {
	spinlock_t		lock;
	struct list_head	head;
};
typedef struct wait_queue_head wait_queue_head_t;

   其中,自旋锁lock是用来防止并发访问,task_list字段是等待队列中等待的进程链表的头。

/*
 * A single wait-queue entry structure:
 */
struct wait_queue_entry {
	unsigned int		flags;
	void			*private;		(1)
	wait_queue_func_t	func;		(2)
	struct list_head	entry;		(3)
};

1)通常指向当前任务task_struct数据结构
2)回调函数
3)挂入等待队列的链表

等待队列操作

定义并初始化“等待队列头”

1)动态初始化

wait_queue_head_t my_queue;  
init_waitqueue_head(&my_queue);  //会将自旋锁初始化为未锁,等待队列初始化为空的双向循环链表。

2)静态定义

DECLARE_WAIT_QUEUE_HEAD (my_queue); 

等待事件

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

wait_event(queue,condition);		//等待以queue为等待队列头等待队列被唤醒,condition必须满足,否则阻塞  
wait_event_interruptible(queue,condition);	//可被信号打断  
wait_event_timeout(queue,condition,timeout);	//阻塞等待的超时时间,时间到了,不论condition是否满足,都要返回  
wait_event_interruptible_timeout(queue,condition,timeout) 

1)首先判断条件是否满足,满足则直接返回
2)条件不满足则执行__wait_event

/* wait_event */
#define __wait_event(wq_head, condition)					\
	(void)___wait_event(wq_head, condition, TASK_UNINTERRUPTIBLE, 0, 0,	\
			    schedule())

/* wait_event_interruptible */
#define __wait_event_interruptible(wq_head, condition)				\
	___wait_event(wq_head, condition, TASK_INTERRUPTIBLE, 0, 0,		\
		      schedule())

/* wait_event_timeout */
#define __wait_event_timeout(wq_head, condition, timeout)			\
	___wait_event(wq_head, ___wait_cond_timeout(condition),			\
		      TASK_UNINTERRUPTIBLE, 0, timeout,				\
		      __ret = schedule_timeout(__ret))

   上述分别为wait_event 、wait_event_interruptible 、wait_event_timeout 宏。可以看出,最终都是调用了___wait_event接口。
   区别仅仅在于传递的进程状态,以及执行的调度函数。

#define ___wait_event(wq_head, condition, state, exclusive, ret, cmd)		\
({										\
	__label__ __out;							\
	struct wait_queue_entry __wq_entry;					\			(1)
	long __ret = ret;	/* explicit shadow */				\
										\
	init_wait_entry(&__wq_entry, exclusive ? WQ_FLAG_EXCLUSIVE : 0);	\	(2)
	for (;;) {								\
		long __int = prepare_to_wait_event(&wq_head, &__wq_entry, state);\	(3)
										\
		if (condition)							\							(4)
			break;							\
										\
		if (___wait_is_interruptible(state) && __int) {			\
			__ret = __int;						\
			goto __out;						\
		}								\
										\
		cmd;								\								(5)
	}									\
	finish_wait(&wq_head, &__wq_entry);					\					(6)
__out:	__ret;									\
})

1)定义一个等待队列
2)初始化该等待队列,代码实现如下:

void init_wait_entry(struct wait_queue_entry *wq_entry, int flags)
{
	wq_entry->flags = flags;
	wq_entry->private = current;			//private指针设置为当前进程task_struct
	wq_entry->func = autoremove_wake_function;	//设置默认回调函数
	INIT_LIST_HEAD(&wq_entry->entry);			//初始化等待队列链表
}

3)for循环内调用prepare_to_wait_event

long prepare_to_wait_event(struct wait_queue_head *wq_head, struct wait_queue_entry *wq_entry, int state)
{
	unsigned long flags;
	long ret = 0;

	spin_lock_irqsave(&wq_head->lock, flags);
	if (unlikely(signal_pending_state(state, current))) {
		list_del_init(&wq_entry->entry);
		ret = -ERESTARTSYS;
	} else {
		if (list_empty(&wq_entry->entry)) {
			if (wq_entry->flags & WQ_FLAG_EXCLUSIVE)
				__add_wait_queue_entry_tail(wq_head, wq_entry);
			else
				__add_wait_queue(wq_head, wq_entry);
		}
		set_current_state(state);
	}
	spin_unlock_irqrestore(&wq_head->lock, flags);

	return ret;
}

if (list_empty(&wq_entry->entry))判断当前等待队列链表是否初始化,如果未初始化,则将当前等待队列链表加入等待队列头链表中。
set_current_state,设置当前进程状态,wait_event为TASK_UNINTERRUPTIBLE
wait_event_interruptible为TASK_INTERRUPTIBLE
在这里插入图片描述
4)调用完prepare_to_wait_event后,再次判断条件是否满足,如果条件满足,则跳出for循环。
5)如果条件不满足,则执行cmd接口,对于wait_event,为schedule,对于wait_event_timeout,为schedule_timeout。即主动执行调度。
6)跳出循环,设置当前进程状态为TSAK_RUNNING。将等待队列从等待队列头链表中删除。对于wait_event,这里是进程被wake_up,且条件满足,才会执行到这里。对于wait_event_timeout,可能是条件满足,也可能是timeout超时,根据返回值判断。

唤醒队列

#define wake_up(x)			__wake_up(x, TASK_NORMAL, 1, NULL)
#define wake_up_nr(x, nr)		__wake_up(x, TASK_NORMAL, nr, NULL)
#define wake_up_all(x)			__wake_up(x, TASK_NORMAL, 0, NULL)
#define wake_up_locked(x)		__wake_up_locked((x), TASK_NORMAL, 1)
#define wake_up_all_locked(x)		__wake_up_locked((x), TASK_NORMAL, 0)

#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)
#define wake_up_interruptible_sync(x)	__wake_up_sync((x), TASK_INTERRUPTIBLE, 1)
void __wake_up(struct wait_queue_head *wq_head, unsigned int mode,
			int nr_exclusive, void *key)
{
	__wake_up_common_lock(wq_head, mode, nr_exclusive, 0, key);
}

static void __wake_up_common_lock(struct wait_queue_head *wq_head, unsigned int mode,
			int nr_exclusive, int wake_flags, void *key)
{
	unsigned long flags;
	wait_queue_entry_t bookmark;

	bookmark.flags = 0;
	bookmark.private = NULL;
	bookmark.func = NULL;
	INIT_LIST_HEAD(&bookmark.entry);

	spin_lock_irqsave(&wq_head->lock, flags);
	nr_exclusive = __wake_up_common(wq_head, mode, nr_exclusive, wake_flags, key, &bookmark);
	spin_unlock_irqrestore(&wq_head->lock, flags);

	while (bookmark.flags & WQ_FLAG_BOOKMARK) {
		spin_lock_irqsave(&wq_head->lock, flags);
		nr_exclusive = __wake_up_common(wq_head, mode, nr_exclusive,
						wake_flags, key, &bookmark);
		spin_unlock_irqrestore(&wq_head->lock, flags);
	}
}

static int __wake_up_common(struct wait_queue_head *wq_head, unsigned int mode,
			int nr_exclusive, int wake_flags, void *key,
			wait_queue_entry_t *bookmark)
{
	wait_queue_entry_t *curr, *next;
	int cnt = 0;

	if (bookmark && (bookmark->flags & WQ_FLAG_BOOKMARK)) {
		curr = list_next_entry(bookmark, entry);

		list_del(&bookmark->entry);
		bookmark->flags = 0;
	} else
		curr = list_first_entry(&wq_head->head, wait_queue_entry_t, entry);

	if (&curr->entry == &wq_head->head)
		return nr_exclusive;

	list_for_each_entry_safe_from(curr, next, &wq_head->head, entry) {	(1)
		unsigned flags = curr->flags;
		int ret;

		if (flags & WQ_FLAG_BOOKMARK)
			continue;

		ret = curr->func(curr, mode, wake_flags, key);					(2)
		if (ret < 0)
			break;
		if (ret && (flags & WQ_FLAG_EXCLUSIVE) && !--nr_exclusive)
			break;

		if (bookmark && (++cnt > WAITQUEUE_WALK_BREAK_CNT) &&
				(&next->entry != &wq_head->head)) {
			bookmark->flags = WQ_FLAG_BOOKMARK;
			list_add_tail(&bookmark->entry, &next->entry);
			break;
		}
	}
	return nr_exclusive;
}

1)遍历当前等待队列头链表中的等待队列。根据传递的唤醒数量,执行循环。
2)执行等待队列定义时,传递的func回调。上文中提到了,默认回调函数为autoremove_wake_function

int autoremove_wake_function(struct wait_queue_entry *wq_entry, unsigned mode, int sync, void *key)
{
	int ret = default_wake_function(wq_entry, mode, sync, key);

	if (ret)
		list_del_init(&wq_entry->entry);
	return ret;
}

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

   autoremove_wake_function最终会调用try_to_wake_up函数将进程置为TASK_RUNNING状态。这样后面的进程调度便会调度到该进程,从而唤醒该进程继续执行。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值