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


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

 

眾所周知,在Linux系統下所有設備都是以文件的形式存在,串口也一樣。

通常I/O操作都是有阻塞與非阻塞的兩種方式。

 

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

在Linux中串口的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)

而且會造成后面讀到的數據可能是前一次要讀的數據,導致每一次都讀了前一次的數據。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM