linux驱动之阻塞与休眠

阻塞与非阻塞

阻塞读取模式进程会进入休眠状态, 所以必须确保有一个地方能够唤醒休眠的进程, 否则, 进程将永久休眠. 唤醒进程最大可能的地方发生在中断里, 因为在硬件资源获得同时往往伴随着一个中断, 而非阻塞方式的进程则不断尝试, 直到可以进行I/O.

驱动程序如何提供阻塞与非阻塞模式的读数据?
linux系统中open文件时对打开模式 给上O_NONBLOCK参数 就是非阻塞方式打开
如:

int fd = open("/dev/xxx", O_RDWR | O_NONBLOCK);//非阻塞方式
int fd = open("/dev/xxx", O_RDWR );//阻塞方式

或者使用fcntl
如:

int fd = open("/dev/xxx", O_RDWR);
int flag = fcntl(fd, F_GETFL);
fcntl(fd, F_SETFL, flag | O_NONBLOCK); //非阻塞方式
fcntl(fd, F_SETFL, flag & ~O_NOBLOCK); //阻塞方式

应用程序open系统调用时, flags参数会传到驱动层中, 保存在file* filp这里面(filp->f_flags)

通过这个参数可以知道应用程序是要阻塞还是非阻塞

static DECLARE_WAIT_QUEUE_HEAD(apm_waitqueue)
static ssize_t drv_read(struct file *fp, char __user *buf, size_t count, loff_t *ppos)if (queue_empty(&as->queue) && fp->f_flags & O_NONBLOCK)//判断是否为阻塞
		return -EAGAIN;//非阻塞直接返回
                                                                                                                                                                                                                                        
	wait_event_interruptible(apm_waitqueue, !queue_empty(&as->queue));//如果是阻塞 启动休眠
    ……
}

等待队列 可使 进程主动休眠

linux驱动程序中, 可以使用等待队列来实现阻塞进程的唤醒.

相关的API

1,定义"等待队列头部"

wait_queue_head_t my_queue;

2,初始化"等待队列的头部"

init_waitqueue_head(&my_queue);

使用 DECLARE_WAIT_QUEUE_HEAD(name) 宏可以作为定义并初始化等待队列头部的"快捷方式"

3, 定义等待队列元素

DECLARE_WAITQUEUE(name, tsk)

4,添加/移除等待队列

void add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait); 
void remove_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);

add_wait_queue()等待队列元素wait添加道等待队列头部q指向的双向链表中.
remove_wait_queue()用于将等待队列元素wait从由q头部指向的链表中移除.

5,等待事件

wait_event(queue, condition) 
wait_event_interruptible(queue, condition) 
wait_event_timeout(queue, condition, timeout) 
wait_event_interruptible_timeout(queue, condition, timeout) 

等待第1个参数queue作为等待队列头部的队列被唤醒,而且第2个参数condition必须满足,否则继续阻塞。
wait_event()和wait_event_interruptible()的区别在于后者可以被信号打断,而前者不能。加上_timeout后的宏意味着阻塞等
待的超时时间,以jiffy为单位,在第3个参数的timeout到达时,不论condition是否满足,均返回。

6,唤醒队列

void wake_up(wait_queue_head_t *queue); 
void wake_up_interruptible(wait_queue_head_t *queue);

上述操作会唤醒以queue作为等待队列头部的队列中所有的进程。wake_up()应该与wait_event()或wait_event_timeout()成对使用,而wake_up_interruptible()则应与wait_event_interruptible()或wait_event_interruptible_timeout()成对使用。wake_up()可唤醒处于TASK_INTERRUPTIBLE和TASK_UNINTERRUPTIBLE的进程,而wake_up_interruptibl()只能唤醒处于TASK_INTERRUPTIBLE的进程。

7,在等待队列上休眠

sleep_on(wait_queue_head_t *q ); 
interruptible_sleep_on(wait_queue_head_t *q ); 

sleep_on() 函数的作用就是将目前进程的状态设置成TASK_UNINTERRUPTIBLE, 并定义一个等待队列元素, 之后把它挂到等待队列头部q指向的双向链表, 指导资源可获得, q队列指向链接的进程被唤醒.
interruptible_sleep_on()与sleep_on()函数类似,其作用是将目前进程的状态置成TASK_INTERRUPTIBLE,并定义一个等待队列元素,之后把它附属到q指向的队列,直到资源可获得(q指引的等待队列被唤醒)或者进程收到信号

等待队列API使用例子

进程通过执行下面步骤将自己加入到一个等待队列中:
1) 调用DECLARE_WAITQUEUE()创建一个等待队列的项;
2) 调用add_wait_queue()把自己加入到等待队列中。该队列会在进程等待的条件满足时唤醒它。在其他地方写相关代码,在事件发生时,对等的队列执行wake_up()操作。
3) 将进程状态变更为: TASK_INTERRUPTIBLE or TASK_UNINTERRUPTIBLE。
4) 如果状态被置为TASK_INTERRUPTIBLE ,则信号唤醒进程。即为伪唤醒(唤醒不是因为事件的发生),因此检查并处理信号。
5) 检查condition是否为真,为真则没必要休眠,如果不为真,则调用scheduled()。
6) 当进程被唤醒的时候,它会再次检查条件是否为真。真就退出循环,否则再次调用scheduled()并一直重复这步操作。
7) condition满足后,进程将自己设置为TASK_RUNNING 并通过remove_wait_queue()退出。
ex:

static ssize_t xxx_wirte(struct file *file, const char *buffer, size_t count,loff_t *ppos)
{
	...
	DECLARE_WAITQUEUE(wait, current); // 定义等待队列元素
	add_wait_queue(&xxx_wait, &wait); //添加元素到等待队列
	//等待设备缓冲区可写
	do{
		avail = device_writable(...);
		if(avail < 0){
			if(file->f_flags & O_NONBLOCK){//非阻塞
				ret = -EAGAIN;
				goto out;
			}
			set_current_state(TASK_INTERRUPTIBLE);//改变进程状态
			schedule();//调度其他进程执行, 当前进程进入休眠
			if(signal_pending(current)){
				ret = -ERESTARTSYS;
				goto out;
			}
		}
	}while(avail < 0);
	device_write(...);//写设备缓冲区
out:
	remove_wait_queue(&xxx_wait, &wait);//将元素移出xxx_wait指引的队列
	set_current_state(&TASK_RUNNING);//设置进程状态为TASK_RUNNING
	return ret;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值