Linux上的低延迟串行通信

Low latency serial communication on Linux

本文关键字:通信 延迟 Linux      更新时间:2023-10-16

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

哪些IO接口会给我最低的延迟:选择、轮询、epoll或使用ioctl手动轮询?阻塞或非阻塞IO是否会影响延迟?

我试着用setserial设置low_latency标志。但它似乎没有任何效果。

我还有其他可以减少延迟的方法吗?由于我控制着所有的设备,甚至可以修补内核,但最好不要

----编辑----

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

请求/应答方案往往效率低下,而且它在串行端口上显示得很快。如果你对throughtput感兴趣,可以看看窗口协议,比如kermit文件发送协议。

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

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

另一种选择是填充数据包,以便每次都达到FIFO阈值。它还将确认是否存在FIFO阈值问题。

10毫秒@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毫秒延迟的原因。

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

---编辑---

原来我完全错了。所需要的只是提高内核的点击率。默认的100个刻度增加了10ms的延迟。1000Hz,串行进程的负的值给了我想要达到的时间行为。

linux上的串行端口被"包装"到unix风格的终端结构中,这会给你带来1个节拍滞后,即10ms。如果stty -F /dev/ttySx raw low_latency有帮助,请尝试,但不能保证。

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

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

您应该能够在arm设备端做同样的事情,在异国情调的串行端口hw上可能会困难得多。

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

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

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

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

我使用ASYNC_LOW_LATENCY总共在大约20mS内获得大约5个事务(到4个设备)。这包括到慢戳设备的两个事务(4mS响应时间)。

在没有ASYNC_LOW_LATENCY的情况下,总时间约为60ms。

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

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

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

你确定你的硬件能做到这一点吗?UART的缓冲区设计引入了相当于许多字节的延迟,这并不罕见。

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

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

与连线上的时间相比,系统调用开销微不足道,因此select()与poll()等不太可能有什么不同。