在Linux内核模块中实现轮询

我有一个简单的字符设备驱动程序,允许您从自定义硬件设备读取。 它使用DMA将设备内存中的数据复制到内核空间(然后直到用户)。

read电话非常简单。 它开始DMA写入,然后等待一个等待队列。 当DMA完成时,中断处理程序设置一个标志并唤醒等待队列。 重要的是要注意,我可以随时启动DMA, 甚至在设备有数据提供之前 。 DMA引擎将一直等待,直到有数据要复制。 这很好。 我可以在用户空间实现一个简单的阻塞读取调用,它的行为和我所期望的一样。

我想实施poll以便我可以在用户空间使用select系统调用,允许我同时监视此设备和套接字。

我可以在pollfind的大部分资源都说:

  1. 为每个可能指示状态改变的等待队列调用poll_wait
  2. 返回一个表示数据是否可用的掩码

第二部分是让我困惑的。 我见过的大多数示例都有一个简单的方法(一个指针比较或状态位)来检查数据是否可用。 在我的情况下, 除非我启动DMA否则数据永远不可用 ,甚至一旦我这样做了,数据也不会立即可用(可能需要一些时间才能使设备有数据并完成DMA)。

这将如何实施呢? poll函数是否应该启动DMA以使数据最终可用? 我想这会打破我的readfunction。

放弃

那么,这是一个很好的体系结构问题,它暗示了一些有关硬件和所需用户空间接口的假设。 所以,让我跳到结论为改变,并试图猜测哪种解决方案将是最好的在你的情况。

设计

考虑到你没有提到write()操作,我会进一步假设你的硬件一直在产生新的数据。 如果是这样的话,你提到的设计可能正是让你感到困惑的东西:

read电话非常简单。 它开始DMA写入,然后等待一个等待队列。

这正是阻止你在常规,常用(也许是你想要的)的方式与你的驱动程序工作。 让我们先思考一下,首先想出所需的用户界面(如何从用户空间使用您的驱动程序)。 下一个案例在这里是常用和充分的(从我的观点来看):

  1. poll()您的设备文件等待新的数据到达
  2. read()您的设备文件以获取到达的数据

现在你可以看到数据请求(到DMA)不应该通过read()操作来启动。 正确的解决方案是在驱动程序中连续读取数据(不需要用户空间触发)并将其存储在内部,当用户要求驱动程序使用数据时(通过read()操作) – 向用户提供数据存储在内部。 如果在驱动程序中没有内部存储的数据 – 用户可以使用poll()操作等待新数据到达。

正如您所看到的,这是众所周知的生产者 – 消费者问题 。您可以使用循环缓冲区来将硬件中的数据存储在驱动程序中(所以当缓冲区已满时故意丢失旧数据以防止缓冲区溢出情况)。 所以生产者(DMA)写入该RX环形缓冲区的头部 ,并且消费者(用户从用户空间执行read()从该RX环形缓冲区的尾部读取。

代码参考

这一切都让我想起了串口控制台 [ 1,2 ]的驱动程序。 所以考虑在驱动程序实现中使用串行API (如果您的设备实际上串行控制台)。 例如,请参阅drivers / tty / serial / atmel_serial.c驱动程序。 我对UART API并不是很熟悉,所以我不能确切地告诉你在那里发生了什么,但是乍看起来并不难,所以你可以从代码中找出一两个东西你的驱动程序设计

如果您的驱动程序不应该使用串行API,则可以使用下一个驱动程序进行参考:

  • 司机/炭/ virtio_console.c
  • 司机/炭/ xillybus / xillybus_core.c

补充

在评论中回答你的问题:

你是否建议read调用poll时,没有数据可用和read应该阻止?

首先,你要决定,是否要提供:

  • 阻止I / O
  • 非阻塞I / O
  • 或者两者兼而有之

让我们假设(为了争辩)你想在你的驱动程序中提供这两个选项。 在这种情况下,如果flags参数包含O_NONBLOCK标志,则应该检查open()调用。 从man 2 open

O_NONBLOCKO_NDELAY

如果可能,文件以非阻塞模式打开。 对于返回的文件描述符, open()或后续操作都不会导致调用进程等待。 有关FIFO(命名管道)的处理,另请参阅fifo(7) 。 有关O_NONBLOCK与强制文件锁定以及文件租约相关的讨论,请参见fcntl(2)

现在,当你意识到用户选择的模式时,你可以做下一步(在你的驱动程序中):

  1. 如果open()标志不包含这样的标志,那么可以阻塞read() (即如果数据不可用,等待DMA事务完成并返回新数据)。
  2. 但是,如果open()标志中有O_NONBLOCK ,并且循环缓冲区中没有可用的数据,则应该使用EWOULDBLOCK错误代码从read()返回。

man 2 read

EAGAINEWOULDBLOCK

文件描述符fd引用一个套接字并被标记为非阻塞( O_NONBLOCK ),并且读取将被阻塞。 POSIX.1-2001允许在这种情况下返回错误,并且不要求这些常量具有相同的值,所以便携式应用程序应该检查两种可能性。

您也可能想阅读下一篇文章,以更好地掌握相应的接口:

[1] POSIX操作系统的串行编程指南

[2] 串行编程HOWTO

补充2

我需要某种背景任务,不断地从设备读取并填充环形缓冲区。 poll现在是微不足道的 – 只要检查缓冲区中是否有任何内容,但read更困难,因为可能需要等待将某些内容发送到环形缓冲区。

比如看一下drivers / char / virtio_console.c驱动的实现。

  1. 在poll()函数中:做poll_wait() (等待新数据到达)
  2. 在接收数据中断处理程序中 :执行wake_up_interruptible() (唤醒pollread操作)
  3. 在read()函数中:
    • 如果端口没有数据 :
      • 如果O_NONBLOCK标志被设置(在open()操作中):return -EAGAIN = -EWOULDBLOCK立即
      • 否则我们有阻塞读:做wait_event_freezable()等待新的数据到达
    • 如果端口确实有数据: 从缓冲区返回数据

另请参见相关问题: 如何将轮询函数添加到内核模块代码? 。

特殊设备 (字符或块1)上的read和其他功能一样, poll功能可以以某种方式实现,从而展现您所期望的行为。 实现的唯一限制是poll()应该通过waitqueue“唤醒”,但是这不会限制可能的行为

而且, poll行为并不需要被read / write ! [再一次,只有在特殊设备的情况下才是如此。]

所以,只要把这个行为强加于你认为对你的任务更有用的poll


我的一个例子 (可能适用于你的情况):

我的同事通过字符设备实现消息的循环缓冲区。 消息可以通过mmap()读取。 当至少一个页面被消息填充时, poll “醒来”。