进程的休眠与唤醒(等待队列)

Posted 正在起飞的蜗牛

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了进程的休眠与唤醒(等待队列)相关的知识,希望对你有一定的参考价值。

1、进程休眠

(1)进程有三种基本状态:就绪态、阻塞态、运行态。
<1>阻塞态:进程缺少除了CPU之外的某些资源,因此该进程不能被运行,被阻塞住了不能被CPU调度;
<2>就绪态:进程分配到了除CPU之外的所有资源,等待CPU调度执行;
<3>运行态:进程获得CPU资源,程序在CPU上运行;
(2)进程休眠:进程休眠就是进程因为缺少除了CPU之外的某些资源而进入阻塞态,会从调度器的运行队列中移走该进程,并把进程放到等待资源的队列中,直到分配到资源从而被唤醒,再次进入到就绪态,等待CPU调度;

2、进程休眠的注意事项

进程进入休眠是很容易的,只要申请不到资源都可能休眠,但是我们要保证进程以一种安全的方式进入休眠,也就是进程进入休眠后,
将来能被成功的唤醒;
(1)永远不要在原子上下文中进入休眠,原子操作本身就是要不可中断的一次性执行完;
(2)不能在拥有锁的时候进入休眠,否则可能会造成死锁;比如A进程在拥有自旋锁时进入休眠等待B进程唤醒,B进程需要先获得自旋锁进行一些操作才能唤醒A进程,
B进程等待A进程释放自旋锁,A进程等待B进程唤醒后才能释放锁,这就死锁了;
(3)拥有信号量时进程是可以进入休眠的,但是要非常注意:用于信号量而休眠的代码必须很短,并且还要确保拥有信号量斌不会阻塞最终唤醒我们自己的那个进程;
(4)进程被唤醒时,无法知道休眠了多长时间或者休眠期间都发生了什么事,所以在进程被唤醒后不要对状态进行任何假定,必须去检查我们等待的条件是否为真,因为
同时可能有别的进程因为等待同一资源而休眠,我们等待的资源可能被别的进程拿走;
(5)除非知道有其他进程会在其他地方唤醒我们,否则进程不能进入休眠;

3、进程的唤醒

(1)要唤醒进程,前提是要知道哪些进程进入休眠,是因为等待什么资源而进入休眠;
(2)在linux中,维护者一个称为等待队列的数据结构,相当于一个进程链表;将等待同一资源的进程放到同一个等待队列里,将来资源有空闲时就从
相应的等待队列中唤醒一个线程;

4、等待队列

4.1、初始化等待队列

	//静态初始化
	DECLARE_WAIT_QUEUE_HEAD(name) 
 
	//动态方法
	wait_queue_head_t my_queue;
	init_waitqueue_head(&my_queue)		

4.2、将进程添加到等待队列

/*
*wq:等待队列的头,就是上面初始化等待队列得到的;
*condition:条件表达式,当wake_up后,condition为真时,唤醒阻塞的进程
*timeout:超时时间
*/

wait_event(wq, condition);	//进程进入休眠后不能被"ctrl+C"打断
wait_event_timeout(wq, condition, timeout);//进程进入休眠后超时返回
wait_event_interruptible(wq, condition);//进程进入休眠后能被"ctrl+C"打断

(1)举例:wait_event_interruptible(wq, havedata==1);
(2)分析:在进程中执行上面的代码,进程会被加入到wq等待队列中,直到将来被wake_up后,并且havedata==1时才能被唤醒;

4.3、唤醒等待队列中的进程

#define wake_up(x) __wake_up(x, TASK_NORMAL, 1, NULL)

#define wake_up_interruptible(x) __wake_up(x, TASK_INTERRUPTIBLE, 1, NULL)

上面的两个唤醒函数和添加到等待队列的函数要对应使用,比如wake_up(x)对应wait_event(wq, condition);

5、驱动代码中等待队列的使用

//等待队列头
wait_queue_head_t rwq,wwq; 

//数据缓冲区标志位
int havedata = 0;

static int hello_init(void)

	······
	//初始化两个等待队列
	init_waitqueue_head(&rwq); 
	init_waitqueue_head(&wwq); 
	······


static ssize_t hello_read (struct file *filep, char __user *buf, size_t size, loff_t *pos)

	int error;

	//此时如果没有准备好数据,则将进程休眠添加到rwq队列,等待唤醒
	wait_event_interruptible(rwq,havedata == 1);
	
	if(size > strlen(kbuf))
	
		size = strlen(kbuf);
	

	if(copy_to_user(buf,kbuf, size))
	
		error = -EFAULT;
		return error;
	

	//数据被读走,可以写数据
	havedata = 0;

	//唤醒需要写的进程
	wake_up_interruptible(&wwq);

	return size;


static ssize_t hello_write (struct file *filep, const char __user *buf, size_t size, loff_t *pos)

	int error;
	
	//此时如果没有上次的数据没有被读走,则将进程休眠添加到wwq队列,等待唤醒
	wait_event_interruptible(wwq,havedata == 0);
	
	if(size > KMAX_LEN)
	
		size = KMAX_LEN;
	
	memset(kbuf,0,sizeof(kbuf));
	if(copy_from_user(kbuf, buf, size))
	
		error = -EFAULT;
		return error;
	
	printk("%s\\n",kbuf);
	
	//数据写入,可以读数据
	havedata = 1;
	
	//唤醒需要读的进程
	wake_up_interruptible(&rwq);
	
	return size;

上面是摘抄的部分驱动代码,在读写接口中实现休眠和唤醒;

以上是关于进程的休眠与唤醒(等待队列)的主要内容,如果未能解决你的问题,请参考以下文章

linux 内核睡眠与唤醒

RTOS训练营队列的读写休眠和唤醒常规应用使用和晚课提问

RTOS训练营队列的读写休眠和唤醒常规应用使用和晚课提问

RTOS训练营队列的读写休眠和唤醒常规应用使用和晚课提问

linux中的等待队列-51

linux中的等待队列-51