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

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

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

  • 4
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
好的,这是一个比较大的问题,我会尽可能详细地回答。首先,进程是操作系统中的一个基本概念,它是程序执行的基本单位。进程可以创建、终止、阻塞、唤醒和进行通信。下面我会分别介绍如何在C语言中模拟这些操作。 1. 进程的创建 在C语言中,可以使用fork()函数创建一个新的进程。fork()函数会复制当前进程的所有资源,包括代码、数据、堆栈等,并创建一个新的进程。新进程与原进程几乎完全相同,但是它有自己的进程ID(PID)和父进程ID(PPID)。在父进程中,fork()函数返回新进程的PID,在子进程中,fork()函数返回0。 2. 进程的终止 在C语言中,可以使用exit()函数来终止一个进程。exit()函数会释放该进程占用的所有资源,并将退出状态传递给父进程。父进程可以使用wait()函数来等待进程退出,并获取子进程的退出状态。 3. 进程的阻塞和唤醒 在C语言中,可以使用sleep()函数将一个进程阻塞一段时间。sleep()函数会使当前进程休眠指定的秒数,并将CPU时间片让给其他进程。当休眠时间结束后,进程会被唤醒,并继续执行。 另外,可以使用信号(signal)来实现进程的阻塞和唤醒。信号是一种异步通知机制,当某个事件发生时,操作系统会向进程发送一个信号。进程可以使用signal()函数来注册信号处理函数,当信号到达时,操作系统会调用该函数。可以使用kill()函数向指定进程发送信号,从而实现进程唤醒。 4. 进程间通信 在C语言中,可以使用管道(pipe)来实现进程间通信。管道是一种半双工的通信方式,它可以在两个进程之间传递数据。一个进程可以将数据写入管道,另一个进程可以从管道中读取数据。可以使用pipe()函数创建一个管道,使用read()和write()函数进行读写操作。 另外,还有其他进程间通信方式,如共享内存、消息队列、信号量等。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

正在起飞的蜗牛

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值