2、等待队列原理深入

本文详细解释了等待队列的工作原理,包括等待队列头的结构、初始化过程,以及进程如何通过wait_event函数睡眠并等待事件。重点强调了内核对等待队列的管理和唤醒机制,用户无需直接操作等待项,它们由内核自动创建和管理。
摘要由CSDN通过智能技术生成

上一节从使用者的角度分别介绍了等待队列、完成量 、工作队列的使用场景和步骤;本节我们稍微深入一下,先看一下等待队列的基本工作原理,当然不会太深入,因为那是内核的工作。

1、等待队列头

        每个等待队列都有一个等待队列头,如上节介绍的NVMe驱动中定义的等待队列头:

 wait_queue_head_t    state_wq;

        wait_queue_head_t   的数据结构定义如下:      

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

      typedef    struct wait_queue_head    wait_queue_head_t;

        可见其本质上就是一个链表另加一个自旋锁构成;链表头用于挂接等待项,锁是因为等待队列可以再中断时修改,所以操作队列之前需要先锁住。

2、初始化等待队列头

        初始化等待队列头的方式如下:

init_waitqueue_head(&state_wq);

        它的工作流程如下,可见它只是初始化了锁和链表;

//它调用__init_waitqueue_head()
void __init_waitqueue_head(struct wait_queue_head *wq_head, const char *name, struct lock_class_key *key)
{
	spin_lock_init(&wq_head->lock);                        //初始化锁
	lockdep_set_class_and_name(&wq_head->lock, key, name);
	INIT_LIST_HEAD(&wq_head->head);                        //初始化队列
}
3、进程睡眠之等待项

        最本质的工作是让进程睡眠以等待事件的发生;它的原理是给进程定义一个等待队列项,把等待项加入到等待队列头,不断检查等待项的状态,以判断是否要唤醒进程。

       好消息是等待项无需用户创建,在调用wait_event()函数时,由内核自动创建,并自动加入到等待队列中。

        wait_event()有两个参数,一个是等待队列头,一个是等待条件;我们稍微追踪一下等待项的结构:

struct wait_queue_entry {
	unsigned int		flags;    //值为WQ_FLAG_EXCLUSIVE表示进程想要被独占唤醒,或者为0
	void			    *private;     //指向当前进程current
	wait_queue_func_t	func;     //指向唤醒进程的函数
	struct list_head	entry;    //用于连接到等待队列头
};

        再稍微追踪一下wait_event()的工作流程,它把具体的工作交由__wait_event(),这是内核的通用做法,以下函数看似复杂,其实很简单;

首先创建并初始化一个等待项( __wq_entry),等待项的初始化如下,没有什么特别之处,private执行本进程,所以本进程就是一个等待项;func指向一个内核实现的函数,用于唤醒进程:

//等待项的初始化
void init_wait_entry(struct wait_queue_entry *wq_entry, int flags)
{
	wq_entry->flags = flags;
	wq_entry->private = current;                  //指向当前进程
	wq_entry->func = autoremove_wake_function;    //唤醒进程的函数
	INIT_LIST_HEAD(&wq_entry->entry);
}

再看一下__wait_event()的流程,初始化等待项之和,调用 prepare_to_wait_event()将等待项(即本进程,毕竟等待项的private指针指向本进程)加入到等待队列:

#define ___wait_event(wq_head, condition, state, exclusive, ret, cmd)		\
({										\
	__label__ __out;							\
    //定义等待项
	struct wait_queue_entry __wq_entry;					\
	long __ret = ret;	/* explicit shadow */				\
    //初始化等待项
	init_wait_entry(&__wq_entry, exclusive ? WQ_FLAG_EXCLUSIVE : 0);	\
	for (;;) {								\
        //使进程睡眠
		long __int = prepare_to_wait_event(&wq_head, &__wq_entry, state);\
										\
		if (condition)							\
			break;							\
										\
		if (___wait_is_interruptible(state) && __int) {			\
			__ret = __int;						\
			goto __out;						\
		}								\
										\
		cmd;								\
	}									\
    //移除等待项
	finish_wait(&wq_head, &__wq_entry);					\
__out:	__ret;									\
})

       我们看一下简化版的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 (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;
}

//把等待项加入等待队列头
static inline void __add_wait_queue(struct wait_queue_head *wq_head, struct wait_queue_entry *wq_entry)
{
	list_add(&wq_entry->entry,  &wq_head->head);
}

        总之以上函数会让进程睡眠,直到事件发生被内核唤醒;具体的唤醒工作就是初始化工作项时fun所指定函数的工作,它完全由内核实现,我们不用关心。

        至此,我的理解是,虽然是等待队列,但实际队列上只能有一个等待项,就是当前进程;且等待项是由内核定义和实现的,用户完全不用关心。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值