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