Linux阻塞与非阻塞串口读取

35

我有这段代码用于在Linux中读取串口数据,但是我不知道阻塞和非阻塞读取串口的区别,以及在哪种情况下哪种方法更好?


6
这完全取决于您的应用程序架构。阻塞更简单,但具有阻塞性。非阻塞需要稍微编写更多代码,但可以在同一时间内执行其他任务。 - Геннадий Казачёк
1个回答

85
你提到的代码在我看来编写和注释都很差。该代码不符合 POSIX 的可移植性实践,如 Setting Terminal Modes ProperlySerial Programming Guide for POSIX Operating Systems 所述。该代码没有提到它使用非规范模式(也称为原始模式),并且重用“阻塞”和“非阻塞”术语来描述 VMINVTIME 属性。
(该代码的作者报告说它早于 POSIX 标准,因此不符合标准是可以理解的,但是发表和倡导使用可能不可移植的旧代码(即在其他情况下无法正常工作)就值得商榷了。)
The conventional definition of a "blocking" versus "nonblocking" read is based on "when" the read call will return to your program (and resume execute with the next statement) and whether there will be data stored in your program's read buffer. A blocking read is the default mode, unless non-blocking is requested by opening the serial terminal with the O_NONBLOCK or O_NDELAY flag.
Canonical mode 对于串行终端的阻塞canonical read调用,一行(也称为记录)文本将始终在提供的缓冲区中返回(除非发生错误)。读取调用将会阻塞(即暂停程序的执行),直到接收和处理行终止字符。
串口的非阻塞规范读取调用将始终“立即”返回。读取可能会返回数据,也可能不会。
如果(自上次读取调用以来)至少接收并存储了一行文本,则最旧的行将从系统缓冲区中删除并复制到程序缓冲区中。返回码将指示数据长度。
如果(自上次读取调用以来)未收到并处理行终止字符,则没有(完整的)可用文本行。read()将返回EAGAIN错误(即-1返回代码和errno设置为EAGAIN)。然后,您的程序可以执行某些计算、请求来自另一个设备的I/O或延迟/睡眠。在任意延迟之后或通过poll()或select()的通知之后,您的程序可以重试read()。
使用阻塞规范模式进行读取的示例程序包含在这个答案中。

非规范模式
当串行终端被配置为非规范模式时,termiosc_cc数组元素VMINVTIME应该被用来控制“阻塞”,但这需要在默认的阻塞模式下打开终端,也就是不指定O_NONBLOCK打开标志。
否则,O_NONBLOCK将优先于VMIN和VTIME的规定,并且read()将会在没有数据可用的情况下设置errno为EAGAIN并立即返回-1而不是0。(这是在最近的Linux 3.x内核中观察到的行为;旧的2.6.x内核可能表现不同。)

The termios man page将(c_cc数组索引)VMIN描述为"非规范读取的最小字符数",并将(c_cc数组索引)VTIME描述为"非规范读取的超时时间(以十分之一秒为单位)"
VMIN应由您的程序进行调整,以适应预期的典型消息或数据报长度和/或每个read()需要检索和处理的最小数据大小。
VTIME应由您的程序进行调整,以适应预期的串行数据的典型突发性或到达率和/或等待数据或数据项的最长时间。

VMINVTIME值相互作用,以确定何时read应返回;它们的精确含义取决于它们中哪些是非零的。有四种可能的情况。
此网页将其解释为:

  • VMIN = 0且VTIME = 0
这是一个完全非阻塞的读取 - 调用直接从驱动程序的输入队列中满足。如果数据可用,它会传输到调用者的缓冲区,最多 nbytes,并返回。否则,立即返回零以指示“无数据”。我们需要注意的是,这是串口的“轮询”,几乎总是不明智的。如果重复执行,它会消耗大量处理器时间并且效率极低。除非您确实知道自己在做什么,否则不要使用此模式。
VMIN = 0 和 VTIME > 0
这是一个纯定时读取。如果输入队列中有数据可用,则将其传输到调用者的缓冲区,最多 nbytes,并立即返回给调用者。否则,驱动程序会阻塞,直到数据到达或者从调用开始计时起 VTIME 十分之一秒已经过去。如果计时器在没有数据的情况下过期,则返回零。单个字节足以满足此读取调用,但如果输入队列中有更多数据,则将其返回给调用者。请注意,这是总体计时器,而不是字符间计时器。
VMIN > 0 和 VTIME > 0
当调用read()时,如果VMIN个字符已经传输到调用者的缓冲区,或者在字符之间VTIME秒已经过去,则该调用被满足。由于此计时器直到第一个字符到达才启动,因此如果串行线路处于空闲状态,此调用可能会无限期地阻塞。这是最常见的操作模式,我们认为VTIME是一个字符间超时而不是总体超时。此调用不应返回零字节读取。
当VMIN > 0且VTIME = 0时,这是一次计数读取,只有当至少传输了VMIN个字符到调用者的缓冲区时才会被满足-没有涉及定时组件。此读取可以从驱动程序的输入队列中满足(在这种情况下,调用可以立即返回),也可以通过等待新数据到达来满足:在这方面,调用可能会无限期地阻塞。如果nbytes小于VMIN,则我们认为这是未定义的行为。
请注意,当VMIN=1时,VTIME规范将无关紧要。任何数据的可用性都将满足单个字节的最小条件,因此可以忽略时间条件(因为它将是具有非零VMIN的字符间时间规范)。@IanAbbot指出了这种特殊情况。
您提到的代码将“非阻塞”模式配置为VMIN=0和VTIME=5。这不会像非阻塞规范读取那样立即导致read()返回; 使用该代码,read()应该始终等待至少半秒钟才能返回。
传统的“非阻塞”的定义是,在系统调用期间,您的调用程序不会被抢占并立即获得控制权(几乎)。
要获取(无条件和)立即返回(用于非规范读取),请设置VMIN = 0和VTIME = 0(附带警告)。

4
解释得很好,但是哇。与Windows所做的相比,这实际上是一个相当混乱的合同。 - Ben Voigt
15
那段代码比 POSIX 早好几年,所以它与之不匹配并不奇怪。 - wallyk
3
“@CMCDragonkai -- The long paragraph that begins with "A nonblocking canonical read..." answers your question.” 的意思是:“@CMCDragonkai,以 '非阻塞规范读取...' 开头的长段回答了你的问题。” - sawdust
5
那段代码基于1985年左右的Unix代码。我提供的代码最后一次实质性更新是在1992年,自那时以来进行了一些微调以支持Solaris、Linux和某些Unix的派生版本。 - wallyk
1
非阻塞规范模式真的存在吗?回答只指向规范阻塞示例... - 71GA
显示剩余7条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接