Linux驱动IO篇 | 您所在的位置:网站首页 › linux怎么唤醒 › Linux驱动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); ...... }首先用wait_queue_head_t定义两个等待队列头rwqh和wwqh,分表代表读等待队列和写等待队列,然后在驱动入口函数vser_init中调用init_waitqueue_head初始化等待队列头。 读操作处理 当应用程序想要从驱动中读取数据,而fifo为空时,此时代表没有数据可读。如果是非阻塞方式则直接返回,如果是阻塞方式,调用wait_event_interruptible函数将进程放入读等待队列rwqh,进程休眠。当fifo不为空时,即fifo有数据时,才会被唤醒。 写操作处理 当应用层想往驱动中写数据,而fifo已经满了,此时代表不可写。如果是非阻塞方式直接返回,如果是阻塞方式,则调用wati_event_interruptible函数将进程放入写等待队列,进程休眠。当fifo未满时,才会被唤醒。 唤醒处理 调用wake_up_interruptible函数唤醒对应的等待队列 通过在上述加入等待队列的操作,当以阻塞方式打开设备文件时,资源可用时会被唤醒,而不至于一直休眠。 等待队列变体上述只是使用了等待队列最常用的函数,等待队列还有其他很多的变体,下面列举常见的: DECLARE_WAIT_QUEUE_HEAD(name) init_waitqueue_head(q) wait_event(wq, condition) wait_event_timeout(wq, condition, timeout) wake_up(x) wait_event_interruptible(wq, condition) wait_event_interruptible_timeout(wq, condition, timeour) wait_event_interruptible_exclusive(wq, condition) wake_up_interruptible(x) wait_event_interruptible_locked(wq, condition) wait_event_interruptible_locked_irq(wq, condition) wake_up_locked(x)虽然有很多的变体,但简单的说,使进程休眠调用wait_event_xxx函数,唤醒进程调用wake_up_xxx函数。其余的后缀一一对应即可。 关于这些宏或函数的更多信息请参考“include/linux/wait.h” |
CopyRight 2018-2019 实验室设备网 版权所有 |