在 Linux 内核模块中实现轮询 [英] Implementing poll in a Linux kernel module

查看:39
本文介绍了在 Linux 内核模块中实现轮询的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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

我想实现 poll 以便我可以在用户空间使用 select 系统调用,允许我同时监控这个设备和一个套接字.

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

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

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

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

解决方案

免责声明

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

设计

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

<块引用>

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

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

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

现在您可以看到数据请求(到 DMA)应该不是read() 操作开始.正确的解决方案是在驱动程序中连续读取数据(没有来自用户空间的任何触发)并将其存储在内部,当用户要求您的驱动程序使用的数据(通过<代码>读取() operation) -- 向用户提供内部存储的数据.如果驱动程序内部没有存储数据——用户可以使用 poll() 操作等待新数据到达.

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

代码参考

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

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

补充

在评论中回答您的问题:

<块引用>

您是否建议 read 在没有可用数据时调用 poll 并且 read 应该阻塞?

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

让我们假设(为了论证)您想在驱动程序中提供这两个选项.在这种情况下,如果 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() 返回code> 调用带有 EWOULDBLOCK 错误代码.

来自man 2 read:

<块引用>

EAGAINEWOULDBLOCK

文件描述符fd 引用了一个socket,并被标记为非阻塞(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() 操作中):返回 -EAGAIN = -EWOULDBLOCK立即
      • 否则我们会阻塞读取:执行 wait_event_freezable() 以等待新数据到达
    • 如果端口确实有数据:从缓冲区返回数据

<小时>

另见相关问题:如何添加投票函数到内核模块代码?.

I have a simple character device driver that allows you to read from a custom hardware device. It uses a DMA to copy data from the device's memory into kernel space (and then up to the user).

The read call is very simple. It starts a DMA write, and then waits on a wait queue. When the DMA completes, the interrupt handler sets a flag and wakes up the wait queue. The important thing to note is that I can start the DMA at any time, even before the device has data to provide. The DMA engine will sit and wait until there is data to copy. This works well. I can implement a simple blocking read call in user space and it behaves as I would expect.

I would like to implement poll so that I can use the select system call in userspace, allowing me to monitor both this device and a socket simultaneously.

Most of the resources I can find on poll say to:

  1. call poll_wait for each wait queue that may indicate a change in status
  2. return a bit mask indicating whether data is available

The second part is what confuses me. Most of the examples I've seen have an easy way (a pointer comparison or status bit) to check whether data is available. In my case, data will never be available unless I initiate the DMA, and even once I do that, the data is not immediately available (it may take some time before the device actually has data and for the DMA to complete).

How would this be implemented then? Should the poll function actually start the DMA so that the data eventually becomes available? I imagine this would break my read function.

解决方案

Disclaimer

Well, this is a good architectural question and it implies some assumptions about your hardware and desired user-space interface. So let me jump into conclusions for a change and try to guess which solution would be best in your case.

Design

Taking into the account that you haven't mentioned write() operation, I will assume further that your hardware is producing new data all the time. If it's so, the design you mentioned can be exactly what is confusing you:

The read call is very simple. It starts a DMA write, and then waits on a wait queue.

This is exactly what prevents you from working with your driver in regular, commonly used (and probably desired for you) way. Let's think out of the box and come up with the desired user interface first (how you would want to use your driver from user-space). The next case is commonly used and sufficient here (from my point of view):

  1. poll() your device file to wait for new data to arrive
  2. read() your device file to obtain arrived data

Now you can see that data requesting (to DMA) should be started not by read() operation. The correct solution would be to read data continuously in the driver (without any triggering from user-space) and store it internally, and when user asks your driver for the data to consume (by read() operation) -- provide the user with data stored internally. If there is no data stored internally in driver -- user can wait for new data to arrive using poll() operation.

As you can see, this is well-known producer-consumer problem.You can use circular buffer to store data from your hardware in your driver (so you intentionally lost old data when buffer is full to prevent buffer overflow situation). So the producer (DMA) writes to the head of that RX ring buffer, and the consumer (user performing read() from user-space) reads from tail of that RX ring buffer.

Code references

This all situation reminds me of serial console [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 driver. I'm not really familiar with UART API, so I can't tell you precisely what's going on there, but it doesn't look too hard at the first glance, so probably you can figure out a thing or two from that code for your driver design.

If your driver shouldn't use Serial API, you can use next drivers for references:

Complementary

Answering your question in comment:

are you suggesting that read calls poll when there is no data available and read should block?

First of all, you want to decide, whether you want to provide:

Let's assume (for the sake of argument) that you want to provide both options in your driver. In that case, you should check in open() call if flags parameter contains O_NONBLOCK flag. From 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).

Now when you're aware of mode chosen by user, you can do next (in your driver):

  1. If flags in open() don't contain such flags, you can do blocking read() (i.e. if data is not available, wait for DMA transaction to finish and then return new data).
  2. But if there is O_NONBLOCK in open() flags and there is no data available in circular buffer -- you should return from read() call with EWOULDBLOCK error code.

From 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.

You also may want to read next articles to get a better grasp on corresponding interfaces:

[1] Serial Programming Guide for POSIX Operating Systems

[2] Serial Programming HOWTO

Complementary 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.

For example look at drivers/char/virtio_console.c driver implementation.

  1. In poll() function: do poll_wait() (to wait for new data to arrive)
  2. In receive data interrupt handler: do wake_up_interruptible() (to wake up poll and read operations)
  3. In read() function:
    • if port has no data:
      • if O_NONBLOCK flag was set (in open() operation): return -EAGAIN = -EWOULDBLOCK immediately
      • otherwise we have blocking read: do wait_event_freezable() to wait for new data to arrive
    • if port do have data: return data from buffer


See also related question: How to add poll function to the kernel module code?.

这篇关于在 Linux 内核模块中实现轮询的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆