Linux 阻塞与非阻塞串行读取

Posted

技术标签:

【中文标题】Linux 阻塞与非阻塞串行读取【英文标题】:Linux Blocking vs. non Blocking Serial Read 【发布时间】:2014-11-17 17:21:20 【问题描述】:

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

【问题讨论】:

这完全取决于您应用程序的架构。阻塞更简单,但是阻塞。非阻塞需要更多的编码,但可以让您同时执行另一项任务。 【参考方案1】:

您提到的代码在 IMO 编码和注释方面都很糟糕。该代码不符合Setting Terminal Modes Properly 和Serial Programming Guide for POSIX Operating Systems 中所述的可移植性POSIX 实践。该代码没有提到它使用非规范(又名原始)模式,并重用“阻塞”和“非阻塞”术语来描述 VMINVTIME 属性。

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


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

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

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

this answer 中包含一个使用阻塞规范模式进行读取的示例程序。

非规范模式 当串行终端配置为非规范模式时,termios c_cc 数组元素 VMIN VTIME应该用来控制“阻塞”,但这需要终端以默认阻塞模式打开,即不要指定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 值相互作用以确定何时读取应返回的标准;它们的确切含义取决于它们中的哪些是非零的。有四种可能的情况。This web page 解释为:

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(附带警告)。

【讨论】:

很好的解释,但是哇。老实说,与what Windows does 相比,这是一个相当混乱的合同 该代码比 POSIX 早了几年,所以它不匹配也就不足为奇了。 @CMCDragonkai -- 以"A nonblocking canonical read..."开头的长段落回答了你的问题。 @wallyk “该代码比 POSIX 早了几年” -- 该代码有多少年了? Serial Programming Guide for POSIX Operating Systems 于 1994 年首次获得版权。 @sawdust:该代码基于 1985 年左右的 Unix 代码。我提供的代码最后一次实质性更新是在 1992 年左右,进行了各种调整,以支持 Solaris、Linux 和 Unix 的一些衍生产品。

以上是关于Linux 阻塞与非阻塞串行读取的主要内容,如果未能解决你的问题,请参考以下文章

linux函数的阻塞与非阻塞IO及错误处理

Linux设备驱动基础03之阻塞与非阻塞IO

Linux设备驱动基础03之阻塞与非阻塞IO

Linux设备驱动基础03之阻塞与非阻塞IO

Linux设备驱动基础03之阻塞与非阻塞IO

Java 同步与异步-阻塞与非阻塞理解