Linux驱动中的等待队列与休眠

Linux驱动中的等待队列与休眠

原文:https://blog.csdn.net/mengluoxixiang/article/details/46239523?spm=1001.2014.3001.5501

背景

在Linux内核驱动中常常会存在这种情况:进程A若想继续执行需要满足某个条件condition的限制,若条件不满足则进程会被挂到等待队列进行等待。

在Linux中,一个等待队列由一个“等待队列头”来管理,看一下这个队列头的初始化:

DECLARE_WAIT_QUEUE_HEAD(name)

或动态的定义初始化:

wait_queue_head_t my_queue_head;
init_waitqueue_head(&my_queue_head);

wait 函数

下面分成三种情况看一下有关wait函数的操作:

普通的wait函数

接着看一下满足不同操作条件的wait函数。

(1)不可中断,没有超时的wait函数:

#define wait_event(wq, condition) 	
do {	
	//判断,如果条件为真则不用调用等待队列,继续执行
	if (condition)
		break;
	__wait_event(wq, condition); //调用等待队列
} while (0)
    
#define __wait_event(wq, condition>
do {
	DEFINE_WAIT(__wait);//定义一个等待线程
	for (;;) {
		//将当前进程挂到等待队列,并设置当前状态
		prepare_to_wait(&wq, &__wait, TASK_UNINTERRUPTIBLE);
		if (condition)
			break;
		schedule();//进程调度,使等待队列开始休眠
	}
	//把唤醒的进程从等待队列中去掉,并设置当前状态为TASK_RUNNING
	finish_wait(&wq, &__wait);
} while (0)

该方法在不满足条件condition时会进行等待,一直等条件满足为止,期间没有时间限制,不允许中断

(2)不可中断,有超时限制的wait函数

#define wait_event_timeout(wq, condition, timeout)	
({							
	long __ret = timeout;	//设置最大等待时间			
	if (!(condition)) 	//如果条件不满足,调用等待队列			
		__wait_event_timeout(wq, condition, __ret);
	__ret;							
})

#define __wait_event_timeout(wq, condition, ret)
do {						
	DEFINE_WAIT(__wait);//定义一个等待线程
					
	for (;;) {	
		//将当前进程挂到等待队列,并设置当前状态
		prepare_to_wait(&wq, &__wait, TASK_UNINTERRUPTIBLE);
		if (condition)
			break;
		//进程调度,使等待队列开始休眠,且有最大休眠时间限制
		ret = schedule_timeout(ret);
		if (!ret)
			break;
	}
	//把唤醒的进程从等待队列中去掉,并设置当前状态为TASK_RUNNING
	finish_wait(&wq, &__wait);
} while (0)

该方法在超时情况下未能得到满足的条件会返回0,否则返回剩余的可等待时间,期间不可中断。

(3)可中断,没有超时限制的wait函数

#define wait_event_interruptible(wq, condition)	
({				
	int __ret = 0;		
	if (!(condition))//如果条件不满足,调用等待队列
		__wait_event_interruptible(wq, condition, __ret);
	__ret;					
})

#define __wait_event_interruptible(wq, condition, ret)		
do {							
	DEFINE_WAIT(__wait);//定义一个等待线程
	for (;;) {						
		prepare_to_wait(&wq, &__wait, TASK_INTERRUPTIBLE);
		if (condition)				
			break;				
		if (!signal_pending(current)) {		
			schedule();				
			continue;				
		}						
		ret = -ERESTARTSYS;				
		break;						
	}						
	finish_wait(&wq, &__wait);//把唤醒的进程从等待队列中去掉,并设置当前状态为TASK_RUNNING
} while (0)

该方法在等待时如果被中断会返回-ERESTARTSYS信号,否则在满足condition条件时返回0,期间没有时间限制。

(4)可中断,有超时限制的wait函数

#define wait_event_interruptible_timeout(wq, condition, timeout)
({							
	long __ret = timeout;				
	if (!(condition))			
		__wait_event_interruptible_timeout(wq, condition, __ret); 
	__ret;						
})

#define __wait_event_interruptible_timeout(wq, condition, ret)	
do {									
	DEFINE_WAIT(__wait);						
									
	for (;;) {							
		prepare_to_wait(&wq, &__wait, TASK_INTERRUPTIBLE);	
		if (condition)						
			break;						
		if (!signal_pending(current)) {				
			ret = schedule_timeout(ret);			
			if (!ret)					
				break;					
			continue;					
		}							
		ret = -ERESTARTSYS;					
		break;							
	}								
	finish_wait(&wq, &__wait);					
} while (0)

该方法若超时结束会返回0,中断结束返回-ERESTARTSYS信号,否则在满足条件时返回剩余可等待时间。

iowait

什么是iowait? 顾名思义,就是系统因为io导致的进程wait。

再深一点讲就是:这时候系统在做io,导致没有进程在干活,cpu在执行idle进程空转,

所以说iowait的产生要满足两个条件,一是进程在等io,二是等io时没有进程可运行。

(1)可中断无超时iowait函数

#define __wait_io_event_interruptible(wq, condition, ret)		
do {									
	DEFINE_WAIT(__wait);						
									
	for (;;) {							
		prepare_to_wait(&wq, &__wait, TASK_INTERRUPTIBLE);	
		if (condition)						
			break;						
		if (!signal_pending(current)) {				
			io_schedule();					
			continue;					
		}							
		ret = -ERESTARTSYS;					
		break;							
	}					
	finish_wait(&wq, &__wait);			
} while (0)

(2)有中断有超时iowait函数

#define __wait_io_event_interruptible_timeout(wq, condition, ret)	
do {									
	DEFINE_WAIT(__wait);						
									
	for (;;) {							
		prepare_to_wait(&wq, &__wait, TASK_INTERRUPTIBLE);	
		if (condition)						
			break;						
		if (!signal_pending(current)) {				
			ret = io_schedule_timeout(ret);			
			if (!ret)					
				break;					
			continue;					
		}							
		ret = -ERESTARTSYS;					
		break;							
	}								
	finish_wait(&wq, &__wait);					
} while (0)

互斥wait函数

接下来看一下互斥等待函数:

正常情况下,当一个进程调用一个等待队列的wake_up函数时,所有这个队列上的进程都被置为可运行的,然而在许多情况下,我们提前知道只有一个进程将被唤醒并能成功的使用资源,其余被被唤醒的进程将再次进入等待,由此看来这种行为会降低系统的性能。因为内核开发者开发了一种“互斥等待”添加到内核中,与普通wait函数不同的是:

1、当一个进程有WQ_FLAG_EXCLUSEVE标志时,它被添加到等待队列的队尾,否则添加到对头。

2、当一个队列上的wake_up函数被调到用时,在唤醒第一个带有WQ_FLAG_EXCLUSEVE标志的进程后停止,但内核仍会调用所有非互斥等待的进程,因为这些进程排在队首,互斥进程排在队尾,而唤醒的顺序是由首到尾。

最后的结果是以顺序的方式唤醒第一个互斥的进程后停止唤醒后面的进程。

使用互斥等待需要满足三个条件:

1、希望该进程对资源进行有效竞争,

2、当资源可用时唤醒一个进程就足够完全消耗资源,

3、所有使用该资源的进程都应该统一的使用互斥等待。

(1)可中断,无超时限制的互斥wait函数

#define __wait_event_interruptible_exclusive(wq, condition, ret)	
do {									
	DEFINE_WAIT(__wait);						
									
	for (;;) {							
		prepare_to_wait_exclusive(&wq, &__wait,			
					TASK_INTERRUPTIBLE);		
		if (condition) {					
			finish_wait(&wq, &__wait);			
			break;						
		}							
		if (!signal_pending(current)) {				
			schedule();					
			continue;					
		}							
		ret = -ERESTARTSYS;					
		abort_exclusive_wait(&wq, &__wait, 	//把该进程的状态回复成running,并从等待队列中删除		
				TASK_INTERRUPTIBLE, NULL);		
		break;							
	}		

大部分会用到的进入wait的函数已经分析完了,下面看一下相关的wake_up函数:

#define wake_up(x)  __wake_up(x, TASK_NORMAL, 1, NULL)//唤醒非中断的一个进程
#define wake_up_nr(x, nr)  __wake_up(x, TASK_NORMAL, nr, NULL)//唤醒非中断的nr个进程
#define wake_up_all(x)  __wake_up(x, TASK_NORMAL, 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)//唤醒可中断的nr个进程
#define wake_up_interruptible_all(x)  __wake_up(x, TASK_INTERRUPTIBLE, 0, NULL)//唤醒可中断的所有进程

重点看一下这几个define里都调用的函数__wake_up()

void __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);
}
EXPORT_SYMBOL(__wake_up);

上面那个函数先加锁再调用__wake_up_common(),然后解锁,继续看__wake_up_common()

static void __wake_up_common(wait_queue_head_t *q, unsigned int mode,
			int nr_exclusive, int wake_flags, void *key)
{
	wait_queue_t *curr, *next;
 
	list_for_each_entry_safe(curr, next, &q->task_list, task_list) {
		unsigned flags = curr->flags;
 
		if (curr->func(curr, mode, wake_flags, key) &&
				(flags & WQ_FLAG_EXCLUSIVE) && !--nr_exclusive)
			break;
	}
}

上面这个函数很巧妙,如果if()的条件不满足的话则遍历所有列表中的结点,否则结束遍历。

看一下会使遍历结束的if中的三个条件:

1、curr->fun(),调用该结点的fun()函数唤醒当前进程,如果唤醒失败则后面的两个条件都不用判断,继续遍历下一个节点

2、flags & WQ_FLAG_EXCLUSIVE ,如果该条件不成立(即不是互斥等待)后面的条件就不用判断了,继续遍历

3、--nr_exclusive,在前两个条件都成立的情况下,nr_exclusive变量先减1,再判断是否为0 ,

也就是说只有在flags & WQ_FLAG_EXCLUSIVE成立时才会对nr_exclusive的值进行自减和判断,

到nr_exclusive自减变为0时,三个条件均成立,此时跳出循环,实现的操作是:

唤醒所有非互斥进程,接着唤醒nr_exclusive个互斥进程后停止唤醒。

这里需要注意的时:当nr_exclusive0时,(!--nr_exclusive)1恒成立,也就是说唤醒所有互斥进程。

如果说我的文章对你有用,只不过是我站在巨人的肩膀上再继续努力罢了。
若在页首无特别声明,本篇文章由 Schips 经过整理后发布。
博客地址:https://www.cnblogs.com/schips/
原文地址:https://www.cnblogs.com/schips/p/linux_driver_wait_and_wait_queue.html