網絡IO模型之BIO、NIO、SELECT、EPOLL簡析

鋪墊

用戶態和內核態

首先來看一Linux架構圖: redis

從上圖能夠看到,系統調用把Linux系統最底下的內核和上面部分作了分隔,而分開的這兩部分,上層「應用程序+庫函數+Shell」就是用戶空間,而底下的內核就是內核空間了。

在用戶空間中,應用程序爲了訪問途中最底層的硬件資源,必須經過系統調用來讓內核去操做全部的硬件資源,而後內核從硬件資源獲取反饋後,將反饋再拿到內核空間,再從內核空間返回給用戶空間的應用程序。網絡

爲何要區分用戶空間和內核空間呢?
架構

在 CPU 的全部指令中,有些指令是很是危險的,若是錯用,將致使系統崩潰,好比清內存、設置時鐘等。若是容許全部的程序均可以使用這些指令,那麼系統崩潰的機率將大大增長。
因此,CPU 將指令分爲特權指令和非特權指令,對於那些危險的指令,只容許操做系統及其相關模塊使用,普通應用程序只能使用那些不會形成災難的指令。好比 Intel 的 CPU 將特權等級分爲 4 個級別:Ring0~Ring3。
其實 Linux 系統只使用了 Ring0 和 Ring3 兩個運行級別(Windows 系統也是同樣的)。當進程運行在 Ring3 級別時被稱爲運行在用戶態,而運行在 Ring0 級別時被稱爲運行在內核態。
在內核態下,進程運行在內核地址空間中,此時 CPU 能夠執行任何指令。運行的代碼也不受任何的限制,能夠自由地訪問任何有效地址,也能夠直接進行端口的訪問。
在用戶態下,進程運行在用戶地址空間中,被執行的代碼要受到 CPU 的諸多檢查,它們只能訪問映射其地址空間的頁表項中規定的在用戶態下可訪問頁面的虛擬地址,且只能對任務狀態段(TSS)中 I/O 許可位圖(I/O Permission Bitmap)中規定的可訪問端口進行直接訪問。
對於之前的 DOS 操做系統來講,是沒有內核空間、用戶空間以及內核態、用戶態這些概念的。能夠認爲全部的代碼都是運行在內核態的,於是用戶編寫的應用程序代碼能夠很容易的讓操做系統崩潰掉。
對於 Linux 來講,經過區份內核空間和用戶空間的設計,隔離了操做系統代碼(操做系統的代碼要比應用程序的代碼健壯不少)與應用程序代碼。即使是單個應用程序出現錯誤也不會影響到操做系統的穩定性,這樣其它的程序還能夠正常的運行。socket

Linux 系統調用和庫函數的區別函數

cpu中斷

軟中斷
硬中斷操作系統

有了上述的基本鋪墊後,下面來解析下各網絡IO模型的結構,這裏咱們以redis服務爲例。線程

BIO(Blocking IO)同步阻塞IO

    1. redis-server啓動,調用系統調用函數socket來在內核空間建立一個文件描述符6 - fd6(僅有一個fd6但尚未實際內存地址),接着使用系同調用bind函數爲該文件描述符綁定一個內存地址,而後再使用系統調用函數listen來監聽fd6,最後使用accept來接收鏈接至fd6的鏈接。
    1. 一個redis-cli啓動準備鏈接至redis-server,也就是要鏈接fd6,也會調用一系列系統函數,當步驟1阻塞中的accept收到該客戶端發來的鏈接請求時會生成一個文件描述符fd7,而後會調用read函數來讀取fd7文件描述符發送過來的消息,若是此時沒有消息發送過來,read函數將一直阻塞直到有消息發送過來。
    1. 若是讀取fd7的進程一直在阻塞當中的時候,有一個客戶端來鏈接redis-server,那麼該客戶端將沒法鏈接,由於原先的進程還在阻塞當中,須要等待處理完fd7這個客戶端的請求(內核將接收到的fd7的數據從內核空間拷貝到用戶空間)或者超時後,另外一個客戶端才能鏈接進來 以上就是整個BIO的阻塞過程。

BIO的缺點顯而易見了,一個進程只能去處理一個fd,若是有多個客戶端鏈接,就會出現堵塞住,固然,能夠啓動多個redis-server進程或者線程來處理客戶端鏈接,一旦請求量上來後,這樣會浪費不少的系統資源。建立線程也須要系統調用設計

NIO(NoneBlocking IO)同步非阻塞IO

能夠看出,NIO和BIO由圖中得出的最大不一樣就是在read讀取上,BIO是redis-server調用read,等待讀取消息,可是沒有消息到達時,他會一直阻塞在那,直到有消息進來,他纔讀取後從內核態拷貝至用戶態纔算完事兒;而NIO是不斷循環調用read來讀取鏈接至redis-server的fd,若是有消息那就處理,須要將數據從內核空間拷貝至用戶空間,因此這裏會阻塞進程,若是沒消息則不會阻塞,而是當即返回無數據的結果。

循環不斷地調用read去檢測數據是否準備好,是十分浪費系統資源的,若是1000個客戶端鏈接中只有1個哭護短髮送了消息,也就是白白浪費了999個系統調用read,並且每一次調用read,都要讓系統內核去判斷一次,加大了系統內核的壓力。3d

Select 多路複用

select模式不像NIO模式,循環系統調用read來檢測每個fd文件描述符,而是將全部鏈接進來的文件描述符都丟給內核,也就是調用 select系統調用函數,將全部鏈接過來的文件描述符一塊發送給內核,此時select進入阻塞狀態,而內核來監視全部的文件描述符是否有新消息到達,若是有文件描述符活躍,則select返回,而後redis-server再調用 read函數將消息從內核態拷貝到用戶態。

select模式解決了NIO模式的循環調用問題,可是他把監視的循環丟給了內核來處理,這也是個問題,CPU就會被佔用去主動遍歷全部fdcode

Epoll 多路複用

Epoll模型是調用epoll_create來在內核中建立一個空間,而後再使用epoll_ctl來將鏈接過來的客戶端文件描述符加到這個空間中,來一個加一個,若是有客戶端發送消息,那麼經過事件驅動,正在處理其餘操做的cpu會收到一個軟中斷,來將有消息的fd移到內核中的另外一塊空間中,而redis-server則不斷調用epoll_wait來循環遍歷有消息的這塊內存,若是該內存中有fd,則調用read來讀取數據。

Epoll的好處就是充分利用了cpu,不須要cpu主動去遍歷全部客戶端鏈接

相關文章
相關標籤/搜索