Linux 上的低延迟串行通信

Posted

技术标签:

【中文标题】Linux 上的低延迟串行通信【英文标题】:Low latency serial communication on Linux 【发布时间】:2012-10-19 00:48:32 【问题描述】:

我正在 Linux 上通过串行端口实现协议。该协议基于请求应答方案,因此吞吐量受限于将数据包发送到设备并获得应答所需的时间。这些设备大多基于 arm 并运行 Linux >= 3.0。我在将往返时间减少到 10 毫秒以下时遇到了麻烦(115200 波特,8 个数据位,无奇偶校验,每条消息 7 个字节)。

哪些 IO 接口的延迟最低:select、poll、epoll 或使用 ioctl 手动轮询?阻塞或非阻塞 IO 会影响延迟吗?

我尝试使用 setserial 设置 low_latency 标志。不过好像没什么效果。

还有什么我可以尝试减少延迟的方法吗?由于我控制所有设备,因此甚至可以修补内核,但最好不要。

---- 编辑----

串行控制器使用的是 16550A。

【问题讨论】:

你用的是什么类型的串口? USB/串行接口可能有点慢。 您需要检查这 10 毫秒的花费,因为如果它们被其他设备丢失,您将无法进行更多优化。 请求和应答消息的大小是多少?如果两者都超过 100 字节,那么您无法获得 115200 非常慢,因此只需移动字节就可以保证很大的延迟。最好的选择是将波特率提高到 921600 之类的值。或者更好的是,切换到千兆以太网。 @OttavioCampana 目前正在等待输入。我一直在轮询,直到 ioctl 告诉我输入可用并且我读取它为止。 【参考方案1】:

请求/应答方案往往效率低下,并且在串行端口上很快出现。如果您对吞吐量感兴趣,请查看窗口协议,例如 kermit 文件发送协议。

现在,如果您想坚持使用您的协议并减少延迟,选择、轮询、读取都会给您大致相同的延迟,因为正如 Andy Ross 所指出的,真正的延迟在于硬件 fifo 处理。

如果幸运的话,您可以在不打补丁的情况下调整驱动程序的行为,但您仍然需要查看驱动程序代码。但是,让 ARM 处理 10 kHz 的中断率肯定不会对整体系统性能有好处...

另一个选项是填充您的数据包,以便您每次都达到 fifo 阈值。它还将确认是否是fifo阈值问题。

10 msec @ 115200 足以传输 100 个字节(假设 8N1),所以您看到的可能是因为未设置 low_latency 标志。试试

setserial /dev/<tty_name> low_latency

它将设置low_latency标志,当在tty层向上移动数据时,内核会使用该标志:

void tty_flip_buffer_push(struct tty_struct *tty)

         unsigned long flags;
         spin_lock_irqsave(&tty->buf.lock, flags);
         if (tty->buf.tail != NULL)
                 tty->buf.tail->commit = tty->buf.tail->used;
         spin_unlock_irqrestore(&tty->buf.lock, flags);

         if (tty->low_latency)
                 flush_to_ldisc(&tty->buf.work);
         else
                 schedule_work(&tty->buf.work);

schedule_work 调用可能是您观察到的 10 毫秒延迟的原因。

【讨论】:

我使用请求/应答方案的原因是为了控制对总线的访问,因为可以有许多设备连接到它。填充数据包对延迟没有影响 @JustMaximumPower : 尝试为您的平台编译 setserial,并执行 setserial /dev/ttySx low_latency 阅读 low_latency 的文档似乎正是我所需要的。但是它对我的程序没有影响。我有一个理论,为什么我需要测试。我会回复你的。【参考方案2】:

在与更多工程师讨论过这个话题后,我得出的结论是,这个问题在用户空间中是无法解决的。由于我们需要跨过桥进入内核领域,因此我们计划实现一个内核模块,该模块与我们的协议对话并为我们提供

--- 编辑---

事实证明我完全错了。所需要的只是增加内核滴答率。默认的 100 个滴答声增加了 10 毫秒的延迟。 1000Hz 和一个负值的串行过程给了我想要达到的时间行为。

【讨论】:

任何要遵循的链接或文档。我还在寻找通过基于 USB 的虚拟串行端口进行比 10 毫秒更快的串行数据通信。从微控制器接收数据,需要在 100 uS 左右尽快回复。我可以试试 1ms。 @Rick2047 这是关于内核滴答率elinux.org/Kernel_Timer_Systems 的一些文档。请记住,这个问题/答案来自 2012 年。从那时起发生了很多变化。我不确定这仍然适用于无滴答内核。【参考方案3】:

Linux 上的串行端口被“包装”到 unix 风格的终端结构中,这会给您带来 1 个滴答延迟,即 10 毫秒。如果stty -F /dev/ttySx raw low_latency 有帮助,请尝试,但不能保证。

在 PC 上,您可以直接与标准串口通信,发出setserial /dev/ttySx uart none 将 linux 驱动程序与串口硬件解除绑定,并通过inb/outb 控制端口到端口寄存器。我试过了,效果很好。

缺点是当数据到达时你不会得到中断,你必须轮询寄存器。经常。

你应该能够在 arm 设备端做同样的事情,在异国串行端口硬件上可能会更难。

【讨论】:

【参考方案4】:

下面是setserial 在端口的文件描述符上设置低延迟的方法:

ioctl(fd, TIOCGSERIAL, &serial);
serial.flags |= ASYNC_LOW_LATENCY;
ioctl(fd, TIOCSSERIAL, &serial);

【讨论】:

我正在代码中执行此操作,它会更改延迟计时器(如 cat 所示),但实际上不会影响延迟。我需要关闭和打开端口,还是设置标志后做任何事情? 您不需要做任何其他事情。 看来您需要以 root 身份运行代码才能正常工作。 @Patrick 这就是不检查返回码的结果:) serial_struct&lt;linux/serial.h&gt;,顺便说一句。【参考方案5】:

这些系统调用都不会影响延迟。如果您想从用户空间尽可能快地读取和写入一个字节,那么您真的不会比简单的read()/write() 对做得更好。尝试用来自另一个用户空间进程的套接字替换串行流,看看延迟是否有所改善。如果他们不这样做,那么您的问题是 CPU 速度和硬件限制。

您确定您的硬件完全可以做到这一点吗?发现具有缓冲区设计的 UART 会引入许多字节的延迟,这种情况并不少见。

【讨论】:

好吧,它开始看起来像你的权利。板子采用16550A芯片,有接收FIFO,接收14个字节后触发中断。现在的问题变成了如何更改 FIFO 大小或解决它? 这只是PC串口吗?如果是这样,您可能会在 linux 驱动程序(看起来在驱动程序/tty/serial/8250 中)进行挖掘,以查看是否有 ioctl 提供对 FIFO 深度的编程控制。【参考方案6】:

简而言之:使用 USB 适配器和 ASYNC_LOW_LATENCY。

我在 Modbus 上使用了基于 FT232RL 的 USB 适配器,速度为 115.2 kbs。

我使用 ASYNC_LOW_LATENCY 在大约 20 毫秒的时间内完成了大约 5 个事务(对 4 个设备)。这包括到慢速设备的两个事务(4 毫秒响应时间)。

没有ASYNC_LOW_LATENCY,总时间约为60毫秒。

使用 FTDI USB 适配器ASYNC_LOW_LATENCY 将芯片本身的字符间计时器设置为 1 毫秒(而不是默认的 16 毫秒)。

我目前正在使用自制的 USB 适配器,我可以将适配器本身的延迟设置为我想要的任何值。将其设置为 200 µS 会在 20 mS 的基础上再减少一个 mS。

【讨论】:

【参考方案7】:

在这些线速度下,无论您如何检查准备情况,您都不应该看到那么大的延迟。

您需要确保串行端口处于原始模式(因此您执行“非规范读取”)并且 VMIN 和 VTIME 设置正确。您想确保 VTIME 为零,这样字符间计时器就不会启动。我可能会先将 VMIN 设置为 1,然后从那里开始调整。

与在线时间相比,系统调用开销微不足道,因此 select() 与 poll() 等不太可能产生差异。

【讨论】:

以上是关于Linux 上的低延迟串行通信的主要内容,如果未能解决你的问题,请参考以下文章

Linux下的虚假串行通信

与 iOS 外部附件的串行通信(无身份验证协处理器)

串行通信 C++

Raspberry Pi 上的 Tornado 使用 websockets 以及监控串行端口 Arduino 通信

如何在 Linux 上通过 C++ 中的串行接口与 Arduino 通信?

以编程方式与 OS X 或 Linux 中的串行端口通信