【问题标题】:Implementing poll in a Linux kernel module在 Linux 内核模块中实现轮询
【发布时间】:2015-12-01 18:21:50
【问题描述】:

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

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

我想实现poll,这样我就可以在用户空间中使用select系统调用,让我可以同时监控这个设备和一个套接字。

我可以在poll 上找到大部分resources 说:

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

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

那么这将如何实现? poll 函数是否应该真正启动 DMA 以便数据最终变得可用?我想这会破坏我的read 功能。

【问题讨论】:

    标签: linux select linux-kernel driver linux-device-driver


    【解决方案1】:

    免责声明

    嗯,这是一个很好的架构问题,它暗示了一些关于您的硬件和所需用户空间界面的假设。因此,让我跳入结论进行更改,并尝试猜测哪种解决方案最适合您的情况。

    设计

    考虑到您没有提到write() 操作,我将进一步假设您的硬件一直在生成新数据。如果是这样,您提到的设计可能正是让您感到困惑的地方:

    read 调用非常简单。它开始 DMA 写入,然后在等待队列中等待。

    这正是阻止您以常规、常用(并且可能是您想要的)方式使用驱动程序的原因。让我们开箱即用,首先提出所需的用户界面(您希望如何从用户空间使用驱动程序)。下一种情况在这里常用且足够(从我的角度来看):

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

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

    如您所见,这是众所周知的producer-consumer problem。您可以使用circular buffer 将来自硬件的数据存储在驱动程序中(因此当缓冲区已满时,您会故意丢失旧数据以防止缓冲区溢出 情况)。因此生产者 (DMA) 写入该 RX 环形缓冲区的 head,而消费者(用户从用户空间执行 read())从该 RX 的 tail 读取环形缓冲区。

    代码参考

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

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

    互补

    在评论中回答您的问题:

    您是否建议read 在没有可用数据且read 应阻止时调用poll

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

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

    O_NONBLOCKO_NDELAY

    尽可能以非阻塞模式打开文件。 open() 和返回的文件描述符上的任何后续操作都不会导致调用进程等待。有关 FIFO(命名管道)的处理,另请参阅 fifo(7)。有关O_NONBLOCK 与强制文件锁定和文件租约结合的影响的讨论,请参阅fcntl(2)

    现在,当您知道用户选择的模式时,您可以执行下一步(在您的驱动程序中):

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

    来自man 2 read

    EAGAINEWOULDBLOCK

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

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

    [1]Serial Programming Guide for POSIX Operating Systems

    [2]Serial Programming HOWTO

    补充2

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

    例如查看drivers/char/virtio_console.c 驱动程序实现。

    1. poll()函数中:做poll_wait()(等待新数据到达)
    2. receive data interrupt handler:做wake_up_interruptible()(唤醒pollread操作)
    3. read()函数中:
      • 如果端口has no data:
        • 如果设置了O_NONBLOCK 标志(在open() 操作中):立即返回-EAGAIN = -EWOULDBLOCK
        • 否则我们会阻塞读取:执行wait_event_freezable() 以等待新数据到达
      • 如果端口有数据:返回data from buffer

    另请参阅相关问题:How to add poll function to the kernel module code?

    【讨论】:

    • 非常有帮助。我在想我需要一些在后台总是启动 DMA 传输的东西。不过有一个问题 - 您是否建议在没有可用数据时 read 调用 poll 并且 read 应该阻止?
    • 我添加了 补充 部分,并为您的问题提供了答案(希望如此)。
    • 我想我明白了。我需要某种不断从设备读取并填充环形缓冲区的后台任务。 poll 现在是微不足道的 - 只需检查该缓冲区中是否有任何内容,但 read 更困难,因为它可能需要等待将某些内容发布到环形缓冲区。
    • 是的,你没看错。有关如何实现 pollread 的解释代码示例,请参阅补充 2
    • 你帮了大忙。谢谢!
    【解决方案2】:

    read特殊设备(字符或块)上的其他功能一样,@​​987654322@ 功能可以以某种方式实现,它表现出您想要的行为实现的唯一限制是poll() 应通过等待队列“唤醒”,但这并不限制可能的行为

    另外,poll的行为不需要绑定read/write! [同样,这仅适用于特殊设备。]

    所以,只需poll 施加您认为对您的任务更有用的行为


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

    我的同事通过字符设备实现消息的循环缓冲区。可以通过mmap() 阅读消息。 poll“唤醒”至少一页已被消息填满。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-04-11
      • 2013-12-17
      • 1970-01-01
      • 2011-07-01
      • 1970-01-01
      • 2013-06-27
      相关资源
      最近更新 更多