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