我有一个简单的字符设备驱动程序,允许您从自定义硬件设备读取.它使用DMA将数据从设备的内存复制到内核空间(然后再复制到用户).
该read
调用非常简单.它启动DMA写入,然后等待等待队列.当DMA完成时,中断处理程序设置一个标志并唤醒等待队列.需要注意的重要一点是,我可以随时启动DMA,甚至在设备提供数据之前.DMA引擎将等待,直到有数据要复制.这很好用.我可以在用户空间中实现一个简单的阻塞读取调用,它的行为与我期望的一样.
我想实现,poll
以便我可以select
在用户空间中使用系统调用,允许我同时监视此设备和套接字.
我能找到的大部分资源poll
都说:
调用poll_wait
可能表示状态发生变化的每个等待队列
返回一个指示数据是否可用的位掩码
第二部分让我感到困惑.我见过的大多数示例都有一种简单的方法(指针比较或状态位)来检查数据是否可用.在我的情况下,除非我启动DMA,否则数据将永远不可用,即使我这样做,数据也不会立即可用(在设备实际拥有数据和DMA完成之前可能需要一些时间).
那怎么实现呢?该poll
函数是否应该实际启动DMA以使数据最终可用?我想这会破坏我的read
功能.
嗯,这是一个很好的架构问题,它暗示了一些关于硬件和所需用户空间接口的假设.因此,让我跳出结论进行更改,并尝试猜测哪种解决方案最适合您的情况.
考虑到您未提及write()
操作的帐户,我将进一步假设您的硬件始终在生成新数据.如果是这样,你提到的设计可能正是令你困惑的:
该
read
调用非常简单.它启动DMA写入,然后等待等待队列.
这正是阻止您以常规,常用(以及可能对您而言)方式使用驱动程序的原因.让我们开箱即用,首先想出用户界面(如何从用户空间使用你的驱动程序).下一个案例在这里常用且充足(从我的观点来看):
poll()
您的设备文件等待新数据到达
read()
您的设备文件以获取到达的数据
现在您可以看到请求(到DMA)的数据应该不是通过read()
操作来启动的.正确的解决方案将是连续驾驶者读取数据(未经用户空间任何触发),并将其存储在内部,当用户请求你的驱动程序的数据消费(通过read()
操作) -为用户提供内部存储的数据.如果驱动程序内部没有存储数据 - 用户可以使用poll()
操作等待新数据到达.
正如您所看到的,这是众所周知的生产者 - 消费者问题.您可以使用循环缓冲区将硬件中的数据存储在驱动程序中(因此当缓冲区已满时故意丢失旧数据以防止缓冲区溢出情况).因此,生产者(DMA)写入该RX环形缓冲区的头部,而消费者(用户read()
空间执行的用户)从该RX环形缓冲区的尾部读取.
这一切情况让我想起了串行控制台 [ 1,2 ]的驱动程序.因此,请考虑在驱动程序实现中使用串行API(如果您的设备实际上是串行控制台).例如,请参阅drivers/tty/serial/atmel_serial.c驱动程序.我对UART API并不是很熟悉,所以我无法准确地告诉你那里发生了什么,但乍一看它看起来并不太难,所以可能你可以从那个代码中找到一两件事你的司机设计.
如果您的驱动程序不应使用Serial API,则可以使用下一个驱动程序进行引用:
司机/炭/ virtio_console.c
司机/炭/ xillybus/xillybus_core.c
在评论中回答你的问题:
如果没有可用的数据并且应该阻止,你建议
read
打电话吗?poll
read
首先,您想决定是否要提供:
阻止I/O.
非阻塞I/O.
或两者兼而有之
让我们假设(为了争论)你想在你的驱动程序中提供这两个选项.在这种情况下,open()
如果flags
参数包含O_NONBLOCK
标志,则应检入呼叫.来自man 2 open
:
O_NONBLOCK
要么O_NDELAY
如果可能,文件以非阻塞模式打开.无论是
open()
也不在其返回将导致调用进程等待的文件描述符的任何后续操作.有关处理FIFO(命名管道)的信息,另请参阅fifo(7)
.有关O_NONBLOCK
与强制文件锁和文件租约一起使用的效果的讨论,请参阅fcntl(2)
.
现在,当您了解用户选择的模式时,您可以执行下一步(在您的驱动程序中):
如果flags
in open()
不包含此类标志,则可以执行阻塞read()
(即,如果数据不可用,请等待DMA事务完成然后返回新数据).
但如果O_NONBLOCK
在open()
标志并没有在循环缓冲区可用数据-你应该返回read()
调用与EWOULDBLOCK
错误代码.
来自man 2 read
:
EAGAIN
要么EWOULDBLOCK
文件描述符
fd
引用套接字并且已标记为nonblocking(O_NONBLOCK
),并且读取将阻塞.POSIX.1-2001允许在这种情况下返回错误,并且不要求这些常量具有相同的值,因此便携式应用程序应检查这两种可能性.
您还可以阅读下一篇文章,以便更好地掌握相应的界面:
[1] POSIX操作系统的串行编程指南
[2] 串行编程HOWTO
我需要某种后台任务,不断从设备读取并填充环形缓冲区.
poll
现在是微不足道的 - 只是检查缓冲区中是否有任何东西,但read
更难,因为它可能需要等待将某些内容发布到环形缓冲区.
例如,查看drivers/char/virtio_console.c驱动程序实现.
在poll()函数中:do poll_wait()
(等待新数据到达)
在接收数据中断处理程序中:do wake_up_interruptible()
(唤醒poll
和read
操作)
在read()函数中:
如果端口没有数据:
如果O_NONBLOCK
设置了标志(在open()
操作中):return -EAGAIN
= -EWOULDBLOCK
立即
否则我们有阻塞读取:wait_event_freezable()
等待新数据到达
如果端口有数据:从缓冲区返回数据
另请参阅相关问题:如何将poll函数添加到内核模块代码中?.