目錄
前言:
一.五種IO模型
二.同步通信和異步通信對比
三.阻塞和非阻塞
四.其它進階IO
4.1 非阻塞IO
前言:
記憶體于外設進行資料互動叫做IO。
IO的過程中主要由兩個動作,一個是等待,一個是拷貝。
比如:
讀IO,就是在等待可以讀資料的條件,條件成立将資料從核心空間拷貝到使用者空間。
寫IO,就是在等待可以寫資料的條件,條件成立将資料從使用者空間拷貝到核心空間。
高效IO的本質是:機關時間内,盡可能的減少等待的比重。
一.五種IO模型
- 阻塞IO:在核心資料準備好之前,系統調用會一直等待。
所有套接字,預設方式都是等待。阻塞IO是最常見的IO模型。
- 非阻塞IO:如果核心還未将資料準備好,系統調用仍會直接放回,并且傳回WOULDBLOCK錯誤碼。
非阻塞IO需要程式員編碼時,以循環的方式反複去嘗試讀寫檔案描述符,這個過程叫輪詢。這對CPU來說是一種較大的浪費,一般隻有在特定的情況下才使用。
- 信号驅動IO:核心将資料準備好之後,使用SIGIO信号,通知程序進行IO操作。
SIGIO信号:檔案描述符準備就緒, 可以開始進行輸入/輸出操作,預設捕捉動作是忽略。我們可以自定義捕捉動作為recvfrom或者sendto。當信号到來時,就可以将資料從核心拷貝到使用者,或者從使用者拷貝到核心。
注意:資料拷貝的過程還是需要程序來做。
- IO多路轉接:通過系統調用,可以同時等待多個檔案描述符的就緒狀态。至少一個就緒,就可以進行讀和寫了。
通過調用select/pol/epoll可以實作同一時刻等待多個檔案描述符的就緒狀态。
select/pol/epoll也是負責等待,那為什麼效率提高了呢?
因為同一時間等待多個檔案,檔案就緒的機率增加了,等待的時間就減少了。
當等待完畢後,調用read或者write進行IO就不需要等待了。
- 異步IO:由核心在資料拷貝完成時,通知程序,使用資料。
不需要程序等待和将資料空核心空間拷貝到使用者空間,這些動作都是核心做的,當核心拷貝完成後,通知程序來使用資料即可。
這個和信号驅動IO不同,信号驅動IO,程序需要自己将資料從核心拷貝到使用者。
總結:
前四種IO歸類為同步IO,最後一個歸類為異步IO。
任何IO過程中,都包含兩個動作,一個是等待,另外一個是拷貝,在實際生活中,等待消耗的時間遠遠大于拷貝的時間。讓IO高效,最核心的辦法就是讓等待的時間盡量少。
二.同步通信和異步通信對比
同步通信和異步通信關注的是消息通信機制。
- 同步通信:就是在發出一個調用時,在沒有得到結果之前,該調用不會傳回。一旦傳回就得到傳回值。調用者主動等待這個調用的結果。需要調用者自己參與。
- 異步通信:調用在發出後,調用直接傳回,沒有傳回結果。而被調用者通過狀态,通知來通知調用者,或者通過回調函數處理這個調用。不需要調用者自己參與。
拿上面的信号驅動IO舉例,信号驅動IO的情況很像異步IO,但是,信号通信IO最後還需要調用者對資料進行拷貝,是以屬于同步IO。
在多線程和多程序中,也有同步和互斥的概念。和這裡是完全不相同的。
- 程序或線程的同步,是程序和線程之間的一種制約關系。
- 多線程之間的同步,是讓多個線程之間,可以按照一定次序運作。使得條件不滿足時等待,滿足時,可以立馬執行。調高效率。
三.阻塞和非阻塞
阻塞和非阻塞,是指條件不滿足時,等待方式的不同。
- 阻塞:在調用結果傳回前,目前程序會被挂起等待,程序被放入等待隊列,狀态設為非R。調用線程隻有在得到結果後才傳回。
- 非阻塞:在不能得到結果之前,目前線程不會被挂起等待,而是會直接傳回。
四.其它進階IO
非阻塞IO,記錄鎖,系統V流機制,IO多路轉接(IO多路複用),readv和writev函數以及存儲映射IO(mmap),這些統稱為進階IO。
下面介紹兩種IO,非阻塞IO和IO多路轉接
4.1 非阻塞IO
一個檔案描述符,預設情況下都是阻塞IO。調用fcntl函數可以改變檔案描述符為非阻塞IO。
#include<unistd.h>
#include<fcntl.h>
int fcntl(int fd, int cmd, ... /* arg */ );
參數 | 作用 |
fd | 打開的檔案描述符 |
cmd | 具體操作 |
... | 可變參數 |
fcntl函數的功能:
- 複制一個現有的描述符,對應cmd = F_DUPFD
- 獲得/設定檔案描述符标記,對應cmd = F_GETFD / F_SETFD
- 獲得/設定檔案狀态标記,對應cmd = F_GETFL / F_SETFL
- 獲得/設定異步IO所有權,對應cmd = F_GETOWN / F_SETOWN
- 獲得/設定記錄鎖,對應cmd = F_GETLK / F_SETLK或F_SETLKW
我們可以使用第三個功能,獲得/設定檔案狀态标記,就可以将一個檔案描述符設定為非阻塞。
使用如下:
void SetNoBlack(int fd){
//獲得檔案狀态
int fl = fcntl(fd,F_GETFL);
if(fl < 0){
perror("fcntl error\n");
return;
}
//設定檔案狀态
fcntl(fd, F_SETFL, fl | O_NONBLOCK);
}
- 使用F_GETFL将目前的檔案描述符屬性取出來,是一個位圖。
- 然後使用F_SETFL将檔案描述符設定回去,設定回去的同時加上 O_NONBLOCK參數。
輪詢方式讀取标準輸入:
輪詢方式讀取:read當讀條件不滿足是,實際是阻塞的。當設定為非阻塞後,不斷循環調用read,就是在輪詢檢測read條件是否滿足。
說明:read非阻塞狀态下,當傳回值為小于0時,說明是讀的條件不滿足,而不是read調用失敗。當讀條件不滿足時,不僅read的傳回值回小于0,還會将全局變量錯誤碼errno設定為EAGAIN(一個宏,值為11)。
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
void SetNoBlack(int fd){
//獲得檔案狀态
int fl = fcntl(fd,F_GETFL);
if(fl < 0){
perror("fcntl error\n");
return;
}
//設定檔案狀态
fcntl(fd, F_SETFL, fl | O_NONBLOCK);
}
int main(){
//将标準輸入設定為非阻塞
SetNoBlack(0);
char c = 0;
//輪詢檢測read
while(1){
sleep(1);
//printf("begin to read\n");
ssize_t n = read(0, &c, 1);
if(n > 0){
printf("%c\n",c);
}
//并不是read錯誤,而是read的條件不滿足
else if(n < 0 && errno == EAGAIN){
printf("read cond is not met....\n");
}
else{
perror("read error\n");
}
printf("------------------\n");
}
return 0;
}
為了看起來友善,我将多路轉接IO寫在了另外一篇部落格。