如何修补内核以减少 uart termios API 的 VTIME 间隙?

如何修补内核以减少 uart termios API 的 VTIME 间隙?

我需要帮助找到一个内核补丁,以从 tty termios API 获取低于 100 毫秒的 VTIME,以减少字符间的间隙。它会阻止读取系统调用,直到 VTIME 超时。

函数 n_tty_read() 是补丁入口点: https://elixir.bootlin.com/linux/latest/source/drivers/tty/n_tty.c#L2131

需要修补的源代码

有人给我建议吗?我必须使用非规范模式(没有帧协议,没有ascii,没有中断)。 VMIN 参数为空,因为所有消息长度都是变量。对于多个 uart 系统来说,轮询每个字符的解决方案成本太高,并且隐含多个进程上下文切换。

提取termios手册:VTIME Timeout in十分之一秒用于非规范读取(时间)。https://man7.org/linux/man-pages/man3/termios.3.html

Linux内核补丁是必要的... HZ除以更大的值是否更好?或者将全局 HZ 设置为较小的值?

Microsoft Windows Serial API 能够配置到 1ms,但 Linux 则不行。所以Windows更适合原始串口处理。 读取间隔超时:https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/ntddser/ns-ntddser-_serial_timeouts

在非规范模式下,读取缓冲区仅接受 4095 个字符

评论摘录“在非规范模式下......只接受 4095 个字符”,我认为问题就在这里,也许就是read_wait队列没有被唤醒的原因。

https://elixir.bootlin.com/linux/latest/source/drivers/tty/n_tty.c#L1667

答案1

我需要帮助找到内核补丁以从 tty termios API 获取低于 100 毫秒的 VTIME,...

Linux 内核补丁是必要的...

这样的内核补丁是不明智的,因为它会改变用户空间 API。 Linux 努力维护一致的 API,而您要求的补丁可能会破坏现有的应用程序。

即使这样的内核补丁没有改变 API,它也不太可能可靠并实现您未声明的目标。您不能或不应仅依赖 VMIN 和 VTIME 设置来检测时间分隔的消息。

如果 UART 使用 FIFO 来缓冲传入数据,则该硬件缓冲区会掩盖软件可以检测到的字符间距。同样,当 UART 驱动程序使用 DMA 存储接收到的数据时,字符间时序将被掩盖。

termios 处理至少位于 UART 设备驱动程序之上的完整层。 termios 层根本看不到与 UART 及其驱动程序处理/缓冲字节后线路上相同的时序信息。


Microsoft Windows Serial API 能够配置到 1ms,但 Linux 则不行。所以Windows更适合原始串口处理。

如果您认为 Windows 更适合您的应用程序,那么您应该使用它。但请注意,广告上的内容并不一定是您会得到的。 Windows API 只是广告以毫秒为单位的超时规范精确。这并不意味着您实际上会获得这样的准确性。例如,有这样的警告:

超时间隔是相对于系统时钟测量的,并且超时测量的精度受到系统时钟粒度的限制。因此,超时可能发生在比指定超时间隔早一个系统时钟周期和晚一个周期之间,具体取决于该时间间隔的开始时间和结束时间在系统时钟周期之间的确切位置。超时可能会发生得更晚如果系统时钟中断处理被其他设备的中断处理延迟。如果指定的超时间隔接近或小于系统时钟滴答之间的周期,超时可能会立即发生,不会有任何延迟


我认为问题就在这里,也许这就是read_wait队列没有被唤醒的原因。

不,你坚持找错地方了。
正如我之前试图向您指出的那样(通过参考这个帖子),可靠的解决方案需要一个定时器来检测空闲的 UART 接收器。然而,即使 UART 硬件实现接收器超时(在我审查的两个 Linux 驱动程序中),该功能也仅用于中止当前/活动的 DMA 操作。

由于接收器超时(可能)检测到时间分隔消息的结束,因此当 UART 驱动程序传递接收到的数据时,需要将此事件/信息传送到 termios 层。这是 termios 需要的VMIN > 0原始模式新变体所缺少的信息VTIME > 0。在这个新变体中,UART 驱动程序需要用接收到的数据指示超时事件,而不是由 termios 检查字符间超时。
这不是一个简单的内核补丁。

答案2

希望我找到了一些解释,并得出结论,我们面临着一个API 过时这意味着可以通过 CPU 过度消耗来绕过。

VTIME 超时粒度为历史定义的作为字节间间隙的单位(POSIX 定义 VTIME=100ms)。当前Linux使用的字节间隙仍然对应于串行线110 波特率(110)。但如今,例如使用 115200 波特率的串行线路,一个字节的 TX 持续时间约为 100 µs(微秒)。

Alan Cox : RE: termios VMIN 和VTIME 行为

它的定义非常明确。它旨在优化块串行操作(uucp 等)

VMIN - 您期望此数据包的字节数 VTIME - 何时放弃等待

形式 0,VTIME 也用于对缺少 select/poll 的旧 SYSV 进行轮询

POSIX 2007 June §11.1.7 非规范模式输入处理

在非规范模式输入处理中,输入字节不会组装成行,并且不会发生擦除和终止处理。 c_cc 数组的 MIN 和 TIME 成员的值用于确定如何处理接收到的字节。 POSIX.1-200x 未指定 O_NONBLOCK 的设置是否优先于 MIN 或 TIME 设置。因此,如果设置了 O_NONBLOCK,则无论 MIN 或 TIME 设置如何,read() 都可能立即返回。此外,如果没有可用数据,read() 可能返回 0,或者返回 -1,并将 errno 设置为 [EAGAIN]。 MIN 表示 read() 函数成功返回时应接收的最小字节数。TIME是一个0.1秒粒度的定时器,用于突发和短期数据传输的超时。如果 MIN 大于 {MAX_INPUT},则对请求的响应未定义。

GNU C 库参考手册,版本 2.37,§17.4.10非规范输入

宏:int VTIME 这是c_cc 数组中TIME 槽的下标。因此,termios.c_cc[VTIME] 就是值本身。

TIME 槽仅在非规范输入模式下才有意义;指定返回之前等待输入的时间,单位为0.1秒。[...]

MIN 为零,但 TIME 具有非零值:在这种情况下,读取等待时间 TIME 以使输入可用;单个字节的可用性足以满足读取请求并导致读取返回。当它返回时,它会返回尽可能多的可用字符,最多可达请求的数量。如果在定时器到期之前没有可用的输入,则读取返回零值。

2005 SCO 集团公司§非规范模式输入处理

  1. (最小值>0,时间>0)。

因为TIME>0,所以作为字节间定时器在收到第一个数据字节时激活,并在收到每个字节数据时复位。 MIN 和 TIME 相互作用如下:

  • 一旦接收到一个字节的数据,字节间定时器启动(请记住,定时器在收到每个字节后重置)。
  • 如果在之前接收到 MIN 字节的数据字节间定时器到期,读取成功完成。
  • 如果字节间定时器到期在接收到 MIN 字节的数据之前,读取会传输在此之前接收到的所有字节。

当 TIME 到期时,读取会传输至少一个字节的数据,因为字节间定时器已启用当且仅当接收到一个字节的数据时。使用这种情况的程序必须等待至少一个字节的数据被读取才能继续。如果 (MIN>0, TIME>0),读取会阻塞,直到接收到一个字节的数据激活 MIN 和 TIME,或者信号中断读取。因此,读取传输至少一个字节的数据。

[...]

  1. (最小值=0,时间>0)。

因为MIN=0,TIME 不再充当字节间定时器,但现在用作处理读取时激活的读取计时器(在佳能中)。一旦接收到任何数据字节或读取计时器到期,读取就会成功完成。如果读取计时器到期,则读取不会传输任何数据字节。如果读取计时器未到期,当且仅当接收到一些数据字节时,读取才会成功完成。如果(MIN=0,TIME>0),读取不会无限期地阻塞等待一个数据字节。如果读取开始后TIME*0.10秒内没有收到字节数据,则返回0表示没有读取到数据。如果在读取开始时缓冲区保存数据,则读取计时器启动,就好像它立即接收到数据一样。当程序可以假设数据在 TIME 间隔后不可用并且可以在数据可用之前完成其他处理时,MIN 和 TIME 非常有用。

答案3

为了提出一个保持内核API兼容性的补丁,我认为主要可能有一个分流解决方案:就是使用CANON模式的VEOF参数值(未使用的unsigned char值)来调整VTIME(在应用schedule_timeout函数之前为超时值添加除法因子) ),适用于非CANON模式读取功能。

使用非佳能模式下未使用的 VEOF 值: https://elixir.bootlin.com/linux/latest/source/include/linux/tty.h#L39

此处应用除法:例如,如果 VEOF 值与默认值不同,则应用减少 VTIME 超时。 https://elixir.bootlin.com/linux/latest/source/drivers/tty/n_tty.c#L2196

相关内容