32

我有这段代码用于从 Linux 中的串行读取,但我不知道在读取串行端口时阻塞和非阻塞有什么区别,在哪种情况下哪个更好?

4

1 回答 1

78

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

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


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

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

串行终端的非阻塞规范读取调用将始终“立即”返回。读取可能会或可能不会返回任何数据。
如果(自上一次读取调用以来)至少有一行文本被接收并存储在系统缓冲区中,那么最早的行将从系统缓冲区中删除并复制到程序的缓冲区中。返回码将指示数据长度。
如果(自上一次读取调用以来)尚未接收和处理行终止字符,则没有(完整的)文本行可用。read()将返回 EAGAIN 错误(即 -1 返回码和errno设置为 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 array index) VMIN描述为“非规范读取的最小字符数”,将 ( c_cc array index) VTIME描述为“非规范读取的超时时间,以十进制为单位”
VMIN应由您的程序调整,以适应预期的典型消息或数据报长度和/或每次read()检索和处理的数据的最小大小。
VTIME应由您的程序调整,以适应预期的串行数据的典型突发性或到达率和/或等待数据或数据的最长时间。

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

  • VMIN = 0 和 VTIME = 0

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

  • VMIN = 0 且 VTIME > 0

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

  • VMIN > 0 和 VTIME > 0

当 VMIN 字符已传输到调用者的缓冲区或 VTIME 十分之一字符之间到期时,满足 read()。由于此计时器在第一个字符到达之前不会启动,因此如果串行线路空闲,此调用可能会无限期地阻塞。这是最常见的操作模式,我们认为 VTIME 是字符间超时,而不是整体超时。此调用不应返回读取的零字节。

  • VMIN > 0 且 VTIME = 0

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

请注意,当 VMIN=1 时,VTIME 规范将无关紧要。任何数据的可用性将始终满足单个字节的最小标准,因此可以忽略时间标准(因为它是具有非零 VMIN 的字符间时间规范)。@IanAbbot 指出了这种特殊情况。


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

于 2014-09-24T00:27:39.947 回答