【发布时间】:2021-03-21 14:04:01
【问题描述】:
由于read 只会从串行读取所有可用数据,this answer 建议使用while 循环等待直到读取所需长度的数据。但是 AFAIK 系统调用很昂贵,因此,这种方法是不是有点粗糙,尤其是当所需的长度很大时?我知道这是否会导致性能问题取决于实际应用程序,我不应该过早优化。我只是好奇在这种情况下是否有更好的做法来避免密集的系统调用?
【问题讨论】:
标签: c linux serial-port
由于read 只会从串行读取所有可用数据,this answer 建议使用while 循环等待直到读取所需长度的数据。但是 AFAIK 系统调用很昂贵,因此,这种方法是不是有点粗糙,尤其是当所需的长度很大时?我知道这是否会导致性能问题取决于实际应用程序,我不应该过早优化。我只是好奇在这种情况下是否有更好的做法来避免密集的系统调用?
【问题讨论】:
标签: c linux serial-port
但是 AFAIK 系统调用很昂贵,...
的确,系统调用比本地过程/函数调用消耗更多的 CPU 周期。系统调用需要在用户模式和(受保护的)内核模式之间进行 CPU 模式转换,然后再返回用户模式。
...因此,这种方法是不是有点粗糙,尤其是当所需的长度很大时?
从串行终端(例如/dev/ttyXn设备)(而不是串行端口)读取时,您必须问自己的第一个问题) 是“将要接收什么样的数据,即(ASCII)文本的数据lines 是否以某种类型的 EOL(行尾)字符终止,或者是否需要将数据简单地视为二进制(或原始)数据?"
应使用规范(又名熟)模式从串行终端读取文本行。操作系统将对您的程序接收到的数据执行词法扫描,并根据您指定的 EOL 字符分隔每个 line 文本。 然后,read() 可以返回一行,假设使用了阻塞 I/O,并且该行文本不长于所提供的缓冲区。
应使用非规范(又名原始)模式从串行终端读取二进制数据。操作系统将忽略数据的值,并且(当使用阻塞 I/O 时)每个 read() 将根据时间和字节数的限制返回一定量的数据。
见this answer for more details。
请注意,您的问题实际上是关于阅读文本的帖子,但 OP 是(错误)使用非规范模式。如果 OP 使用了正确的模式来匹配输入,那么他可能永远不会遇到部分读取问题。
我只是好奇在这种情况下是否有更好的做法来避免密集的系统调用?
正确的 termios 配置对于使用串行终端的高效 I/O 至关重要。
阻塞 I/O 模式应被视为首选模式。
当进程更频繁地放弃控制时,操作系统可以更好地执行多任务调度。
操作系统可以更有效地确定何时可以将数据返回给用户。
另请注意,在使用阻塞模式时,termios 配置最有效,例如非规范模式下的 VMIN 和 VTIME 规范。
例如,使用 select() 或 poll() 然后使用 read() 比使用只是(阻塞)read()。但是您可以找到许多这样的代码示例,因为似乎存在一些流行的误解,即程序可以通过这种方式更快地从“UART”获取数据。
但非阻塞和异步模式不一定更快(在多任务操作系统中),read() 只是从与实际硬件相隔数层的 termios 缓冲区中获取数据。
如果您的程序使用非阻塞模式但在等待数据时没有执行有用的工作,而是使用 select() 或 poll()(甚至更糟调用 sleep()),那么你的程序就会变得不必要地复杂和低效。见this answer。
阻塞模式 read() 可以完成所有等待您的程序的工作,使您的程序更简单、更易于编写和维护,并且运行时效率更高。
但是,对于阻止非规范读取,您将不得不接受某种程度的低效率。您可以做的最好的事情是权衡延迟与系统调用的数量。一个可能的例子是this answer,它试图在每个系统调用中获取尽可能多的数据,但允许对接收到的二进制数据进行简单的逐字节词法扫描。
请注意,读取串行终端时延迟的可能来源是配置不当的内核,而不是 termios API 和 read() 开销。
例如,通过 ioctl() 设置 ASYNC_LOW_LATENCY 标志(例如,参见 High delay in RS232 communication on a PXA270)是改善 read() 延迟的一种方法。
【讨论】:
为避免密集的系统调用调用,您可以使用 sleep() 系统函数。
从串口读取数据通常是线程业务。
线程休眠 100 毫秒,然后从串口读取数据,并将它们放入 FIFO 队列中。
【讨论】:
sleep() 不是必需的,因为 read() 可以实现相同的目的。 Linux 文件描述符(无论是文件、管道还是串行连接)已经提供了 FIFO。那为什么还要再复制一个 FIFO 呢?
按照当今的标准,串行连接(源自 RS-232 标准)是非常慢的连接。它们通常不支持高于 1 Mbps 的数据速率,而 USB 4 的数据速率高达 20 Gbps。因此,在使用串行连接时,系统主要处于等待状态。关键是要避免忙等待,即如果没有数据到达,系统不应该在串行连接上花费任何时间。
链接代码在此失败。由于它在打开端口时设置了O_NDELAY 标志,read() 不会阻塞,而是在没有可用数据时立即返回 0。这可能会吸收 100% 的单个 CPU。是否涉及系统调用无关紧要。循环尽可能快且频繁地运行,直到新数据到达。它缺少一种不浪费 CPU 时间的等待机制。
最简单的解决方案是不设置O_NDELAY 标志。如果没有可用数据,read() 将阻塞。而且它完全不需要花费任何 CPU 时间。一旦有新数据到达,Linux 会唤醒线程或进程。
如果阻止不是一个选项,还有许多其他选项,但它们取决于您的其余代码,我们对此一无所知。
此外,我建议对像 系统调用很昂贵这样的语句要非常小心。在这种形式下,陈述是错误的。当然,任何事情在 CPU 时间、内存空间、有效时间等方面都是有代价的。但是没有任何数字或相对于替代操作而言,它弊大于利。
【讨论】: