天天看點

典型I/O模型——阻塞IO,非阻塞IO,信号驅動IO,異步IO,IO多路轉接(select&poll&epoll)1.典型IO模型

典型IO模型總結

  • 1.典型IO模型
    • 1.1 什麼是IO模型
    • 1.2 阻塞IO模型
    • 1.3 非阻塞IO模型
    • 1.4 信号驅動IO模型
    • 1.5 異步IO模型
    • 1.6 多路轉接IO模型
      • 1.6.1 select
        • 1.6.1.1 select優缺點總結
      • 1.6.2 poll
        • 1.6.2.1 函數接口
        • 1.6.2.2 程式設計執行個體
        • 1.6.2.3 poll的優缺點
      • 1.6.3 epoll(目前公認的在Linux系統下監控性能最高)
        • 1.6.3.1 epoll(建立句柄)接口
        • 1.6.3.2 epoll(添加/删除/修改事件結構)接口
        • 1.6.3.3 epoll監控接口
        • 1.6.3.4 epoll的工作原理
        • 1.6.3.5 epoll模拟代碼
        • 1.6.3.6 工作方式:水準觸發 LT模式(EPOLLLT)
        • 1.6.3.7 工作方式:邊緣觸發 ET模式(EPOLLET)

1.典型IO模型

1.1 什麼是IO模型

  • 我們在執行一個程式時,這個程式要求外設進行輸入或者輸出,核心在處理這個資料時,都在做什麼?
  • 第一步:等待IO就緒,已經準備好需要的資源了,可以開始操作
    典型I/O模型——阻塞IO,非阻塞IO,信号驅動IO,異步IO,IO多路轉接(select&poll&epoll)1.典型IO模型
  • 第二步:将資料拷貝到緩沖區當中(接受緩沖區&發送緩沖區)

1.2 阻塞IO模型

  • 資源不可用的情況下,IO請求一直被阻塞,直到資源可以用
    典型I/O模型——阻塞IO,非阻塞IO,信号驅動IO,異步IO,IO多路轉接(select&poll&epoll)1.典型IO模型
  • 以釣魚舉例子:釣魚的時候,将魚鈎抛入水中

    (發起IO調用)

    ,一直盯着魚漂

    (等待IO就緒)

    ,魚兒咬鈎

    (拷貝資料到緩沖區)

    ,将魚兒釣上來

    (IO調用傳回)

  • 阻塞IO的特點:

    1.發起IO調用之後,等待的時間取決于核心

    2.在等待的過程中,執行流是被挂起的(對CPU的使用率低)

    3.在IO就緒到拷貝資料之間,實時性很高(響應快)

1.3 非阻塞IO模型

  • 我們在使用程式是輪詢的方式,一直詢問核心資料有沒有準備好
  • 資源不可用的時候,IO請求不會被阻塞,而是直接傳回,傳回目前資源不可用(EBUSY)
典型I/O模型——阻塞IO,非阻塞IO,信号驅動IO,異步IO,IO多路轉接(select&poll&epoll)1.典型IO模型
  • 以釣魚舉例子:釣魚的時候,将魚鈎抛入水中,看一眼魚漂,魚漂如果沒動,則看一會手機,再看一眼魚漂,魚漂沒動,再看一會手機,如此往複,直到魚兒咬鈎,将魚釣上來
  • 非阻塞IO的特點:

    1.非阻塞IO對CPU的使用率比阻塞IO高 ``2.代碼結構相對複雜

    3.需要搭配循環使用,直到IO請求完成

    4.IO準備就緒到資料拷貝之間,實時性不高(因為可能在你看手機的過程中,魚兒咬鈎了)

差別:

在資源不可用的情況下,就看系統的調用是否立即傳回

  • 立即傳回:非阻塞IO
  • 沒有立即傳回:阻塞IO

1.4 信号驅動IO模型

  • 信号驅動IO:核心态中把資料準備完成之後,使用之前定義的SIGIO信号通知應用程式進行IO操作
流程:

1.自定義一個IO信号(SIGIO)的處理函數,再處理函數當中發起IO調用

2.程式收到一個IO信号(SIGIO),核心就會調用自定義的處理函數,在自定義的處理函數中發起IO調用

典型I/O模型——阻塞IO,非阻塞IO,信号驅動IO,異步IO,IO多路轉接(select&poll&epoll)1.典型IO模型
  • 以釣魚舉例子:先在魚杆上綁一個鈴铛,将魚鈎抛入水中(接下來玩手機),魚兒咬鈎之後,鈴铛就會響,将魚兒釣上來
  • 信号驅動IO的特點:

    1.IO準備就緒到拷貝資料之間,實時性更強

    2.代碼更加複雜,流程控制更加困難(引入了信号)

    3.不需要重複發起IO調用,但是需要在代碼當中增加自定義信号的邏輯

1.5 異步IO模型

  • 異步IO:當核心中把程式拷貝完成後,再通知程式(信号驅動就是告訴應用程式何時開始拷貝資料)
流程:

1.自定義信号(SIGIO)處理函數,用來通知資料拷貝完成

2.發起一個異步IO調用,并且異步IO調用直接傳回

3.異步IO調用傳回之後,執行流可以執行使用者代碼

(由作業系統核心來等待IO就緒和資料拷貝)

4.當資料拷貝完成之後,核心通過信号來告知調用者

典型I/O模型——阻塞IO,非阻塞IO,信号驅動IO,異步IO,IO多路轉接(select&poll&epoll)1.典型IO模型
  • 異步IO調用:異步IO函數接口當中一般會設定一個函數指針,來儲存IO調用完成之後,通知調用者的回調函數
小結:

1.同步IO:

當程式發出一個調用的時候,在沒有得到結果之前,這個調用就不會傳回。當調用傳回的時候,就一定是得到了一個結果

2.異步IO:

當程式發出一個調用,這個調用就直接傳回了,是以沒有傳回結果。等到核心中完成了拷貝的操作,再通過一些方式來通知調用者(信号),或者通過回調函數來處理這個調用

3.異步IO最大的特點:使用者不需要拷貝資料了,拷貝資料由作業系統核心來完成,完成拷貝之後,通知調用

1.6 多路轉接IO模型

  • IO多路轉接:可以完成大量檔案描述符的監控,監控的事件:

    可讀事件

    可寫事件

    異常事件

  • 監控檔案描述符:哪個檔案描述符準備就緒,就處理哪一個檔案描述符
  • 好處:避免了其他程序對沒有就緒的檔案描述符進行操作,進而陷入阻塞狀态

1.6.1 select

  • 作用:用程式來對多個檔案描述符的狀态進行監控,如果有描述符準備就緒,就傳回該描述符,讓使用者對這個描述符進行操作
  • 流程:

    .将用于關心的檔案描述符拷貝到核心當中,核心來進行監控

    .如何核心監控到某個檔案描述符就緒,則傳回該描述符

    .使用者對傳回的描述符進行操作
  • 函數接口
    典型I/O模型——阻塞IO,非阻塞IO,信号驅動IO,異步IO,IO多路轉接(select&poll&epoll)1.典型IO模型

1.

nfds

:取值為監控最大的檔案描述符數值+1(最大的數值為1024),輪詢的範圍由

nfds

來決定

2.

fd_set

:本質是一個結構體,結構體内部是一個

fds_bits

數組,可以了解為一個1024位的位圖,對應1024個檔案描述符

典型I/O模型——阻塞IO,非阻塞IO,信号驅動IO,異步IO,IO多路轉接(select&poll&epoll)1.典型IO模型
_FD_SIZE:#define _FD_SETSIZE 1024
_NFDBITS:#define _NFDBITS (8*(int)sizeof(_fd_mask))
	數組的元素個數:1024 / 8*(int)sizeof(_fd_mask)
_fd_mask:typedef long int_fd mask
	數組當中比特位的個數:
	(1024/8 * (int)sizeof(_fd_mask)) * 8 *(int)sizeof(_fd_mask)) = 1024
總結:select的事件集合當中總共有1024個比特位,取決于宏 _FD_SETSIZE 這個宏的大小
           

3.

舉個例子

典型I/O模型——阻塞IO,非阻塞IO,信号驅動IO,異步IO,IO多路轉接(select&poll&epoll)1.典型IO模型

4.

fd_set集合中提供了4個函數

典型I/O模型——阻塞IO,非阻塞IO,信号驅動IO,異步IO,IO多路轉接(select&poll&epoll)1.典型IO模型
//從事件集合當中删除檔案描述符fd,描述符對應的比特位置0
void FD_CLR(int fd, fd_set *set);

//判斷fd描述符是否在set集合當中;
//傳回值:0表示沒有在集合當中,非0表示在集合當中
int  FD_ISSET(int fd, fd_set *set);

//将檔案描述符fd設定到set集合當中,描述符對應的比特位置1
void FD_SET(int fd, fd_set *set);

//清空事件集合,将所有的比特位置0
void FD_ZERO(fd_set *set);
           

5.

readfds:

可讀檔案描述符的結合 ;

writefds:

可寫檔案描述符的集合;

exceptfds:

異常檔案描述符的集合

6.

timeout:

逾時時間;

tv_sec:秒

tv_usec:微秒

典型I/O模型——阻塞IO,非阻塞IO,信号驅動IO,異步IO,IO多路轉接(select&poll&epoll)1.典型IO模型
  • timeout == NULL

    阻塞監控

  • timeout == 0

    非阻塞監控

  • timeout > 0

    等待逾時時間監控

7.

函數傳回值

典型I/O模型——阻塞IO,非阻塞IO,信号驅動IO,異步IO,IO多路轉接(select&poll&epoll)1.典型IO模型

1.6.1.1 select優缺點總結

  • 優點
1.select遵循的是POSIX标準,可以

跨平台移植

2.select的

逾時時間

可以精确到微秒
  • 缺點

1.select采用的是輪詢周遊,監控的效率會随着檔案描述符的增多而下降

2.select所能監控的檔案描述符是有上限的(

1024

),取決于核心FD_SETSIZE宏的值

3.select監控檔案描述符的時候,需要将集合拷貝到核心當中,select發現有事件就緒之後,同時需要将事件集合從核心拷貝到使用者空間,效率也會受影響

4.select在傳回的時候,

會将未就緒的檔案描述符從集合當中去除掉

,導緻下一次監控的時候,如果還需要監控去除掉的檔案描述符,就得

重新添加

5.select

無法直接檢視那個檔案描述符已經就緒

,需要手

動通過傳回事件的集合去判斷

6.select的

逾時機制

,如果在循環判斷的情況下,每次調用之前都需要更新一下時間。因為在計時的時候,這個結構體中的時間是會變的

1.6.2 poll

poll和select相比,跨平台移植性不如select,poll函數隻能在Linux環境下使用,也采用輪詢周遊

與select相比,改進的點:

1.不限制監控的檔案描述符的個數

2.select使用的是事件集合方式,poll采用的是事件結構

(檔案描述符對應一個

事件結構

,這個結構中

有兩個事件

,一個

是要監控的檔案描述符

,另一個

是這個檔案描述符所對應的事件

1.6.2.1 函數接口

典型I/O模型——阻塞IO,非阻塞IO,信号驅動IO,異步IO,IO多路轉接(select&poll&epoll)1.典型IO模型

fds:

事件結構數組

nfds:

事件結構數組中有效的元素個數

timeout:

逾時時間
典型I/O模型——阻塞IO,非阻塞IO,信号驅動IO,異步IO,IO多路轉接(select&poll&epoll)1.典型IO模型
fd:

關心的檔案描述符

是什麼

events:關心的檔案描述符産生的事件是什麼,如果關心多個事件,可以将多個事件

按照按位或的方式連接配接起來

典型I/O模型——阻塞IO,非阻塞IO,信号驅動IO,異步IO,IO多路轉接(select&poll&epoll)1.典型IO模型
revents:當關心的檔案描述符産生對應的關心的事件時,傳回給調用者發生的事件(每次監控的時候,就會被初始化為空)

1.6.2.2 程式設計執行個體

#include<stdio.h>
#include<unistd.h>
#include<poll.h>
#include<iostream>

int main()
{
	//建立一個struct pollfd結構體,關心0号檔案描述符,标準輸入
	//監視可讀事件
    struct pollfd fd_arr[10];
    fd_arr[0].fd = 0;
    fd_arr[0].events = POLLIN;

	//輪詢周遊
    while(1)
    {
        int ret = poll(fd_arr, 1 , 1000);
        if(ret < 0) //poll出錯
        {
            perror("poll error");
            return -1;
        }
        else if(ret == 0)//監控逾時
        {
            printf("poll timeout\n");
            continue;
        }

        for(int i = 0; i < ret; i++)
        {
            if(fd_arr[i].revents == POLLIN)//當發生的事件可讀
            {
				//讀取資料
                char buf[1024] = {0};
                read(fd_arr[i].fd, buf, sizeof(buf) - 1);
                printf("buf: %s\n",buf);
            }
        }
    }
    return 0;

}
           

運作結果:

典型I/O模型——阻塞IO,非阻塞IO,信号驅動IO,異步IO,IO多路轉接(select&amp;poll&amp;epoll)1.典型IO模型

1.6.2.3 poll的優缺點

優點:

  • poll采用了事件結構的方式,簡化了代碼的編寫
  • poll不限制檔案描述符的個數
  • 不需要再二次監控的時候重新添加檔案描述符

缺點:

  • poll采用輪詢周遊事件結構數組的方式,随着檔案描述符增多,性能下降
  • poll不支援平台
  • poll也沒有告訴使用者哪一個具體的檔案描述符就緒了,需要自己判斷
  • poll也需要将事件結構拷貝到核心,從核心再拷貝到使用者空間

1.6.3 epoll(目前公認的在Linux系統下監控性能最高)

1.6.3.1 epoll(建立句柄)接口

典型I/O模型——阻塞IO,非阻塞IO,信号驅動IO,異步IO,IO多路轉接(select&amp;poll&amp;epoll)1.典型IO模型
  • 建立

    epoll

    的操作句柄
#include <sys/epoll.h>

int epoll_create(int size);
           

size:本來的含義是定義epoll最大能夠監控檔案描述符的個數,核心在2.6.8之後就啟棄用了,現在采用動态記憶體開辟的方式,來進行擴容,size的值大于0,在使用完之後,必須使用close進行關閉

傳回值:傳回epoll的操作句柄

核心觀點:在核心當中就是建立一個struct eventpoll結構體,在這個結構體當中有兩個重要的變量,一個是紅黑樹,一個是雙向連結清單

epoll的操作句柄其實就是用來找到struct eventpoll結構體,進而對結構體當中的變量進行操作

1.6.3.2 epoll(添加/删除/修改事件結構)接口

典型I/O模型——阻塞IO,非阻塞IO,信号驅動IO,異步IO,IO多路轉接(select&amp;poll&amp;epoll)1.典型IO模型

向核心維護的紅黑樹當中添加/删除/修改事件結構

#include <sys/epoll.h>

int epoll_ctl(int epfd, int op, int fd, 
		struct epoll_event *event);
           

epfd:epoll_create的傳回值,epoll的操作句柄

op:想讓epoll_ctl做的事

典型I/O模型——阻塞IO,非阻塞IO,信号驅動IO,異步IO,IO多路轉接(select&amp;poll&amp;epoll)1.典型IO模型

fd:告訴epoll函數,我們關心的檔案描述符

event:epoll的事件結構,類型是 struct epoll_event結構體

typedef union epoll_data    
{    
  *ptr和fd兩者隻能選一個*
  
  void *ptr;  --->如果使用ptr,需要包含fd的内容
  int fd; --->使用者關心的檔案描述符,可以當做檔案描述符中的事件就緒之後,傳回給我們時檢視;其取值為檔案描述符的數值

  uint32_t u32;    
  uint64_t u64;    
} epoll_data_t;    
    
struct epoll_event    
{    
  想讓檔案描述符關心的事件集合:EPOLLIN可讀事件 EPOLLOUT可寫事件
  uint32_t events;  /* Epoll events */ 
  
  epoll_data類型的聯合結構體
  epoll_data_t data;    /* User data variable */    
} __EPOLL_PACKED;  

           
典型I/O模型——阻塞IO,非阻塞IO,信号驅動IO,異步IO,IO多路轉接(select&amp;poll&amp;epoll)1.典型IO模型

1.6.3.3 epoll監控接口

典型I/O模型——阻塞IO,非阻塞IO,信号驅動IO,異步IO,IO多路轉接(select&amp;poll&amp;epoll)1.典型IO模型
#include <sys/epoll.h>

int epoll_wait(int epfd, struct epoll_event *events,
         int maxevents, int timeout);
           

epfd:epoll操作句柄

events:事件結構數組,作為出參,傳回就緒的事件結構,一個檔案描述符對應一個事件結構

maxevents:表示最大能接受多少個事件結構

timeout:逾時時間

典型I/O模型——阻塞IO,非阻塞IO,信号驅動IO,異步IO,IO多路轉接(select&amp;poll&amp;epoll)1.典型IO模型

1.6.3.4 epoll的工作原理

典型I/O模型——阻塞IO,非阻塞IO,信号驅動IO,異步IO,IO多路轉接(select&amp;poll&amp;epoll)1.典型IO模型
典型I/O模型——阻塞IO,非阻塞IO,信号驅動IO,異步IO,IO多路轉接(select&amp;poll&amp;epoll)1.典型IO模型

1.6.3.5 epoll模拟代碼

#include<iostream>
#include<stdio.h>
#include<unistd.h>
#include<sys/epoll.h>
#include<string.h>
#include<stdlib.h>

/*
 * 1.建立epoll操作句柄
 * 2.添加事件結構
 *   2.1準備事件結構
 *   2.2關心的事件及檔案描述符
 * 3.監控
 *   3.1阻塞
 *   3.2非阻塞
 *   3.3帶有逾時事件
 * 4.判斷epoll_wait的傳回值
 * 5.執行相應的操作 
 * */

int main()
{
    int epfd = epoll_create(5);
    if(epfd < 0)
    {
        perror("create error");
        return 0;
    }
    struct epoll_event ee;
    ee.events = EPOLLIN;
    ee.data.fd = 0;
    epoll_ctl(epfd, EPOLL_CTL_ADD, 0, &ee);


    while(1)
    {

    struct epoll_event arr[10];
    memset(arr, '\0', sizeof(struct epoll_event) * 10);
    
    int ret = epoll_wait(epfd, arr, 10, 0);
    if(ret < 0)
    {
        perror("epoll_wait error");
        return 0;
    }
    else if(ret == 0)
    {
        sleep(1);
        printf("timeout..\n");
        continue;
    }
    for(int i = 0; i < 10; i++)
    {
        if(arr[i].events == EPOLLIN)
        {
            char buf[1024] = {0};
            read(arr[i].data.fd, buf,sizeof(buf) - 1);
            printf("buf:%s", buf);
        }
    }
    }
    return 0;
}
           
典型I/O模型——阻塞IO,非阻塞IO,信号驅動IO,異步IO,IO多路轉接(select&amp;poll&amp;epoll)1.典型IO模型

1.6.3.6 工作方式:水準觸發 LT模式(EPOLLLT)

  • epoll的預設工作方式,

    select和poll

    都是水準觸發方式
  • 在LT模式當中,當epoll中檢測到了等待觸發事件就緒後,可以不立即進行處理,而是隻處理一部分。等到第二次調用epoll_wait函數的時候,可以接着剛才沒有處理完的資料進行操作(

    支援阻塞讀寫和非阻塞讀寫)

  • 可讀事件:隻要發送緩沖區當中資料大于低水位标記(1位元組),就會一直觸發可讀事件就緒,直到接收緩沖區當中沒有資料可讀(接收緩沖區當中的資料低于低水位标記)
  • 可寫事件:隻要發送緩沖區當中的空間大小大于低水位标記(1位元組),就會一直觸發可寫事件就緒,直到緩沖區當中沒有空間可寫

1.6.3.7 工作方式:邊緣觸發 ET模式(EPOLLET)

  • EPOLLET隻有epoll才有
  • 可讀事件:隻有新的資料到來的時候,才會觸發可讀,否則通知一次就不通知了(

    每次到來一個新的資料,隻會通知一次,如果應用程式沒有将接收緩沖區當中的資料讀走或者讀完,也不會通知,直到新的資料到來,才會觸發可讀事件。如果出發可讀事件,盡量将資料讀完

  • 對于ET模式而言,如果就緒事件産生,一定要把握好機會,對于可讀事件,将資料讀完,對于可寫事件,将資料寫完
如何在代碼中展現ET
設定檔案描述符對應的事件結構的時候,隻需要在事件結構當中關心的事件變量按位或上 EPOLLET 就可以了
struct epoll_enent ev;
ev.events = EPOLLIN | EPOLLET;
           

繼續閱讀