在Linux内核模块

时间:2015-12-01 18:21:51

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

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

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

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

我可以在poll上找到的大多数resources说:

  1. 为每个可能表示状态发生变化的等待队列调用poll_wait
  2. 返回指示数据是否可用的位掩码
  3. 第二部分让我感到困惑。我见过的大多数示例都有一种简单的方法(指针比较或状态位)来检查数据是否可用。在我的情况下,除非我启动DMA ,否则数据将永远不可用,甚至一旦我这样做,数据就不会立即可用(在设备实际拥有数据之前可能需要一些时间) DMA完成)。

    如何实施呢? poll函数是否应该实际启动DMA以使数据最终可用?我想这会破坏我的read功能。

2 个答案:

答案 0 :(得分:8)

声明

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

设计

考虑到您尚未提及write()操作的帐户,我将进一步假设您的硬件始终生成新数据。如果是这样,你提到的设计可能正是让你感到困惑的原因:

  

read电话非常简单。它启动DMA写入,然后等待等待队列。

这正是阻止您以常规,常用(以及可能对您而言)方式使用驱动程序的原因。让我们开箱即用,首先想出用户界面(如何从用户空间使用你的驱动程序)。下一个案例在这里经常使用和充分(从我的观点来看):

  1. poll()您的设备文件等待新数据到达
  2. read()您的设备文件以获取到达的数据
  3. 现在您可以看到请求(到DMA)的数据应该由read()操作启动而不是。正确的解决方案是在驱动程序中连续读取数据(无需从用户空间触发)并在内部存储,并在用户向驱动程序请求数据消耗时(read()操作) - 为用户提供内部存储的数据。如果驱动程序内部没有存储数据 - 用户可以使用poll()操作等待新数据到达。

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

    代码参考

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

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

    补充

    在评论中回答你的问题:

      

    如果没有可用数据且read应该阻止,您建议poll调用read吗?

    首先,您想决定是否要提供:

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

      

    O_NONBLOCKO_NDELAY

         

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

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

    1. 如果flags中的open()不包含此类标记,则可以阻止read()(即,如果数据不可用,请等待DMA事务完成然后返回新数据)。
    2. 但如果O_NONBLOCK标志中有open()并且循环缓冲区中没有可用数据 - 您应该从read()调用EWOULDBLOCK错误代码返回。
    3. 来自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()以等待新数据到达
        • 如果端口有数据:return data from buffer
      4. 另见相关问题:How to add poll function to the kernel module code?

答案 1 :(得分:0)

特殊设备(字符或块1)上的read和其他函数一样,poll函数可以某种方式实现,其表现出行为 you欲望即可。 实现的唯一限制是poll()将通过waitqueue“唤醒”,但这不会限制可能的行为

此外,poll 的行为不需要与read / write 绑定! [同样,仅在特殊设备的情况下才是这样。]

因此,只需将这种行为强加于您认为对您的任务更有用的poll

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

我的同事通过角色设备实现消息的循环缓冲。可以通过mmap()阅读消息。 poll当至少有一页被消息填充时“醒来”。