1. 程式人生 > >Netty系列一:IO網路模型(select/poll/epoll)

Netty系列一:IO網路模型(select/poll/epoll)

一 概念說明

在進行解釋之前,首先要說明幾個概念:
- 使用者空間和核心空間
- 程序切換
- 程序的阻塞
- 檔案描述符
- 快取 I/O

使用者空間與核心空間

現在作業系統都是採用虛擬儲存器,那麼對32位作業系統而言,它的定址空間(虛擬儲存空間)為4G(2的32次方)。作業系統的核心是核心,獨立於普通的應用程式,可以訪問受保護的記憶體空間,也有訪問底層硬體裝置的所有許可權。為了保證使用者程序不能直接操作核心(kernel),保證核心的安全,操心繫統將虛擬空間劃分為兩部分,一部分為核心空間,一部分為使用者空間。針對linux作業系統而言,將最高的1G位元組(從虛擬地址0xC0000000到0xFFFFFFFF),供核心使用,稱為核心空間,而將較低的3G位元組(從虛擬地址0x00000000到0xBFFFFFFF),供各個程序使用,稱為使用者空間。

程序切換

為了控制程序的執行,核心必須有能力掛起正在CPU上執行的程序,並恢復以前掛起的某個程序的執行。這種行為被稱為程序切換。因此可以說,任何程序都是在作業系統核心的支援下執行的,是與核心緊密相關的。

從一個程序的執行轉到另一個程序上執行,這個過程中經過下面這些變化:
1. 儲存處理機上下文,包括程式計數器和其他暫存器。
2. 更新PCB資訊。
3. 把程序的PCB移入相應的佇列,如就緒、在某事件阻塞等佇列。
4. 選擇另一個程序執行,並更新其PCB。
5. 更新記憶體管理的資料結構。
6. 恢復處理機上下文。

注:總而言之就是很耗資源,具體的可以參考這篇文章:程序切換

程序的阻塞

正在執行的程序,由於期待的某些事件未發生,如請求系統資源失敗、等待某種操作的完成、新資料尚未到達或無新工作做等,則由系統自動執行阻塞原語(Block),使自己由執行狀態變為阻塞狀態。可見,程序的阻塞是程序自身的一種主動行為,也因此只有處於執行態的程序(獲得CPU),才可能將其轉為阻塞狀態。當程序進入阻塞狀態,是不佔用CPU資源的。

檔案描述符fd

檔案描述符(File descriptor)是電腦科學中的一個術語,是一個用於表述指向檔案的引用的抽象化概念。

檔案描述符在形式上是一個非負整數。實際上,它是一個索引值,指向核心為每一個程序所維護的該程序開啟檔案的記錄表。當程式開啟一個現有檔案或者建立一個新檔案時,核心向程序返回一個檔案描述符。在程式設計中,一些涉及底層的程式編寫往往會圍繞著檔案描述符展開。但是檔案描述符這一概念往往只適用於UNIX、Linux這樣的作業系統。

快取 I/O

快取 I/O 又被稱作標準 I/O,大多數檔案系統的預設 I/O 操作都是快取 I/O。在 Linux 的快取 I/O 機制中,作業系統會將 I/O 的資料快取在檔案系統的頁快取( page cache )中,也就是說,資料會先被拷貝到作業系統核心的緩衝區中,然後才會從作業系統核心的緩衝區拷貝到應用程式的地址空間。

快取 I/O 的缺點:
資料在傳輸過程中需要在應用程式地址空間和核心進行多次資料拷貝操作,這些資料拷貝操作所帶來的 CPU 以及記憶體開銷是非常大的。

二 IO網路模型

剛才說了,對於一次IO訪問(以read舉例),資料會先被拷貝到作業系統核心的緩衝區中,然後才會從作業系統核心的緩衝區拷貝到應用程式的地址空間。所以說,當一個read操作發生時,它會經歷兩個階段:
1. 等待資料準備 (Waiting for the data to be ready)
2. 將資料從核心拷貝到程序中 (Copying the data from the kernel to the process)

正式因為這兩個階段,linux系統產生了下面五種網路模式的方案。
- 阻塞 I/O(blocking IO)
- 非阻塞 I/O(nonblocking IO)
- I/O 多路複用( IO multiplexing)
- 訊號驅動 I/O( signal driven IO)
- 非同步 I/O(asynchronous IO)

注:由於signal driven IO在實際中並不常用,所以我這隻提及剩下的四種IO Model。

這裡我們重點說明一下,比較比較成熟的IO多路複用模型。

I/O 多路複用( IO multiplexing)

IO multiplexing就是我們說的select,poll,epoll,有些地方也稱這種IO方式為event driven IO。select/epoll的好處就在於單個process就可以同時處理多個網路連線的IO。它的基本原理就是select,poll,epoll這個function會不斷的輪詢所負責的所有socket,當某個socket有資料到達了,就通知使用者程序。
這裡寫圖片描述

所以,I/O 多路複用的特點是通過一種機制一個程序能同時等待多個檔案描述符,而這些檔案描述符(套接字描述符)其中的任意一個進入讀就緒狀態,select()函式就可以返回。

blocking IO只調用了一個system call (recvfrom)。但是,用select的優勢在於它可以同時處理多個connection。

所以,如果處理的連線數不是很高的話,使用select/epoll的web server不一定比使用multi-threading + blocking IO的web server效能更好,可能延遲還更大。select/epoll的優勢並不是對於單個連線能處理得更快,而是在於能處理更多的連線。)

在IO multiplexing Model中,實際中,對於每一個socket,一般都設定成為non-blocking,但是,如上圖所示,整個使用者的process其實是一直被block的。只不過process是被select這個函式block,而不是被socket IO給block。

三 I/O 多路複用之select、poll、epoll詳解

select,poll,epoll都是IO多路複用的機制。I/O多路複用就是通過一種機制,一個程序可以監視多個描述符,一旦某個描述符就緒(一般是讀就緒或者寫就緒),能夠通知程式進行相應的讀寫操作。但select,poll,epoll本質上都是同步I/O,因為他們都需要在讀寫事件就緒後自己負責進行讀寫,也就是說這個讀寫過程是阻塞的,而非同步I/O則無需自己負責進行讀寫,非同步I/O的實現會負責把資料從核心拷貝到使用者空間。

在 select/poll中,程序只有在呼叫一定的方法後,核心才對所有監視的檔案描述符進行掃描,而epoll事先通過epoll_ctl()來註冊一 個檔案描述符,一旦基於某個檔案描述符就緒時,核心會採用類似callback的回撥機制,迅速啟用這個檔案描述符,當程序呼叫epoll_wait() 時便得到通知。(此處去掉了遍歷檔案描述符,而是通過監聽回撥的的機制。這正是epoll的魅力所在。)

現代作業系統系統很長一段時間都是再使用select做輪訓網路事件通知的,最終linux在2.6核心中使用epoll的方式對select做了重大改進:

epoll的優點主要是一下幾個方面:

1) 監視的描述符數量不受限制,它所支援的FD上限是最大可以開啟檔案的數目,這個數字一般遠大於2048,舉個例子,在1GB記憶體的機器上大約是10萬左 右,具體數目可以cat/proc/sys/fs/file-max察看,一般來說這個數目和系統記憶體關係很大。select的最大缺點就是程序開啟的fd是有數量限制的。這對於連線數量比較大的伺服器來說根本不能滿足。雖然也可以選擇多程序的解決方案(Apache就是這樣實現的),不過雖然linux上面建立程序的代價比較小,但仍舊是不可忽視的,加上程序間資料同步遠比不上執行緒間同步的高效,所以也不是一種完美的方案。

2) I/O效率不會隨著FD數目的增加而線性下降
epoll不同於select和poll輪詢的方式,而是通過每個fd定義的回撥函式來實現的。通過事件驅動的方式,只有就緒的fd才會執行回撥函式。

3) 使用mmap加速核心與記憶體使用者空間的訊息傳遞
epoll通過系統核心和使用者空間使用mmap(共享記憶體的方式)來實現