以低延迟访问 PCI 内存条 (Linux)

Access PCI memory BAR with low latency (Linux)

背景:

我有一块PCI卡,基本上就是一个时钟。它通过GPS获取时间并将当前时间保存在某个寄存器中。

目标:

我想一遍又一遍地读取有限数量的registers/bytes(例如当前时间),尽可能低的延迟。 (时钟提供非常高的精度,我认为延迟越高,精度就会降低。)。操作系统是RedHat。编程语言是C/C++。我还想写入设备内存,延迟不是问题。

可能的路线:

我看到了这些方式。如果你看到另一个,请告诉我:

  1. 编写一个Linux内核模块驱动程序,创建一个字符设备(或一个字符设备供每个寄存器读取)。然后用户 space 应用程序可以对 /dev/ 文件执行 "read"。
  2. DMA
  3. 通过用户 space 应用程序(系统调用)将 sysfs resourceX 文件映射到用户 space。 (例如 here
  4. 写一个Linux 内核模块驱动,实现mmap文件操作。

问题:

  1. 在实际读取寄存器时,哪种方式延迟最低?我知道 mmap 会在内核中造成大量开销,但据我所知,这仅用于初始化。
  2. 方式 3 是合法的方式吗?对我来说这看起来像是一个黑客。如何从应用程序自动确定 /sys/ 路径?
  3. 方式3和方式4有区别吗?我是 PCI 驱动程序编程的新手,我想我并不真正了解方式 4 的工作原理。我读了 this(以及那本书的其他章节),但也许你可以给我一个提示或一个例子。我将不胜感激。

方法 3 或 4 应该可以正常工作。它们在延迟方面没有区别。延迟大约为 100 ns。

如果您需要初始化设备,或控制允许哪些应用程序访问它,或一次强制执行一个 reader 等,则需要方法 4。方法 3 确实有点像一个 hack,因为它跳过了所有这些。但是如果你不需要这些东西就更简单了。

字符设备肯定有更高的延迟,因为每次读取设备时都需要内核转换。

DMA 方法的延迟完全取决于设备将时间写入内存的频率。 CPU 访问内存的延迟比 MMIO 低,但是如果设备每毫秒只执行一次 DMA,那么这就是您的延迟。此外,该方法会产生大量无用的 DMA 流量,因为 CPU 读取值的频率远低于写入值的频率。

添加到@prl 的回答中...

方法 3 对我来说似乎完全合法。这就是它的用途。您可能想看看内核文档文件:https://www.kernel.org/doc/Documentation/filesystems/sysfs-pci.txt

您还可以使用 /sys 文件系统来查找您的设备。首先,记下时钟卡的供应商 ID 和设备 ID(如果需要,还有子系统供应商/设备),然后您可以轻松地遍历 /sys/devices 层次结构,寻找匹配的设备(使用 vendordevice等特殊文件)。找到它后,您大概知道从设备数据 sheet 中打开哪个 resourceN 文件,然后 mmap 它位于适当的偏移量,您就完成了。

所有这些都假定您的设备已经配置并启用。通常,当系统启动时,PCI 设备不会执行任何操作。一些驱动程序需要声明设备,并初始化/配置它。完成后,如果仅通过读取一两个寄存器即可访问时间,则可以使用方法 3。(我不确定:它 may 可能用于 PCI设备是自初始化的,但我从未见过。我认为可能至少需要启用它的内存 space。如果设置是,可能可以从 user-space 完成足够小/足够简单。)

与方法 4 的主要区别在于,控制设备的驱动程序将提供支持以允许显式 mmap 区域。对于 user-space 应用程序,除了使用的设备名称外,这两种方法之间几乎没有区别。对于方法 4,驱动程序可能会提供一个符号设备名称 /dev/clock0 或类似的名称供 user-space 应用程序使用(并且大概应用程序不需要去寻找设备, 它只会 知道 要打开的设备文件名)。

从 user-space,您将使用任何一种方法以几乎相同的方式执行 mmap 操作。在方法 4 中,驱动程序在内部提供要映射的物理地址——可能还有偏移量——而不是通用 PCI 子系统这样做,但无论哪种方式,它只是 open + mmap.

Linux 驱动程序编程并不难,但如果您以前没有做过,那么那里有一个显着的学习曲线,所以除非确实需要,否则我绝对不会使用方法 4这样做。