圖解 IO 多路複用之 Select 實現原理

    Linux 上提供了 IO 多路複用機制的實現有多種,常見的有 select、poll、epoll,下面分析一下 select 的多路複用的原理。

    服務器端有 1 個監聽文件描述符和若干個通信文件描述符,每當服務器端建立一個新的連接後就會生成一個通信的文件描述符,如下圖所示:

    select 可以同時檢測讀緩衝區(Read buffer)、寫緩衝區(Write buffer),也可以只檢測某一個緩衝區數據。如果檢測到讀緩衝區中有數據、寫緩衝區有空閒的空間,這些都可以認爲文件描述符是就緒狀態。

1、select() 函數

    select 函數的主要作用是將用戶態的文件描述符複製到內核中,然後由內核幫助我們檢測並返回就緒的文件描述符, select 的原型函數如下所示:

int select(int nfds, fd_set *readfds, fd_set *writefds,
           fd_set *exceptfds, struct timeval * timeout);

(1)nfds

    內核需要檢測的集合(讀緩衝區、寫緩衝區、讀寫異常的緩衝區)中最大的文件描述符 + 1,該參數的意義在於,因爲內核需要線性遍歷需要檢測的集合中的文件描述符,這個值是循環結束的條件。當然沒有設置的時候,就使用最大的值 1024,如下所示:

(2)readfds

    讀緩衝區的文件描述符的集合,內核只檢測這個集合中文件描述符對應的讀緩衝區,如下圖所示:

    該參數是傳入傳出參數,即 select 函數先將數據複製到內核中,然後再由內核寫出到對應的內存空間上。

(3)writefds

    寫緩衝區文件描述符的集合,內核只檢測本集合中文件描述符對應的寫緩衝區,如下圖所示:

    該參數也是傳入傳出參數,如果不需要檢測寫緩衝區中的數據只需要將該參數可以指定爲 NULL 即可。

(4)exceptfds

    讀寫異常的文件描述符的集合,主要用於內核檢測集合中文件描述符是否有異常狀態,該參數也是傳入傳出,如果不需要檢測讀寫異常緩衝區中的數據只需要將該參數可以指定爲 NULL 即可。

(5)timeout

    超時的時間,主要用來強制退出 select() 函數的阻塞狀態,其值有如下幾種情況:

kmDNyC

    在 select 函數的讀集合、寫集合、讀寫異常的集合參數前都帶有 fd_set,那麼 fd_set 是什麼呢?其實 fd_set 表示一個文件描述符的集合,大小是 1024bit 位。如下所示的 fd_set 圖:

    如果想要操作 fd_set 集合(如查詢、賦值等操作),官方也提供一些函數來操作這個集合,常見的函數如下所示:

# 將fd_set對應的標誌位設置爲0        
void FD_CLR(int fd, fd_set *set);
# 讀取fd_set對應的標誌位上的值
int  FD_ISSET(int fd, fd_set *set);
# 將fd_set對應的標誌位設置爲1
void FD_SET(int fd, fd_set *set);
# 將fd_set集合中所有的文件文件描述符對應的標誌位設置爲0,
void FD_ZERO(fd_set *set);

後面遍歷的文件描述符集合的時候,需要使用這些函數做操作。

2、select 的工作原理

    當客戶端和服務端建立連接之後,服務端會給客戶端生成一個通信文件描述,我們以讀數據爲案例分析 Select 的工作原理,如下圖所示:

(1)在用戶態上,給 Seclet 函數的讀緩衝區集合的參數(參數 readfds)對應的位置設置成 1,如下圖所示:

(2)設置好讀取緩衝區的參數之後,由 select 函數將數據複製到內核的文件描述符上

(3)內核根據傳入的文件描述符對應位置上的值做判斷,如果 bit 位上的值是 1 就檢測對應的 Socket 上的讀緩衝區數據,如果 bit 位上是 0 就不做檢測,如下圖所示:

(4)如果有客戶端發送數據到內核中來的時候,如下圖所示:

    此時 Socket0 中會通過 DMA 技術複製數據到讀緩衝區上,那麼對應的文件描述符爲就緒狀態。內核根據文件描述符表依次線性執行下去,假設 seclet 函數設置了超時時間,如果在規定時間內還沒有數據到達監聽 Socket 上,此時內核將文件描述的 bit 位上的 1 修改成 0(修改成 0 表示不滿足就緒條件),如下所示:

    內核修改好內核上的文件描述符之後,重新的將內核文件描述符信息寫會用戶態的 fd_set 上,如下圖所示:

(4)select 函數返回的值如下所示:

GiLaau

    如果此時返回值大於 0,表示有就緒狀態的文件描述符,具體是哪個文件描述符就緒了,需要業務自己來遍歷文件 fd_set 來判斷。遍歷中如果 bit 位上的值是 1 則表示當前的讀緩衝區上有數據,開始讀取數據操作,如下:

    以上就是 select 的讀取數據的整個流程,寫數據的流程其實也是一樣的,這裏就不在具體的分析了。

總結:

(1)select 的工作原理是將當前進程中所有文件描述符一次性的從用戶態複製到內核態,隨後在內核態中遍歷每個文件描述符來判斷是否就緒,、內核將所有就緒狀態的文件描述符從內核態拷貝到用戶態,最後用戶態遍歷判斷具體哪個文件描述符已就緒並進行相應的業務處理。

(2)文件描述符是 bit 位的數組組成,長度有 1024 的限制並且文件描述符無法重用,每次循環都是重新創建。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/KN7BiqGrblCmrVnWVs4A-w