一尘不染

Linux阻塞与非阻塞串行读取

linux

我有这段代码可以在Linux中从Serial读取,但是我不知道在读取SerialPort时阻塞和非阻塞之间有什么区别,在哪种情况下哪个更好?


阅读 537

收藏
2020-06-02

共1个答案

一尘不染

您提到的代码是IMO编码和注释不当的代码。该代码不符合POSIX的可移植性惯例,如正确设置终端模式POSIX操作系统的串行编程指南中所述。该代码没有提到它使用非规范(也称为原始)模式,并且重用了“阻塞”和“非阻塞”术语来描述
VMINVTIME 属性。

(该代码的作者报告说,它早于POSIX标准,因此不合规。这是可以理解的,但此后应发布并提倡使用可能无法移植的旧代码(即,在替代情况下按预期运行)
)是有问题的。)

“阻塞”与“非阻塞”读取的常规定义基于“何时”读取调用将返回到您的程序(并使用下一条语句恢复执行),以及程序的读取缓冲区中是否将存储数据。阻塞读取是默认模式,除非使用O_NONBLOCK或O_NDELAY选项打开串行端口来请求非阻塞。

规范模式
对于阻塞的串行规范调用,将始终在提供的缓冲区中返回一行文本(即记录)(除非发生错误)。只要需要接收和处理行终止符,read调用就会阻塞(即,暂停程序执行)。

串行端口的无阻塞规范读取调用将始终“立即”返回。读取可能会或可能不会返回任何数据。
如果(自从上一次读取调用以来)至少已接收到一行文本并将其存储在系统缓冲区中,则最早的行将从系统缓冲区中删除并复制到程序的缓冲区中。返回码将指示数据长度。
如果(自上次读取调用以来)未接收到行终止符并对其进行了处理,则没有(完整)行文本可用。在 read()方法
会返回一个错误EAGAIN(即-1返回代码和 错误号 设置为EAGAIN)。然后,您的程序可以执行一些计算,或者从其他设备请求I /
O,或者延迟/睡眠。在任意延迟之后,或者通过 poll()select() 进行通知,您的程序可以重试 read()

非规范模式
将串行端口配置为非规范模式时,应使用 termios c_cc 数组元素 VMINVTIME
来控制“阻塞”,但这要求在默认阻塞模式下打开端口,即不要指定O_NONBLOCK打开选项。否则,O_NONBLOCK将优先于VMIN和VTIME规范,并且
read() 会将 errno设置 为EAGAIN并在没有可用数据时立即返回-1而不是0。(这是在最近的Linux
3.x内核中观察到的行为;较旧的2.6.x内核的行为可能有所不同。)

termios手册页将( c_cc 数组索引) VMIN 描述为 “非 规范 读取的最小字符数” ,将( c_cc
数组索引) VTIME 描述为 “非 规范 读取的超时(以分秒为单位)” 。您的程序应调整
VMIN ,以适应预期的典型消息或数据报长度和/或每个 read() 检索和处理的数据的最小大小。您的程序应调整
VTIME 以适应预期的典型串行数据突发性或到达速率和/或等待数据或数据的最大时间。

VMINVTIME 值进行交互,以确定何时读应该返回的标准; 它们的确切含义取决于其中哪一个非零。有四种可能的情况。
该网页将其解释为:

  • VMIN = 0和VTIME = 0

这是一个完全非阻塞的读取-
调用直接从驱动程序的输入队列中立即得到满足。如果有可用数据,则最多将数据传输到调用者的缓冲区,并返回。否则,立即返回零以指示“无数据”。我们将注意到这是串行端口的“轮询”,这几乎总是一个坏主意。如果重复进行,则会消耗大量的处理器时间,效率极低。除非您真的非常了解自己在做什么,否则不要使用此模式。

  • VMIN = 0且VTIME> 0

这是纯定时读取。如果输入队列中有可用数据,则将数据最多传输到调用者的缓冲区(最多nbytes),并立即返回给调用者。否则,驱动程序将阻塞,直到数据到达为止,或者从调用开始起VTIME十分之一过期。如果计时器在没有数据的情况下到期,则返回零。一个字节足以满足此读取调用的要求,但是如果输入队列中有更多可用字节,则会将其返回给调用方。请注意,这是一个整体计时器,而不是字符间计时器。

  • VMIN> 0和VTIME> 0

当VMIN字符已转移到调用者的缓冲区中,或者当两个字符之间的VTIME十分之一到期时,将满足read()的要求。由于此计时器直到第一个字符到达时才启动,因此如果串行线路空闲,则此调用可以无限期地阻塞。这是最常见的操作模式,我们认为VTIME是字符间超时,而不是整体超时。此调用永远不要返回读取的零字节。

(根据我的经验,该VMIN>0 and VTIME>0模式并不能像所宣传的那样正常工作。计时器似乎间隔很短,不到1/10秒。我还没有看到它在2.6的ARM和Linux
3.13上可以运行x86。在快速波特率(115200)下,当VMIN = 1和VTIME =
1时,read()有时返回10个或更多字节,但更常见的是,无论VTIME值如何,它只是部分读取几个字节。是否需要破译?在现代快速波特率下,至少0.1秒的消息间隔太长了(而且不切实际)。

  • VMIN> 0且VTIME = 0

仅当至少VMIN字符已传输到调用者的缓冲区时才满足此计数读取的要求-
不涉及时序组件。可以从驱动程序的输入队列(调用可以立即返回)中满足读取要求,也可以通过等待新数据到达来满足此要求:在此方面,调用可以无限期地阻塞。我们认为,如果nbytes小于VMIN,则这是未定义的行为。

您提到的代码将“非阻塞”模式配置为VMIN =0和VTIME=5。这不会导致read()像非阻塞规范读取那样立即返回;使用该代码,read()应该始终等待至少半秒钟再返回。“无阻塞”的常规定义是,在syscall期间,您的调用程序不会被抢占,而是立即(几乎)获得控制权。要获得(无条件和)立即返回(用于非规范读取),请设置VMIN
= 0和VTIME = 0。

2020-06-02