从串行读取所需数据量的最佳实践?

Best Practise to Read Desired Amount of Data from Serial?

由于read 只会从串行读取所有可用数据,this answer 建议使用while 循环等待读取所需长度的数据。但是 AFAIK 系统调用很昂贵,因此,这种方法在某种程度上是不是很粗糙,尤其是当所需的长度很大时?我知道这是否会导致性能问题取决于实际应用,我不应该做过早的优化。我只是好奇在这种情况下是否有更好的做法来避免密集的系统调用?

为避免密集的系统调用,您可以使用 sleep() 系统函数。

从串口读取数据通常是线程业务。

线程休眠100ms,然后从串口读取数据入队FIFO队列

按照今天的标准,串行连接(源自 RS-232 标准)是非常慢的连接。它们通常不支持高于 1 Mbps 的数据速率——相比之下,USB 4 的数据速率高达 20 Gbps。因此,在使用串行连接时,系统主要处于等待状态。关键是要避免忙等待,即如果没有数据到达,系统不应该在串行连接上花费任何时间。

链接代码失败了。因为它在打开端口时设置 O_NDELAY 标志,所以 read() 不会阻塞,如果没有可用数据则立即 return 0。这可能会吸收 100% 的单个 CPU。是否涉及系统调用无关紧要。循环尽可能快且频繁地运行,直到新数据到达。它缺少一种等待而不浪费 CPU 时间的机制。

最简单的解决方案是不设置 O_NDELAY 标志。然后 read() 如果没有可用数据将阻塞。而且它这样做根本没有花费任何 CPU 时间。 Linux 将在新数据到达时唤醒线程或进程。

如果阻塞不是一个选项,还有许多其他选项,但它们取决于您的其余代码,我们对此一无所知。

此外,我建议要非常小心系统调用很昂贵这样的说法。在这种形式下,陈述是错误的。当然,任何事情都有 CPU 时间、内存 space、有效时间等方面的成本。但是没有任何数字或没有相对于替代操作的数字,弊大于利。

But AFAIK system calls are expensive, ...

没错,系统调用比本地 procedure/function 调用消耗更多 CPU 周期。系统调用需要 CPU 用户模式到(受保护的)内核模式之间的模式转换,然后返回用户模式。

... thus, isn't that approach somehow crude, especially when the desired length is large?

从串行终端(例如/dev/ttyXn 设备读取数据时,您必须问自己的第一个问题(而不是串行端口)是“要接收什么样的数据,即是(ASCII)的数据由某种类型的 EOL(行尾)字符终止的文本,或者是否需要将数据简单地视为 binary(或原始)数据?

应使用规范(也称为熟化)模式从串行终端读取文本行。 OS 将为您的程序执行接收到的数据的词法扫描,并根据您指定的 EOL 字符分隔每个 文本。 read() 然后可以 return 假设使用阻塞 I/O 的一行,并且文本行不长于提供的缓冲区。

应使用非规范(也称为原始)模式从串行终端读取二进制数据。 OS 将忽略数据的值,并且(当使用阻塞 I/O 时)每个 read() 将 return 一个数据量基于时间和字节数的限制。

参见 this answer for more details

请注意,post 您的问题实际上是关于阅读文本的,但 OP 正在(错误地)使用非规范模式。如果 OP 使用正确的模式来匹配输入,那么他可能永远不会遇到部分读取问题。


I am just curious that whether there is a better practise to avoid intensive system calls in this scenario?

正确的 termios 配置对于串行终端的高效 I/O 至关重要。

阻塞 I/O 模式应被视为首选模式。
当进程更频繁地放弃控制时,OS 可以更好地执行多任务调度。
OS 可以更有效地确定用户何时可以使用 return 数据。
另请注意,termios 配置在使用阻塞模式时最有效,例如非规范模式下的 VMIN 和 VTIME 规范。

例如使用 select()poll() 然后 read( ) 比仅与(阻塞)read() 相比多了一个系统调用。但是您可以找到许多这样的代码示例,因为似乎存在一些流行的误解,认为程序可以通过这种方式更快地从“UART”获取数据。
但是非阻塞和异步模式不一定更快(在多任务处理中 OS),并且 read() 只是从 termios 缓冲区中获取数据,该缓冲区从中删除了几层实际硬件。

如果您的程序使用非阻塞模式但在等待数据时不执行有用的工作,而是使用 select() poll()(或者更糟的是调用 sleep()),那么您的程序就会不必要地复杂和低效。参见 this answer
阻塞模式 read() 可以完成所有等待您的程序的工作,使您的程序更简单、更易于编写和维护,并提高运行时效率。

但是,为了阻止非规范读取,您将不得不接受某种程度的低效率。您能做的最好的事情就是权衡延迟与系统调用的数量。一个可能的例子是 ,它试图在每个系统调用中获取尽可能多的数据,但允许对接收到的二进制数据进行简单的逐字节词法扫描。


请注意,读取串行终端时延迟的一个可能来源是内核配置不当,而不是 termios API 和 read() 开销。
例如,通过 ioctl() 设置 ASYNC_LOW_LATENCY 标志(例如参见 [​​=16=])是改进 read()[=70 的一种方法=] 延迟。