- 阻塞 io 模型 blocking IO
- 非阻塞 io 模型 nonblocking IO
- io多路複用模型 IO multiplexing
- 細談 io 多路複用技術 select 和poll
- 細談事件驅動--epoll
- 總結
操做系統在處理io的時候,主要有兩個階段:linux
咱們通常將上述過程簡化理解爲:服務器
而根據這兩個階段而不一樣的操做方法,就會產生多種io模型,本文只討論select,poll,epoll,因此只引出三種io模型。網絡
最經常使用的也就是阻塞io模型。默認狀況下,全部文件操做都是阻塞的。咱們以套接字接口爲例來說解此模型,在進程空間調用recvfrom,其系統調用知道數據包到達而且被複制到進程緩衝中或者發生錯誤時纔會返回,在此期間會一直阻塞,因此進程在調用recvfrom開始到它返回的整段時間都是阻塞的,所以稱之爲阻塞io模型。多線程
注意:在阻塞狀態下,程序是不會浪費CPU的,cpu只是不執行io操做了,還會去作別的。socket
應用層有數據過來,會調用recvfrom方法,可是這個時候應用層的數據還沒複製到kernel中,將應用層數據複製到kerne這個階段是須要時間的,因此recvfrom方法會阻塞,當內核中的數據準備好以後,recvfrom方法還不會返回,而是會發起一個系統調用將kernel中的數據複製到進程的緩衝區中,也就是user space,當這個工做完成以後,recvfrom纔會返回並解除程序的阻塞。tcp
因此咱們總結能夠發現,主要就是上面兩個階段函數
阻塞io模型就是將這個兩個過程合併在一塊兒,一塊兒阻塞。 而非阻塞模型則是將第一個過程的阻塞變成非阻塞,第二個階段是系統調用,是必須阻塞的,因此非阻塞模型也是同步的,由於它們在kernel裏的數據準備好以後,進行系統調用,將數據拷貝到進程緩衝區中。性能
就是對於第一個階段,也就是應用層數據到kernel的過程當中,recvfrom會輪詢檢查,若是kernel數據沒有準備還,就返回一個EWOULDBLOCK錯誤。不斷的輪詢檢查,直到發現kernel中的數據準備好了,就返回,而後進行系統調用,將數據從kernel拷貝到進程緩衝區中。有點類似busy-waiting的方法。spa
目的:由於阻塞模型在沒有收到數據的時候就會阻塞卡住,若是一次須要接受多個socket fd的時候,就會致使必須處理完前面的fd,才能處理後面的fd,即便可能後面的fd比前面的fd還要先準備好,因此這樣就會形成客戶端的嚴重延遲。爲了處理多個請求,咱們天然先想到用多線程來處理多個socket fd,可是這樣又會啓動大量的線程,形成資源的浪費,因此這個時候就出現了io多路複用技術。就是用一個進程來處理多個fd的請求。操作系統
應用:適用於針對大量的io請求的狀況,對於服務器必須在同時處理來自客戶端的大量的io操做的時候,就很是適合
select的工做流程: 單個進程就能夠同時處理多個網絡鏈接的io請求(同時阻塞多個io操做)。基本原理就是程序呼叫select,而後整個程序就阻塞了,這時候,kernel就會輪詢檢查全部select負責的fd,當找到一個client中的數據準備好了,select就會返回,這個時候程序就會系統調用,將數據從kernel複製到進程緩衝區。
下圖爲select同時從多個客戶端接受數據的過程
雖然服務器進程會被select阻塞,可是select會利用內核不斷輪詢監聽其餘客戶端的io操做是否完成。
poll的原理與select很是類似,差異以下:
根據fd_size的定義,它的大小爲32個整數大小(32位機器爲32*32,全部共有1024bits能夠記錄fd),每一個fd一個bit,因此最大隻能同時處理1024個fd
每次要判斷【有哪些event發生】這件事的成本很高,由於select(polling也是)採起主動輪詢機制
1.每一次呼叫 select( ) 都須要先從 user space把 FD_SET複製到 kernel(約線性時間成本) 爲何 select 不能像epoll同樣,只作一次複製就好呢? 每一次呼叫 select()前,FD_SET均可能更動,而 epoll 提供了共享記憶存儲結構,因此不須要有 kernel 與 user之間的數據溝通
2.而後kernel還要輪詢每一個fd,約線性時間
對於select和poll的上述缺點,就引進了一種新的技術,epoll技術
epoll 提供了三個函數:
int epoll_create(int size); 創建一個 epoll 對象,並傳回它的id
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); 事件註冊函數,將須要監聽的事件和須要監聽的fd交給epoll對象
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout); 等待註冊的事件被觸發或者timeout發生
epoll解決的問題:
epoll沒有fd數量限制 epoll沒有這個限制,咱們知道每一個epoll監聽一個fd,因此最大數量與能打開的fd數量有關,一個g的內存的機器上,能打開10萬個左右
epoll不須要每次都從user space 將fd set複製到內核kernel epoll在用epoll_ctl函數進行事件註冊的時候,已經將fd複製到內核中,因此不須要每次都從新複製一次
select 和 poll 都是主動輪詢機制,須要拜訪每一個 FD; epoll是被動觸發方式,給fd註冊了相應事件的時候,咱們爲每個fd指定了一個回調函數,當數據準備好以後,就會把就緒的fd加入一個就緒的隊列中,epoll_wait的工做方式實際上就是在這個就緒隊列中查看有沒有就緒的fd,若是有,就喚醒就緒隊列上的等待者,而後調用回調函數。
雖然epoll。poll。epoll都須要查看是否有fd就緒,可是epoll之因此是被動觸發,就在於它只要去查找就緒隊列中有沒有fd,就緒的fd是主動加到隊列中,epoll不須要一個個輪詢確認。 換一句話講,就是select和poll只能通知有fd已經就緒了,但不能知道到底是哪一個fd就緒,因此select和poll就要去主動輪詢一遍找到就緒的fd。而epoll則是不但能夠知道有fd能夠就緒,並且還具體能夠知道就緒fd的編號,因此直接找到就能夠,不用輪詢。