【轉】Linux串口IO模式的一些心得(阻塞、非阻塞、超時)

摘要 假如您從未接觸過串口編程 這篇短文可能不適合您^_^

 

衆所周知,在Linux系統下全部設備都是以文件的形式存在,串口也同樣。linux

一般I/O操做都是有阻塞與非阻塞的兩種方式。ios

 

其中"超時"這個概念實際上是阻塞中的一種處理手段,本質仍是屬於阻塞的I/O模式.編程

在Linux中串口的IO操做 本文將它分爲三種狀態:ubuntu

阻塞狀態ide

超時狀態函數

非阻塞狀態spa

 

這三種狀態的轉換組合有這麼幾種:.net

阻塞 --> 超時code

阻塞 --> 非阻塞blog

超時 --> 阻塞

超時 --> 非阻塞

非阻塞 --> 阻塞

 

咱們一個一個來分析

首先在一個串口的描述符打開的時候指定它的模式是阻塞仍是阻塞

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)

並且會形成後面讀到的數據多是前一次要讀的數據,致使每一次都讀了前一次的數據。

相關文章
相關標籤/搜索