一:阻塞与非阻塞概念:
1.1
阻塞操作是指执行设备操作时,如果不能获取资源,则挂起进程直到满足可操作的条件后再进行操作
被挂起的进程进入睡眠状态,被从调度器的运行队列移走,直到等待的条件被满足,而非阻塞操作的
进程在不能进设备操作的时候,并不挂起,它要么放弃,要么不停的查询,直到可以进行操作为止,
1.2
在阻塞访问时候,不能获取资源的进程将会进入休眠,将CPU资源让给其他进程。因为阻塞的进程会进入
休眠状态,所以必须保证有一个地方可以唤醒休眠的进程,否则,进程就真的寿正终寝了,唤醒进程的地
方最大可能发生在中断里面,因为在硬件资源获得的同时往往伴随这一个中断。非阻塞的进程不断的尝试
知道了可以进行I/O
1.3
应用程序可以选择是以 阻塞 或者 非阻塞的方式访问:
阻塞的访问方式: fd = open("/dev/xxx", O_RDWD);
非阻塞的当时访问 : fd = open("/dev/xxx", O_RDWD | O_NONBLOCK);
1.4
问题:为什么要引入阻塞与非阻塞操作
答案:驱动程序通常需要提供这样的能力:当应用程序进行 read() write() 等系统调用的时候,若
设备资源不能获取,正常情况下read() write()等操作会立即返回,需要重新访问,但是用户
要求上层只进行一次设备的读写操作,驱动内部的xxx_read xxx_write 等待资源可获取,完成
上层read() write();此时便有了阻塞访问,以及对应的非阻塞访问。
二:关于 阻塞-等待队列 的操作
Linux设备驱动中,可以使用的等待队列(Qait Queue)来实现阻塞进程的唤醒,阻塞的进程可以使用等待队列(Wait_Queue)来实现唤醒,等待队列很早就作为一个基本功能出现在Linux内核里面了,等待队列以 队列 为基础数据结构,与进程调度机制紧密结合,可以用来同步对系统资源的访问。
2.1 等待队列操作:
1 wait_queue_head_t my_head //定义等待队列头
2 init_waitqueue_head(&my_head) //初始化等待队列头
DECLARE_WAIT_QUEUE_HEAD(name) //初始化等待队列头 宏
3 DECLARE_WAITQUEUE(name,tsk) //定义等待队列元素
4 add_wait_queue(wait_queue_head_t *q,wait_queue_t *wait) //添加等待队列元素
remove_wait_queue(wait_queue_head_t *q,wait_queue_t *wait)//移除等待队列元素
5 wait_event(queue,condition)//等待事件
wait_event_interruptible(queue,condition) //
等待第一个参数作为等待队列头部的队列被唤醒,第二个参数condition必须被满足,他们的区别
是 wait_event_interruptible 可以被信号打断,前者不能;
6 wake_up(wait_queue_head_t *queue)//唤醒队列
wake_up_interruptible(wait_queue_head_t *queue)//唤醒队列
唤醒以 queue作为等待队列头部的队列中的所有进程,wake_up可有唤醒处于 TASK_INTERRUPTIBLE
和 TASK_UNINTERRUPTIBLE 的进程,而 wake_up_interruptible 只能唤醒处于TASK_INTERRUPTIBLE状态的
进程
2.2:例程:
在设备驱动中使用等待队列模板:
static ssize_t mac_read(struct file *filp, char *buffer, size_t count, loff_t *ppos)
{
0 //定义等待队列头部
1 //定义等待队列元素
DECLARE_WAITQUEUE(wait,current);
mutex_lock(&dev->mutex);
2 //添加到等待队列
add_wait_queue(&dev->r_wait, &wait);
//等待 缓冲区可写
while(dev->current_len == 0){
//如果是非阻塞访问
if(filp->f_flags & O_NONBLOCK){
return -EAGAIN;
}
//阻塞访问
3 //进行进程状态切换,设置当前进程状态为 浅睡眠,并没有真正睡眠
__set_current_state(TASK_INTERRUPTIBLE);
mutex_unlock(&dev->mutex);
4 //调度当前读进程出去 其他进程执行 此时读进程进入睡眠状态
schedule();
//由于当前读进程切换出去的时候是 TASK_INTERRUPTIBLE 状态,可以被信号唤醒
//判断是否是信号唤醒进程,如果是 将读进程移除等待队列,并设置进程状态已运行
5 if(signal_pending(current))
{
remove_wait_queue(&dev->r_wait, &wait);//从附属的等待队列移除
set_current_state(TASK_RUNNING);
return - ERESTARTSYS;
}
mutex_lock(&dev->mutex);
}
wake_up_interruptible(&dev->w_wait);//唤醒可能阻塞的写进程
return 0;
}
注意 :因为进程调度出去的时候 的进程状态是 TASK_INTERRUPTIBLE ,即浅度睡眠,所以
唤醒他的可能是信号,因此我们首先通过 signal_pending(current)了解是不是信号
唤醒
无论是 读函数 还是写函数,在执行 schedule() 把自己切换出去之前,都要主动释放互斥体
因为如果读进程阻塞,说明是 fifo 空,必须依赖写进程往 FIFO 里面写东西来唤醒读进程,但是
写进程为了写 FIFO 也必须要拿到这个互斥锁来访问这个FIFO临界资源,如果读进程在把自己调度
出去之前没有释放这个互斥体,那么写进程就无法获得互斥体,也就是无法访问FIFO临界资源,导致
读写进程的锁死。