天天看點

高效IO——五種IO模型概念和非阻塞IO前言:一.五種IO模型二.同步通信和異步通信對比三.阻塞和非阻塞四.其它進階IO

目錄

前言:

一.五種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——五種IO模型概念和非阻塞IO前言:一.五種IO模型二.同步通信和異步通信對比三.阻塞和非阻塞四.其它進階IO

為了看起來友善,我将多路轉接IO寫在了另外一篇部落格。

繼續閱讀