非阻塞IO
在应用程序中,使用open函数打开一个/dev
目录下的一个设备文件时,默认是以阻塞的方式打开。
所谓阻塞,就是当我们请求的资源不可用时(资源被占用,没有数据到达等等),会使得进程休眠,从现象看就是卡在那里。
应用层
如果我们希望以非阻塞方式打开设备文件,则应该在open设备文件时,添加一个O_NONBLOCK
的flag参数,例如:
fd = open("/dev/vser0", O_RDWR | O_NONBLOCK);
驱动层
应用层以非阻塞方式打开设备文件,则驱动层也要有对应的处理操作才行。
应用层传入的O_NONBLOCK
标志,会保存在struct file
结构体的f_flags
成员中。当资源不可用时,同时判断f_flags变量是否为O_NONBLOCK
,有则代表以非阻塞方式打开,然后返回一个-EAGAIN
错误,提示应用层资源暂时不可用。
下面是驱动中read
函数非阻塞处理的伪代码,例如:
static ssize_t vser_read(struct file *flip, char __user *buf, size_t count, loff_t *pos)
{
......
if (资源不可用)
if (filp- >f_flags & O_NONBLOCK)
return -EAGAIN;
......
}
当出现资源不可用时,会出现以下提示:
read: Resource temporarily unavailable
阻塞IO
上述非阻塞方式打开设备文件,虽然可以防止进程休眠,无论结果如何都会立即返回,但 缺点是必须要定期查询资源是否可以获得 ,例如上述代码中,每次调用read函数都要去查询一下资源是否可用。这种操作效率非常低。
但是用阻塞IO的话,进程休眠期间就再也不能做其他的事情了 。
所以不论是非阻塞IO还是阻塞IO,都有缺点,有没有好的办法呢?
正确做法应该是: 使用阻塞IO,驱动中添加唤醒操作 。
什么意思呢? 既然有休眠,就应该有对应的唤醒操作,否则进程将会一直休眠下去 。驱动程序应该在资源可用时负责执行唤醒操作。
要实现既有休眠,又有唤醒的阻塞IO模型,应该使用 等待队列 。
等待队列
我们以一个虚拟串口设备为例:
如图是一个虚拟串口设备示例图,这是一个功能弱化之后的只具备内回环作用的串口。
主要功能 :在驱动中实现一个FIFO
,驱动接收用户层传来的数据,然后将之放入FIFO
,当应用层要获取数据时,驱动将FIFO
中的数据读出,然后复制给应用层。
我们以这个虚拟串口设备为例,讲解等待队列的使用。
为了方便理解,简化了不必要的代码,下面是驱动代码:
//定义内核fifo
DEFINE_KFIFO(vsfifo, char ,32)
//定义两个等待队列头
wait_queue_head_t rwqh;//读等待队列
wait_queue_head_t wwqh;//写等待队列
static ssize_t vser_read(struct file *flip, char __user *buf, size_t count, loff_t *pos)
{
......
/* fifo为空,没有数据可读,进入休眠*/
if(kfifo_is_empty(&vsfifo)) {
if(flip- >f_flags & O_NONBLOCK)//非阻塞方式,直接返回
return -EAGAIN;
/* 阻塞方式,没有数据可读,将进程放入读等待队列rwqh,进程休眠;唤醒条件是fifo不为空 */
if (wait_event_interruptible(rwqh, !kfifo_is_empty(vsfifo)))
return -ERESTARTSYS;
}
//将fifo中的数据返回给应用层
ret = kfifo_to_user(&vsfifo, buf, count, &copied);
/* fifo未满,还有空间,代表可以往fifo写数据,唤醒写等待队列 */
if (!kfifo_is_full(&vsfifo))
wake_up_interruptible(&wwqh);
......
}
static ssize_t vser_write(struct file *flip, const char __user *buf, size_t count, loff_t *pos)
{
......
/* fifo已满,不可写 */
if (kfifo_is_full(&vsfifo)) {
if (flip- >f_flags & O_NONBLOCK)//非阻塞方式,直接返回
return -EAGAIN;
/* 阻塞方式,进程休眠,放入写等待队列,唤醒条件是fifo未满时 */
if (wati_event_interruptible(wwqh, !kfifo_is_full(&vsfifo)))
return -ERESTARTSYS;
}
//从应用层获取数据,写入fifo
ret = kfifo_from_user(&vsfifo, buf, count, &copied);
/* fifo不为空,唤醒读等待队列rwqh */
if (!kfifo_is_empty(&vsfifo))
wake_up_interruptible(&rwqh);
......
}
/* 驱动入口函数 */
static int __init vser_init(void)
{
......
/* 初始化等待队列头 */
init_waitqueue_head(rwqh);
init_waitqueue_head(wwqh);
......
}