linux等待队列wait queue

        linux内核的等待队列是在内核中运用非常广泛的数据结构,它是以双循环链表为基础的数据结构,与进程的休眠---唤醒机制紧密相连,可以用来同步对系统资源的访问、异步事件通知、跨进程通信等。

        假设进程A想要获取某资源(读网卡数据),但是此时资源没有准备好(网卡还未接收到数据),这时内核必须切换到其他进程进行,直到资源准备好再唤醒该进程。

1、等待队列头

struct wait_queue_head {
	spinlock_t		lock;  //用于互斥访问的自旋锁
	struct list_head	head;
};
typedef struct wait_queue_head wait_queue_head_t;

可通过宏DECLARE_WAIT_QUEUE_HEAD(name)动态创建或者函数init_waitqueue_head(&name)创建类型为wait_queue_head_t的等待队列头name。

//静态创建
#define DECLARE_WAIT_QUEUE_HEAD(name) \
	struct wait_queue_head name = __WAIT_QUEUE_HEAD_INITIALIZER(name)

#define __WAIT_QUEUE_HEAD_INITIALIZER(name) {					\
	.lock		= __SPIN_LOCK_UNLOCKED(name.lock),			\
	.head		= { &(name).head, &(name).head } }


//动态创建
#define init_waitqueue_head(wq_head)						\
	do {									\
		static struct lock_class_key __key;				\
										\
		__init_waitqueue_head((wq_head), #wq_head, &__key);		\
	} while (0)

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

2、等待队列元素

struct wait_queue_entry {
	unsigned int		flags;
	void			*private;  //指向等待队列的进程task_struct
	wait_queue_func_t	func;  //唤醒函数
	struct list_head	entry;  //链表元素,将wait_queue_entry 挂到wait_queue_head_t
};

类似的,队列元素的创建也类似:DECLARE_WAITQUEUE(name, task) 定义一个名为name的等待队列元素,或者使用init_waitqueue_entry(&name, tsk)动态创建

//静态创建宏
#define DECLARE_WAITQUEUE(name, tsk)						\
	struct wait_queue_entry name = __WAITQUEUE_INITIALIZER(name, tsk)

#define __WAITQUEUE_INITIALIZER(name, tsk) {					\
	.private	= tsk,							\
	.func		= default_wake_function,				\
	.entry		= { NULL, NULL } }


//动态创建
static inline void init_waitqueue_entry(struct wait_queue_entry *wq_entry, 
                                            struct task_struct *p)
{
	wq_entry->flags		= 0;
	wq_entry->private	= p;
	wq_entry->func		= default_wake_function;
}

//也可以使用init_waitqueue_func_entry函数来初始化为自定义的唤醒函数
static inline void
init_waitqueue_func_entry(struct wait_queue_entry *wq_entry, wait_queue_func_t func)
{
	wq_entry->flags		= 0;
	wq_entry->private	= NULL;
	wq_entry->func		= func;
}

3、添加移除等待队列

内核提供了几个函数将元素添加删除至等待队列,实现位于kernel/sched/wait.c

add_wait_queue():队列添加非独占普通等待队列(flag清除WQ_FLAG_EXCLUSIVE标志)

add_wait_queue_exclusive():队列尾部添加独占等待队列(flag设置WQ_FLAG_EXCLUSIVE标志)

remove_wait_queue():移除元素

void add_wait_queue(struct wait_queue_head *wq_head, struct wait_queue_entry *wq_entry)
{
	unsigned long flags;

	wq_entry->flags &= ~WQ_FLAG_EXCLUSIVE;
	spin_lock_irqsave(&wq_head->lock, flags);
	__add_wait_queue_entry_tail(wq_head, wq_entry);
	spin_unlock_irqrestore(&wq_head->lock, flags);
}
EXPORT_SYMBOL(add_wait_queue);

void add_wait_queue_exclusive(struct wait_queue_head *wq_head, struct wait_queue_entry *wq_entry)
{
	unsigned long flags;

	wq_entry->flags |= WQ_FLAG_EXCLUSIVE;
	spin_lock_irqsave(&wq_head->lock, flags);
	__add_wait_queue_entry_tail(wq_head, wq_entry);
	spin_unlock_irqrestore(&wq_head->lock, flags);
}
EXPORT_SYMBOL(add_wait_queue_exclusive);

//移除
void remove_wait_queue(struct wait_queue_head *wq_head, struct wait_queue_entry *wq_entry)
{
	unsigned long flags;

	spin_lock_irqsave(&wq_head->lock, flags);
	__remove_wait_queue(wq_head, wq_entry);
	spin_unlock_irqrestore(&wq_head->lock, flags);
}
EXPORT_SYMBOL(remove_wait_queue);


/
static inline void __add_wait_queue_entry_tail(struct wait_queue_head *wq_head, struct wait_queue_entry *wq_entry)
{
	list_add_tail(&wq_entry->entry, &wq_head->head); //添加到队列头部
}

static inline void
__remove_wait_queue(struct wait_queue_head *wq_head, struct wait_queue_entry *wq_entry)
{
	list_del(&wq_entry->entry);
}

示意图如下:

4、进程 休眠——唤醒

把进程(task_struct)添加到等待队列后,就可以休眠该进程,让出cpu给其他进程运行,内核提供wait_event宏和它的几个变种来实现进程休眠,直到condition成立。

wq_head为等待队列头,condition是一个bool表达式,

wait_event(wq_head, condition)  //非中断休眠
wait_event_timeout(wq_head, condition, timeout)  //同上,另外进程等待限定时间返回不论                                                                                                condition是否成立
wait_event_interruptible(wq_head, condition) //进程可以被信号打断
wait_event_interruptible_timeout(wq_head, condition, timeout)   //类似上面
io_wait_event(wq_head, condition)

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


#define __wait_event(wq_head, condition)					\
	(void)___wait_event(wq_head, condition, 
                        TASK_UNINTERRUPTIBLE,  \  //带interruptible为TASK_INTERRUPTIBLE
                        0,               \  //
                        0,	\  //timeout
			    schedule())


/* 定义等待队列元素,并将元素加入到等待队列中
 * 循环判断等待条件condition是否满足,若条件满足,或者接收到中断信号,等待结束,函数返回
 * 若condition满足,返回0;否则返回-ERESTARTSYS
 */
#define ___wait_event(wq_head, condition, state, exclusive, ret, cmd)		\
({										\
	__label__ __out;							\
	struct wait_queue_entry __wq_entry;					\
	long __ret = ret;	/* explicit shadow */				\
			
    // 初始化等待队列元素__wq_entry,关联当前进程,根据exclusive参数初始化属性标志 
    // 唤醒函数为autoremove_wake_function()    							
	init_wait_entry(&__wq_entry, exclusive ? WQ_FLAG_EXCLUSIVE : 0);	\
    // 等待事件循环  
	for (;;) {								\
		//当检测进程是否有待处理信号则返回值__int不为0 
		long __int = prepare_to_wait_event(&wq_head, &__wq_entry, state);\
			                                                                    \
        // 当前进程让出调度器前,判断condition是否成立。若成立,提前结束,后续将返回0 
		if (condition)							\
			break;							\
										\
        // 当前进程让出调度器前,判断当前进程是否接收到中断信号(或KILL信号)       
        // 如果成立,将提前返回-ERESTARTSYS   
		if (___wait_is_interruptible(state) && __int) {			\ 
			__ret = __int;						\
			goto __out;						\
		}								\
										\
        // 此处实际执行schedule(),当前进程让出调度器 
		cmd;								\
	}									\
    // 设置进程为可运行状态,并且将等待队列元素从等待队列中删除    
	finish_wait(&wq_head, &__wq_entry);					\
__out:	__ret;									\
})


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);
}
EXPORT_SYMBOL(init_wait_entry);

//防止wait没有在队列中,但是事件已经产生导致无限等待
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);
    // 返回非0值条件:可被信号中断并且确实有信号挂起
	if (unlikely(signal_pending_state(state, current))) {
        // 将等待队列元素从等待队列中删除,返回-ERESTARTSYS
		list_del_init(&wq_entry->entry);
		ret = -ERESTARTSYS;
	} else {
        // 判断wq_entry->entry是否为空,即等待队列元素是否已经被添加到等待队列中
		if (list_empty(&wq_entry->entry)) {
            // WQ_FLAG_EXCLUSIVE标志被设置时,将等待队列元素添加到等待队列尾部(独占等待)
            // 否则,将等待队列元素添加到等待队列头部。同2.1中对WQ_FLAG_EXCLUSIVE标志介绍。
			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;
}
EXPORT_SYMBOL(prepare_to_wait_event);

 用state_value改变当前的进程状态
#define set_current_state(state_value)				\
	do {							\
		current->task_state_change = _THIS_IP_;		\
		smp_store_mb(current->state, (state_value));	\
	} while (0)


/*  设置进程为可运行状态,并且将等待队列元素从等待队列中删除  */
void finish_wait(struct wait_queue_head *wq_head, struct wait_queue_entry *wq_entry)
{
	unsigned long flags;

    // 将当前进程状态改为可运行状态(TASK_RUNNING)
    // 类似set_current_state(),差别在于未进行内存屏障
	__set_current_state(TASK_RUNNING);

    // 等待队列元素若仍在等待队列中,则将其删除
	if (!list_empty_careful(&wq_entry->entry)) {
		spin_lock_irqsave(&wq_head->lock, flags);
		list_del_init(&wq_entry->entry);
		spin_unlock_irqrestore(&wq_head->lock, flags);
	}
}
EXPORT_SYMBOL(finish_wait);

简单总结下进程进入休眠的步骤:

1、使用add_wait_queue将当前进程关联的等待队列元素添加到等待队列

2、set_current_state设置中断状态

3、判断资源是否拿到,或是否捕捉到中断信号

4、没拿到进程让出调度器,调用schedule()进入休眠状态

5、资源得到满足,将等待队列元素从等待队列删除

唤醒等待队列

当资源准备好后,就可以唤醒等待队列中的进程,内核通过wake_up()和它的几个变种来唤醒等待队列中的进程

wake_up(&wq_head)  //唤醒等待队列上的所有进程
wake_up_interruptible(&wq_head)  //只唤醒哪些执行可中断睡眠的进程
wake_up_nr(&wq_head, nr) //唤醒给定数目的独占等待进程
wake_up_interruptible_nr(&wq_head, nr)
wake_up_interruptible_all(&wq_head)

#define TASK_NORMAL         (TASK_INTERRUPTIBLE | TASK_UNINTERRUPTIBLE)
//可知TASK_NORMAL唤醒TASK_INTERRUPTIBLE 和 TASK_UNINTERRUPTIBLE的所有进程

#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_interruptible(x)	__wake_up(x, TASK_INTERRUPTIBLE, 1, NULL)

void __wake_up(struct wait_queue_head *wq_head, unsigned int mode,
			int nr_exclusive, void *key)
{
	unsigned long flags;

	spin_lock_irqsave(&wq_head->lock, flags);
	__wake_up_common(wq_head, mode, nr_exclusive, 0, key);
	spin_unlock_irqrestore(&wq_head->lock, flags);
}
EXPORT_SYMBOL(__wake_up);


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

     在等待队列头指向的链表上,从curr指向的元素开始依次遍历元素
	list_for_each_entry_safe(curr, next, &wq_head->head, entry) {
		unsigned flags = curr->flags;

        // 调用等待队列元素绑定的唤醒回调函数
        // 注意,具体唤醒何种进程(TASK_INTERRUPTIBLE/TASK_UNINTERRUPTIBLE),作为参数传递给唤        
        // 醒函数处理
        // 当进程不符合唤醒条件时,ret为0,详见try_to_wake_up()
		int ret = curr->func(curr, mode, wake_flags, key);
		if (ret < 0)
			break;

        // 如果当前等待队列元素为独占等待,并且独占等待个数已经等于nr_exclusive,提前退出循环
        // 独占等待进程被加入到等待队列的尾部,因此此时表明所有唤醒工作已经完成
		if (ret && (flags & WQ_FLAG_EXCLUSIVE) && !--nr_exclusive)
			break;
	}
}

wake_up会编译等待队列上的所有元素,最终会调用等待队列元素所绑定的唤醒函数

DECLARE_WAITQUEUE(name, tsk)使用default_wake_function()

init_wait_entry(&name,flag)中使用autoremove_wake_function()

default_wake_function

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);
}
EXPORT_SYMBOL(default_wake_function);

static int
try_to_wake_up(struct task_struct *p, unsigned int state, int wake_flags)
{
	unsigned long flags;
	int cpu, success = 0;

	smp_mb__before_spinlock();
	raw_spin_lock_irqsave(&p->pi_lock, flags);

    // 此处对进程的状态进行筛选,跳过不符合状态的进程(TASK_INTERRUPTIBLE/TASK_UNINTERRUPTIBLE)
	if (!(p->state & state))
		goto out;

	trace_sched_waking(p);

	/* We're going to change ->state: */
	success = 1;
	cpu = task_cpu(p);

	smp_rmb();
	if (p->on_rq && ttwu_remote(p, wake_flags)) //当前进程已处于rq运行队列,则无需唤醒
		goto stat;

...

	ttwu_queue(p, cpu, wake_flags);
stat:
	ttwu_stat(p, cpu, wake_flags);
out:
	raw_spin_unlock_irqrestore(&p->pi_lock, flags);

	return success;
}


static void ttwu_queue_remote(struct task_struct *p, int cpu, int wake_flags)
{
	struct rq *rq = cpu_rq(cpu);  // 获取当前进程的运行队列

	p->sched_remote_wakeup = !!(wake_flags & WF_MIGRATED);

	if (llist_add(&p->wake_entry, &cpu_rq(cpu)->wake_list)) {
		if (!set_nr_if_polling(rq->idle))
			smp_send_reschedule(cpu);
		else
			trace_sched_wake_idle_without_ipi(cpu);
	}
}
...
default_wake_function函数调用顺序为:

default_wake_function() --> try_to_wake_up() --> ttwu_queue() --> ttwu_do_activate() --> ttwu_do_wakeup()

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;
}
EXPORT_SYMBOL(autoremove_wake_function);

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);
}
EXPORT_SYMBOL(default_wake_function);

可以看到autoremove_wake_function相比default_wake_function,在成功执行唤醒工作后,会自动将等待队列元素从等待队列中移除,所以使用default_wake_function()时不能忘记将元素移除。

  • 4
    点赞
  • 40
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Linux waitqueue 是一种 Linux 内核中的同步机制,它用于控制进程的执行顺序,使进程之间能够协调进行。 它通过让一个进程在等待另一个进程完成某个操作时进入睡眠状态,并在另一个进程完成操作后唤醒等待的进程。这样,它可以避免进程在不同步的情况下同时进行某些操作,从而减少系统资源的浪费。 因此,waitqueue 机制是 Linux 内核中常用的一种机制,它可以保证系统的正确性和高效性。 ### 回答2: 在Linux操作系统中,waitqueue是一种用于进程或线程等待的机制。 当一个进程或线程需要等待某个条件满足时,它可以使用waitqueue机制来挂起自己的执行。等待队列waitqueue)是一个数据结构,用于维护等待某个事件发生的进程或线程的列表。 当一个条件被满足时,比如某个共享资源变为可用,就会唤醒等待该条件的进程或线程。唤醒的过程是通过使用wake_up函数来实现的。 当一个进程或线程需要等待条件满足时,它会调用wait_event函数,将自己加入到等待队列中,并将自己标记为等待状态。之后,该进程或线程就会进入睡眠状态,并且由调度器决定运行其他进程或线程。 当条件满足时,比如共享资源变为可用,唤醒该条件的进程或线程的时候,会调用wake_up函数来唤醒等待的进程或线程。被唤醒的进程或线程会从wait_event的调用处继续执行,并继续执行后续逻辑。 需要注意的是,使用waitqueue机制需要配合锁机制使用,以避免竞态条件的产生。在加入等待队列和唤醒过程中,需要对共享资源进行加锁保护,以防止并发访问导致的数据不一致性。 总之,waitqueueLinux中一种用于进程或线程等待的机制,它通过等待队列来管理等待某个条件满足的进程或线程,并通过唤醒函数来唤醒等待的进程或线程。它是实现同步和互斥的重要工具之一,能够实现进程或线程之间的协作与同步。 ### 回答3: Linux中的waitqueue等待队列)是一种用于进程调度的机制。它允许一个或多个进程阻塞并等待某个特定条件的满足。 waitqueue是一个数据结构,类似于一个队列,用于存储等待某个条件满足的进程。当一个进程等待某个条件时,它会将自己添加到waitqueue中,并进入睡眠状态。 在Linux内核中,waitqueue通常与锁(如spinlock或mutex)结合使用。当一个进程需要等待某个条件时,它需要先获取锁,在锁的保护下将自己添加到waitqueue中,然后释放锁并进入睡眠状态。当条件满足时,另一个进程会获取相同的锁,唤醒等待waitqueue中的进程。 waitqueue的实现依赖于内核调度器。当一个进程被唤醒时,它会从睡眠状态返回到可运行状态,并进入内核调度器的调度队列等待分配CPU执行。 waitqueue提供了一种线程同步的机制,使得进程可以等待某个条件满足而不需要忙等待。它在很多Linux内核中的子系统中广泛使用,如设备驱动、文件系统等。 总结来说,waitqueueLinux内核中用于进程调度的一种机制,它允许一个或多个进程等待某个条件的满足。它借助锁和睡眠状态实现进程的阻塞和唤醒,依赖于内核调度器进行进程的调度。waitqueue在提供进程同步、避免忙等待等方面发挥了重要作用。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值