首先來看一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
有了上述的基本鋪墊後,下面來解析下各網絡IO模型的結構,這裏咱們以redis服務爲例。線程
socket
來在內核空間建立一個文件描述符6 - fd6(僅有一個fd6但尚未實際內存地址),接着使用系同調用bind
函數爲該文件描述符綁定一個內存地址,而後再使用系統調用函數listen
來監聽fd6,最後使用accept
來接收鏈接至fd6的鏈接。accept
收到該客戶端發來的鏈接請求時會生成一個文件描述符fd7,而後會調用read
函數來讀取fd7文件描述符發送過來的消息,若是此時沒有消息發送過來,read
函數將一直阻塞直到有消息發送過來。BIO的缺點顯而易見了,一個進程只能去處理一個fd,若是有多個客戶端鏈接,就會出現堵塞住,固然,能夠啓動多個redis-server進程或者線程來處理客戶端鏈接,一旦請求量上來後,這樣會浪費不少的系統資源。建立線程也須要系統調用設計
循環不斷地調用read去檢測數據是否準備好,是十分浪費系統資源的,若是1000個客戶端鏈接中只有1個哭護短髮送了消息,也就是白白浪費了999個系統調用read,並且每一次調用read,都要讓系統內核去判斷一次,加大了系統內核的壓力。3d
select
系統調用函數,將全部鏈接過來的文件描述符一塊發送給內核,此時select進入阻塞狀態,而內核來監視全部的文件描述符是否有新消息到達,若是有文件描述符活躍,則select返回,而後redis-server再調用
read
函數將消息從內核態拷貝到用戶態。
select模式解決了NIO模式的循環調用問題,可是他把監視的循環丟給了內核來處理,這也是個問題,CPU就會被佔用去主動遍歷全部fdcode
Epoll的好處就是充分利用了cpu,不須要cpu主動去遍歷全部客戶端鏈接