在 Linux 内核模块中实现轮询
Implementing poll in a Linux kernel module
我有一个简单的字符设备驱动程序,可让您从自定义硬件设备读取数据。它使用 DMA 将数据从设备内存复制到内核 space(然后由用户决定)。
read
调用非常简单。它开始 DMA 写入,然后在等待队列中等待。当 DMA 完成时,中断处理程序设置一个标志并唤醒等待队列。需要注意的重要一点是,我可以随时启动 DMA,甚至在设备有数据提供之前。 DMA 引擎将等待直到有数据要复制。这很好用。我可以在用户 space 中实现一个简单的阻塞读取调用,它的行为符合我的预期。
我想实现 poll
以便我可以在用户 space 中使用 select
系统调用,允许我同时监视此设备和套接字。
我能在 poll
上找到的大部分 说到:
- 为每个可能指示状态变化的等待队列调用
poll_wait
- return 指示数据是否可用的位掩码
第二部分让我感到困惑。我见过的大多数示例都有一种简单的方法(指针比较或状态位)来检查数据是否可用。在我的例子中,数据 将永远不可用,除非我启动 DMA,即使我这样做了,数据也不会立即可用(设备实际拥有数据可能需要一些时间并让 DMA 完成)。
那怎么实现呢? poll
函数是否应该实际启动 DMA,以便数据最终可用?我想这会破坏我的 read
功能。
与read
和特殊设备(字符或块一)上的其他功能一样,poll
功能可以以某种方式实现,展示你想要的行为。 实现的唯一限制是poll()
应通过等待队列"awoken",但这并不限制可能的行为 .
此外,poll
的行为不需要与read
/write
绑定! [同样,这仅适用于 特殊设备。]
因此,只需 将您认为对您的任务更有用的行为强加给 poll
。
我实践中的一个例子(可能适用于你的情况):
我的同事通过字符设备实现消息的循环缓冲区。可以通过 mmap()
阅读消息。 poll
"awakes" 当至少一页已被消息填满时。
免责声明
好吧,这是一个很好的体系结构问题,它暗示了一些关于您的硬件和所需的用户-space 界面的假设。因此,让我直接得出更改的结论,并尝试猜测哪种解决方案最适合您的情况。
设计
考虑到你没有提到write()
操作,我进一步假设你的硬件一直在生产新数据。如果是这样,您提到的设计可能正是让您感到困惑的地方:
The read
call is very simple. It starts a DMA write, and then waits on a wait queue.
这正是阻止您以常规、常用(并且可能是您想要的)方式使用驱动程序的原因。让我们跳出框框思考并首先提出所需的用户界面(您希望如何使用来自 user-space 的驱动程序)。下一个案例是常用的并且在这里足够了(从我的角度来看):
poll()
您的设备文件等待新数据到达
read()
你的设备文件获取到达的数据
现在您可以看到数据请求(到 DMA)应该是 而不是 由 read()
操作开始的。正确的解决方案是在驱动程序中连续读取数据(没有任何来自 user-space 的触发)并将其存储在内部,当用户向您的驱动程序询问数据以 consume(通过read()
操作)——为用户提供内部存储的数据。如果驱动程序内部没有存储数据——用户可以使用poll()
操作等待新数据到达。
如您所见,这是众所周知的 producer-consumer problem.You can use circular buffer 将硬件数据存储在驱动程序中(因此当缓冲区已满时您故意丢失旧数据以防止 缓冲区溢出情况)。因此,生产者 (DMA) 写入该 RX 环形缓冲区的 head,而消费者(用户从 user-space 执行 read()
)从 tail 那个 RX 环形缓冲区。
代码参考
这一切让我想起了串行控制台 [1, 2] drivers. So consider using Serial API in your driver implementation (if your device in fact is a serial console). For example see drivers/tty/serial/atmel_serial.c驱动程序。我不太熟悉 UART API,所以我不能准确地告诉你那里发生了什么,但乍一看并不太难,所以你可能会弄清楚一两件事来自您的驱动程序设计的代码。
如果你的驱动不应该使用串口API,你可以参考下一个驱动:
互补
在评论中回答您的问题:
are you suggesting that read
calls poll
when there is no data available and read
should block?
首先你要决定,是否要提供:
- 阻塞I/O
- non-blocking I/O
- 或两者
让我们假设(为了论证)您想要在您的驱动程序中提供这两个选项。在这种情况下,如果 flags
参数包含 O_NONBLOCK
标志,您应该检查 open()
调用。来自 man 2 open
:
O_NONBLOCK
or O_NDELAY
When possible, the file is opened in nonblocking mode. Neither the open()
nor any subsequent operations on the file descriptor which is returned will cause the calling process to wait. For the handling of FIFOs (named pipes), see also fifo(7)
. For a discussion of the effect of O_NONBLOCK
in conjunction with mandatory file locks and with file leases, see fcntl(2)
.
现在当你知道用户选择的模式时,你可以做下一步(在你的驱动程序中):
- 如果
open()
中的 flags
不包含此类标志,您可以进行阻塞 read()
(即,如果数据不可用,等待 DMA 事务完成,然后 return新数据)。
- 但是如果
open()
标志中有 O_NONBLOCK
并且循环缓冲区中没有可用数据——你应该 return 从 read()
调用 EWOULDBLOCK
错误代码。
来自 man 2 read
:
EAGAIN
or EWOULDBLOCK
The file descriptor fd
refers to a socket and has been marked nonblocking (O_NONBLOCK
), and the read would block. POSIX.1-2001 allows either error to be returned for this case, and does not require these constants to have the same value, so a portable application should check for both possibilities.
您可能还想阅读下一篇文章以更好地掌握相应的接口:
[1] Serial Programming Guide for POSIX Operating Systems
互补2
I need some sort of background task that is continuously reading from the device and populating the ring buffer. poll
is now trivial - just check if there's anything in that buffer, but read
is more difficult because it may need to wait for something to be posted to the ring buffer.
例如查看 drivers/char/virtio_console.c 驱动程序实现。
- 在poll()函数中:执行
poll_wait()
(等待新数据到达)
- 在receive data interrupt handler中:执行
wake_up_interruptible()
(唤醒poll
和read
操作)
- 在read()函数中:
- 如果端口 has no data:
- 如果设置了
O_NONBLOCK
标志(在 open()
操作中): return -EAGAIN
= -EWOULDBLOCK
立即
- 否则我们会阻塞读取:
wait_event_freezable()
等待新数据到达
- 如果端口确实有数据:return data from buffer
另见相关问题:。
我有一个简单的字符设备驱动程序,可让您从自定义硬件设备读取数据。它使用 DMA 将数据从设备内存复制到内核 space(然后由用户决定)。
read
调用非常简单。它开始 DMA 写入,然后在等待队列中等待。当 DMA 完成时,中断处理程序设置一个标志并唤醒等待队列。需要注意的重要一点是,我可以随时启动 DMA,甚至在设备有数据提供之前。 DMA 引擎将等待直到有数据要复制。这很好用。我可以在用户 space 中实现一个简单的阻塞读取调用,它的行为符合我的预期。
我想实现 poll
以便我可以在用户 space 中使用 select
系统调用,允许我同时监视此设备和套接字。
我能在 poll
上找到的大部分
- 为每个可能指示状态变化的等待队列调用
poll_wait
- return 指示数据是否可用的位掩码
第二部分让我感到困惑。我见过的大多数示例都有一种简单的方法(指针比较或状态位)来检查数据是否可用。在我的例子中,数据 将永远不可用,除非我启动 DMA,即使我这样做了,数据也不会立即可用(设备实际拥有数据可能需要一些时间并让 DMA 完成)。
那怎么实现呢? poll
函数是否应该实际启动 DMA,以便数据最终可用?我想这会破坏我的 read
功能。
与read
和特殊设备(字符或块一)上的其他功能一样,poll
功能可以以某种方式实现,展示你想要的行为。 实现的唯一限制是poll()
应通过等待队列"awoken",但这并不限制可能的行为 .
此外,poll
的行为不需要与read
/write
绑定! [同样,这仅适用于 特殊设备。]
因此,只需 将您认为对您的任务更有用的行为强加给 poll
。
我实践中的一个例子(可能适用于你的情况):
我的同事通过字符设备实现消息的循环缓冲区。可以通过 mmap()
阅读消息。 poll
"awakes" 当至少一页已被消息填满时。
免责声明
好吧,这是一个很好的体系结构问题,它暗示了一些关于您的硬件和所需的用户-space 界面的假设。因此,让我直接得出更改的结论,并尝试猜测哪种解决方案最适合您的情况。
设计
考虑到你没有提到write()
操作,我进一步假设你的硬件一直在生产新数据。如果是这样,您提到的设计可能正是让您感到困惑的地方:
The
read
call is very simple. It starts a DMA write, and then waits on a wait queue.
这正是阻止您以常规、常用(并且可能是您想要的)方式使用驱动程序的原因。让我们跳出框框思考并首先提出所需的用户界面(您希望如何使用来自 user-space 的驱动程序)。下一个案例是常用的并且在这里足够了(从我的角度来看):
poll()
您的设备文件等待新数据到达read()
你的设备文件获取到达的数据
现在您可以看到数据请求(到 DMA)应该是 而不是 由 read()
操作开始的。正确的解决方案是在驱动程序中连续读取数据(没有任何来自 user-space 的触发)并将其存储在内部,当用户向您的驱动程序询问数据以 consume(通过read()
操作)——为用户提供内部存储的数据。如果驱动程序内部没有存储数据——用户可以使用poll()
操作等待新数据到达。
如您所见,这是众所周知的 producer-consumer problem.You can use circular buffer 将硬件数据存储在驱动程序中(因此当缓冲区已满时您故意丢失旧数据以防止 缓冲区溢出情况)。因此,生产者 (DMA) 写入该 RX 环形缓冲区的 head,而消费者(用户从 user-space 执行 read()
)从 tail 那个 RX 环形缓冲区。
代码参考
这一切让我想起了串行控制台 [1, 2] drivers. So consider using Serial API in your driver implementation (if your device in fact is a serial console). For example see drivers/tty/serial/atmel_serial.c驱动程序。我不太熟悉 UART API,所以我不能准确地告诉你那里发生了什么,但乍一看并不太难,所以你可能会弄清楚一两件事来自您的驱动程序设计的代码。
如果你的驱动不应该使用串口API,你可以参考下一个驱动:
互补
在评论中回答您的问题:
are you suggesting that
read
callspoll
when there is no data available andread
should block?
首先你要决定,是否要提供:
- 阻塞I/O
- non-blocking I/O
- 或两者
让我们假设(为了论证)您想要在您的驱动程序中提供这两个选项。在这种情况下,如果 flags
参数包含 O_NONBLOCK
标志,您应该检查 open()
调用。来自 man 2 open
:
O_NONBLOCK
orO_NDELAY
When possible, the file is opened in nonblocking mode. Neither the
open()
nor any subsequent operations on the file descriptor which is returned will cause the calling process to wait. For the handling of FIFOs (named pipes), see alsofifo(7)
. For a discussion of the effect ofO_NONBLOCK
in conjunction with mandatory file locks and with file leases, seefcntl(2)
.
现在当你知道用户选择的模式时,你可以做下一步(在你的驱动程序中):
- 如果
open()
中的flags
不包含此类标志,您可以进行阻塞read()
(即,如果数据不可用,等待 DMA 事务完成,然后 return新数据)。 - 但是如果
open()
标志中有O_NONBLOCK
并且循环缓冲区中没有可用数据——你应该 return 从read()
调用EWOULDBLOCK
错误代码。
来自 man 2 read
:
EAGAIN
orEWOULDBLOCK
The file descriptor
fd
refers to a socket and has been marked nonblocking (O_NONBLOCK
), and the read would block. POSIX.1-2001 allows either error to be returned for this case, and does not require these constants to have the same value, so a portable application should check for both possibilities.
您可能还想阅读下一篇文章以更好地掌握相应的接口:
[1] Serial Programming Guide for POSIX Operating Systems
互补2
I need some sort of background task that is continuously reading from the device and populating the ring buffer.
poll
is now trivial - just check if there's anything in that buffer, butread
is more difficult because it may need to wait for something to be posted to the ring buffer.
例如查看 drivers/char/virtio_console.c 驱动程序实现。
- 在poll()函数中:执行
poll_wait()
(等待新数据到达) - 在receive data interrupt handler中:执行
wake_up_interruptible()
(唤醒poll
和read
操作) - 在read()函数中:
- 如果端口 has no data:
- 如果设置了
O_NONBLOCK
标志(在open()
操作中): return-EAGAIN
=-EWOULDBLOCK
立即 - 否则我们会阻塞读取:
wait_event_freezable()
等待新数据到达
- 如果设置了
- 如果端口确实有数据:return data from buffer
- 如果端口 has no data:
另见相关问题: