众所周知,在Linux系统下全部设备都是以文件的形式存在,串口也同样。ios
一般I/O操做都是有阻塞与非阻塞的两种方式。编程
其中"超时"这个概念实际上是阻塞中的一种处理手段,本质仍是属于阻塞的I/O模式.ubuntu
在Linux中串口的IO操做 本文将它分为三种状态:ide
阻塞状态函数
超时状态spa
非阻塞状态code
这三种状态的转换组合有这么几种:进程
阻塞 --> 超时it
阻塞 --> 非阻塞io
超时 --> 阻塞
超时 --> 非阻塞
非阻塞 --> 阻塞
咱们一个一个来分析
首先在一个串口的描述符打开的时候指定它的模式是阻塞仍是阻塞
fd = open("/dev/tttyS0",O_RDWR | O_NOCTTY);//以阻塞模式打开串口 fd = open("/dev/tttyS0",O_RDWR | O_NOCTTY | O_NDELAY);//以非阻塞模式打开串口 有些地方使用的是 fd = open("/dev/tttyS0",O_RDWR | O_NOCTTY | O_NOBLOCK);
引用《UNIX环境高级编程》(第二版):
O_NOCTTY:O_NOCTTY 若是p a t h n a m e指的是终端设备,则不将此设备分配做为此进程的控制终端
O_NDELAY 与 O_NOBLOCK区别
较早的系统 V版本引入了 O _ N D E L AY(不延迟)标志,它与 O _ N O N B L O C K
(不阻塞)选择项相似,但在读操做的返回值中具备两义性。若是不能从管道、
F I F O或设备读得数据,则不延迟选择项使 r e a d返回0,这与表示已读到文件尾端的
返回值0相冲突。 S V R 4仍支持这种语义的不延迟选择项,可是新的应用程序应当
使用不阻塞选择项以代替之。
当一个串口是阻塞状态的时候即可以设置它为超时状态。
利用 struct termios 的 cc_t c_cc[NCCS] 成员
c_cc[VTIME] 非规范模式读取时的超时时间(单位:百毫秒)
c_cc[VMIN] 非规范模式读取时的最小字符数
如需须要设置超时则c_cc[VMIN] 必须等于0
这表明可以读取的最小字符是0个,即便用read读取数据超时read返回0
有一个须要注意的地方!
当c_cc[VTIME] 设置为 0 且 c_cc[VMIN] == 0 的时候,表明超时0秒(姑且这么叫吧!)
这个时候使用read读取数据会当即返回(有读到数据时返回字节数,没有数据和通常超时同样返回0)
可是!
虽然这时候在现象上看起来和非阻塞模式同样(read都不会阻塞)但返回值不一样
非阻塞模式: read没有读到数据当即返回-1
超时0秒时: read没有读到数据当即返回 0 (设置了超时的阻塞模式)
ret = read(fd,recvbuf,BUF_SIZE); if(ret == -1)//非阻塞模式时"无数据返回" { //do something } ret = read(fd,recvbuf,BUF_SIZE); if(ret == 0)//阻塞模式设置超时0秒时"超时返回" { //do something }
补充:在非阻塞模式下修改c_cc[VMIN] 和 c_cc[VTIME] 的状况
若在非阻塞模式下修改
c_cc[VMIN]为0而且c_cc[VTIME]也为0时read无数据会返回 0 (现象同"超时0秒"同样)
这时假若将c_cc[VMIN]或者c_cc[VTIME]中任意一个项修改为>0,那么read就返回-1了。
虽然表现形式同样,但在编程时必需要了解本身使用的是哪种模式和串口当前的状态才能更好的分析和处理问题。
这里说一下我曾经遇到过的一个问题:
我在打开串口时使用阻塞模式打开,可是没有设置c_cc[VMIN]的值,而它初始化后就是0,因此发现串口没有被阻塞,其实缘由就是串口模式仍是阻塞模式没错,可是它是超时0秒的状态,因此在没有数据到达时read也返回了。
关于阻塞模式下c_cc[VMIN] 和 c_cc[VTIME]的取值与现象,如下简称为VMIN和VTIME
这两个值有这些组合
VTIME | VMIN | 说明 |
0 | 0 | "超时0秒" |
0 | >0 | 一直阻塞到接收到VMIN个数据时read返回 |
>0 | 0 | 普通超时 |
>0 | >0 | 当接收到第一个字节时开始计算超时。 若是超时时间未到但数据已经达到VMIN个read当即返回。 若是超时时间到了就返回当前读到的个数。 |
阻塞状态和非阻塞状态的切换
非阻塞状态时使用
flag = fcntl(fd,F_GETFL); //先获取原文件状态(假定函数执行成功返回文件状态) flag &= ~O_NONBLOCK; //去除非阻塞的flag fcntl(fd,F_SETFL,flag); //设置新的文件状态
使用以上的方式可转换成阻塞状态,这时就能够能够进行超时设置了。
若是在非阻塞状态已经设置了超时时,在转换成阻塞状态后超时随之生效。
阻塞状态时使用
flag = fcntl(fd,F_GETFL); //先获取原文件状态(假定函数执行成功返回文件状态) flag |= O_NONBLOCK; //增长阻塞的flag fcntl(fd,F_SETFL,flag); //设置新的文件状态
使用以上的方式可转换成非阻塞状态,超时效果失效。
也可使用如下方法设置成非阻塞状态:
fcntl(fd,F_SETFL,FNDELAY); 有些代码中使用 fcntl(fd,F_SETFL,FNONBLOCK);
这里提一下 <fcntl.h>头文件中几个宏的定义
gcc 版本 4.6.3 (Ubuntu/Linaro 4.6.3-1ubuntu5)
/* Define some more compatibility macros to be backward compatible with BSD systems which did not managed to hide these kernel macros. */ #ifdef __USE_BSD # define FAPPEND O_APPEND # define FFSYNC O_FSYNC # define FASYNC O_ASYNC # define FNONBLOCK O_NONBLOCK # define FNDELAY O_NDELAY #endif /* Use BSD. */
/* open/fcntl - O_SYNC is only implemented on blocks devices and on files located on a few file systems. */ #define O_ACCMODE 0003 #define O_RDONLY 00 #define O_WRONLY 01 #define O_RDWR 02 #define O_CREAT 0100 /* not fcntl */ #define O_EXCL 0200 /* not fcntl */ #define O_NOCTTY 0400 /* not fcntl */ #define O_TRUNC 01000 /* not fcntl */ #define O_APPEND 02000 #define O_NONBLOCK 04000 #define O_NDELAY O_NONBLOCK #define O_SYNC 04010000 #define O_FSYNC O_SYNC #define O_ASYNC 020000
能够得出一下结论:
O_NDELAY == O_NONBLOCK == 0x4000
FNONBLOCK == O_NONBLOCK
FNDELAY == O_NDELAY
因此 FNDELAY == FNONBLOCK
上面设置非阻塞模式的方法归根结底就是要把文件状态加上O_NONBLOCK一个非阻塞的标志。
2015-01-21补充:
若是在非阻塞模式下调用read时没有立刻读到数据会当即返回-1,错误提示是(Resource temporarily unavailable)
并且会形成后面读到的数据多是前一次要读的数据,致使每一次都读了前一次的数据。