五種IO模型分析

来源:http://www.cnblogs.com/f-ck-need-u/archive/2017/10/03/7624733.html
-Advertisement-
Play Games

本文目錄:1. 基礎2. I/O模型2.1 Blocking I/O模型2.2 Non-Blocking I/O模型2.3 I/O Multiplexing模型2.4 Signal-driven I/O模型2.5 Asynchronous I/O模型2.6 同步IO和非同步IO、阻塞和非阻塞的區分3. ...


本文目錄:
1. 基礎
2. I/O模型
2.1 Blocking I/O模型
2.2 Non-Blocking I/O模型
2.3 I/O Multiplexing模型
2.4 Signal-driven I/O模型
2.5 Asynchronous I/O模型
2.6 同步IO和非同步IO、阻塞和非阻塞的區分
3. select()、poll()和epoll
3.1 select() & poll()
3.2 epoll

1. 基礎

在引入IO模型前,先對io等待時某一段數據的"經歷"做一番解釋。如圖:

當某個程式或已存在的進程/線程(後文將不加區分的只認為是進程)需要某段數據時,它只能在用戶空間中屬於它自己的記憶體中訪問、修改,這段記憶體暫且稱之為app buffer。假設需要的數據在磁碟上,那麼進程首先得發起相關係統調用,通知內核去載入磁碟上的文件。但正常情況下,數據只能載入到內核的緩衝區,暫且稱之為kernel buffer。數據載入到kernel buffer之後,還需將數據複製到app buffer。到了這裡,進程就可以對數據進行訪問、修改了。

現在有幾個需要說明的問題。

(1).為什麼不能直接將數據載入到app buffer呢

實際上是可以的,有些程式或者硬體為了提高效率和性能,可以實現內核旁路的功能,避過內核的參與,直接在存儲設備和app buffer之間進行數據傳輸,例如RDMA技術就需要實現這樣的內核旁路功能。

但是,最普通也是絕大多數的情況下,為了安全和穩定性,數據必須先拷入內核空間的kernel buffer,再複製到app buffer,以防止進程串進內核空間進行破壞。

(2).上面提到的數據幾次拷貝過程,拷貝方式是一樣的嗎

不一樣。現在的存儲設備(包括網卡)基本上都支持DMA操作。什麼是DMA(direct memory access,直接記憶體訪問)?簡單地說,就是記憶體和設備之間的數據交互可以直接傳輸,不再需要電腦的CPU參與,而是通過硬體上的晶元(可以簡單地認為是一個小cpu)進行控制。

假設,存儲設備不支持DMA,那麼數據在記憶體和存儲設備之間的傳輸,必須通過電腦的CPU計算從哪個地址中獲取數據、拷入到對方的哪些地址、拷入多少數據(多少個數據塊、數據塊在哪裡)等等,僅僅完成一次數據傳輸,CPU都要做很多事情。而DMA就釋放了電腦的CPU,讓它可以去處理其他任務。

再說kernel buffer和app buffer之間的複製方式,這是兩段記憶體空間的數據傳輸,只能由CPU來控制。

所以,在載入硬碟數據到kernel buffer的過程是DMA拷貝方式,而從kernel buffer到app buffer的過程是CPU參與的拷貝方式。

(3).如果數據要通過TCP連接傳輸出去要怎麼辦

例如,web服務對客戶端的響應數據,需要通過TCP連接傳輸給客戶端。

TCP/IP協議棧維護著兩個緩衝區:send buffer和recv buffer,它們合稱為socket buffer。需要通過TCP連接傳輸出去的數據,需要先複製到send buffer,再複製給網卡通過網路傳輸出去。如果通過TCP連接接收到數據,數據首先通過網卡進入recv buffer,再被覆制到用戶空間的app buffer。

同樣,在數據複製到send buffer或從recv buffer複製到app buffer時,是CPU參與的拷貝。從send buffer複製到網卡或從網卡複製到recv buffer時,是DMA操作方式的拷貝。

如下圖所示,是通過TCP連接傳輸數據時的過程。

(4).網路數據一定要從kernel buffer複製到app buffer再複製到send buffer嗎

不是。如果進程不需要修改數據,就直接發送給TCP連接的另一端,可以不用從kernel buffer複製到app buffer,而是直接複製到send buffer。這就是零複製技術。

例如httpd不需要訪問和修改任何信息時,將數據原原本本地複製到app buffer再原原本本地複製到send buffer然後傳輸出去,但實際上複製到app buffer的過程是可以省略的。使用零複製技術,就可以減少一次拷貝過程,提升效率。

當然,實現零複製技術的方法有多種,見我的另一篇結束零複製的文章:零複製(zero copy)技術

以下是以httpd進程處理文件類請求時比較完整的數據操作流程。

大致解釋下:客戶端發起對某個文件的請求,通過TCP連接,請求數據進入TCP 的recv buffer,再通過recv()函數將數據讀入到app buffer,此時httpd工作進程對數據進行一番解析,知道請求的是某個文件,於是發起某個系統調用(例如要讀取這個文件,發起read()),於是內核載入該文件,數據從磁碟複製到kernel buffer再複製到app buffer,此時httpd就要開始構建響應數據了,可能會對數據進行一番修改,例如在響應首部中加一個欄位,最後將修改或未修改的數據複製(例如send()函數)到send buffer中,再通過TCP連接傳輸給客戶端。

2. I/O模型

所謂的IO模型,描述的是出現I/O等待時進程的狀態以及處理數據的方式。圍繞著進程的狀態、數據準備到kernel buffer再到app buffer的兩個階段展開。其中數據複製到kernel buffer的過程稱為數據準備階段,數據從kernel buffer複製到app buffer的過程稱為數據複製階段。請記住這兩個概念,後面描述I/O模型時會一直用這兩個概念。

本文以httpd進程的TCP連接方式處理本地文件為例,請無視httpd是否真的實現瞭如此、那般的功能,也請無視TCP連接處理數據的細節,這裡僅僅只是作為方便解釋的示例而已。另外,本文用本地文件作為I/O模型的對象不是很適合,它的重頭戲是在套接字上,如果想要看處理TCP/UDP過程中套接字的I/O模型,請看完此文後,再結合我的另一篇文章"不可不知的socket和TCP連接過程"以重新認識I/O模型。

再次說明,從硬體設備到記憶體的數據傳輸過程是不需要CPU參與的,而記憶體間傳輸數據是需要CPU參與的。

2.1 Blocking I/O模型

如圖:

假設客戶端發起index.html的文件請求,httpd需要將index.html的數據從磁碟中載入到自己的httpd app buffer中,然後複製到send buffer中發送出去。

但是在httpd想要載入index.html時,它首先檢查自己的app buffer中是否有index.html對應的數據,沒有就發起系統調用讓內核去載入數據,例如read(),內核會先檢查自己的kernel buffer中是否有index.html對應的數據,如果沒有,則從磁碟中載入,然後將數據準備到kernel buffer,再複製到app buffer中,最後被httpd進程處理。

如果使用Blocking I/O模型:

(1).當設置為blocking i/o模型,httpd從都是被阻塞的。
(2).只有當數據複製到app buffer完成後,或者發生了錯誤,httpd才被喚醒處理它app buffer中的數據。
(3).cpu會經過兩次上下文切換:用戶空間到內核空間再到用戶空間。
(4).由於階段的拷貝是不需要CPU參與的,所以在階段準備數據的過程中,cpu可以去處理其它進程的任務。
(5).階段的數據複製需要CPU參與,將httpd阻塞,在某種程度上來說,有助於提升它的拷貝速度。
(6).這是最省事、最簡單的IO模式。

如下圖:

2.1 Non-Blocking I/O模型

(1).當設置為non-blocking時,httpd第一次發起系統調用(如read())後,立即返回一個錯誤值EWOULDBLOCK(至於read()讀取一個普通文件時能否返回EWOULDBLOCK請無視,畢竟I/O模型主要是針對套接字文件的,就當read()是recv()好了),而不是讓httpd進入睡眠狀態。UNP中也正是這麼描述的。

When we set a socket to be nonblocking, we are telling the kernel "when an I/O operation that I request cannot be completed without putting the process to sleep, do not put the process to sleep, but return an error instead.

(2).雖然read()立即返回了,但httpd還要不斷地去發送read()檢查內核:數據是否已經成功拷貝到kernel buffer了?這稱為輪詢(polling)。每次輪詢時,只要內核沒有把數據準備好,read()就返回錯誤信息EWOULDBLOCK。
(3).直到kernel buffer中數據準備完成,再去輪詢時不再返回EWOULDBLOCK,而是將httpd阻塞,以等待數據複製到app buffer。
(4).httpd在階段不被阻塞,但是會不斷去發送read()輪詢。在被阻塞,將cpu交給內核把數據copy到app buffer。

如下圖:

2.3 I/O Multiplexing模型

稱為多路IO模型或IO復用,意思是可以檢查多個IO等待的狀態。有三種IO復用模型:select、poll和epoll。其實它們都是一種函數,用於監控指定文件描述符的數據是否就緒,就緒指的是對某個系統調用不再阻塞了,例如對於read()來說,就是數據準備好了就是就緒狀態。就緒種類包括是否可讀、是否可寫以及是否異常,其中可讀條件中就包括了數據是否準備好。當就緒之後,將通知進程,進程再發送對數據操作的系統調用,如read()。所以,這三個函數僅僅只是處理了數據是否準備好以及如何通知進程的問題。可以將這幾個函數結合阻塞和非阻塞IO模式使用,例如設置為非阻塞時,select()/poll()/epoll將不會阻塞在對應的描述符上,調用函數的進程/線程也就不會被阻塞。

select()和poll()差不多,它們的監控和通知手段是一樣的,只不過poll()要更聰明一點,所以此處僅以select()監控單個文件請求為例簡單介紹IO復用,至於更具體的、監控多個文件以及epoll的方式,在本文的最後專門解釋。

(1).當想要載入某個文件時,假如httpd要發起read()系統調用,如果是阻塞或者非阻塞情形,那麼read()會根據數據是否準備好而決定是否返回,是否可以主動去監控這個數據是否準備到了kernel buffer中呢,亦或者是否可以監控send buffer中是否有新數據進入呢?這就是select()/poll()/epoll的作用。
(2).當使用select()時,httpd發起一個select調用,然後httpd進程被select()"阻塞"。由於此處假設只監控了一個請求文件,所以select()會在數據準備到kernel buffer中時直接喚醒httpd進程。之所以阻塞要加上雙引號,是因為select()有時間間隔選項可用控制阻塞時長,如果該選項設置為0,則select不阻塞,此時表示立即返回但一直輪詢檢查是否就緒,還可以設置為永久阻塞。
(3).當select()的監控對象就緒時,將通知(輪詢情況)或喚醒(阻塞情況)httpd進程,httpd再發起read()系統調用,此時數據會從kernel buffer複製到app buffer中並read()成功。
(4).httpd發起第二個系統調用(即read())後被阻塞,CPU全部交給內核用來複制數據到app buffer。 (5).對於httpd只處理一個連接的情況下,IO復用模型還不如blocking I/O模型,因為它前後發起了兩個系統調用(即select()和read()),甚至在輪詢的情況下會不斷消耗CPU。但是IO復用的優勢就在於能同時監控多個文件描述符。

如圖:

更詳細的說明,見本文末。

2.4 Signal-driven I/O模型

即信號驅動IO模型。當開啟了信號驅動功能時,首先發起一個信號處理的系統調用,如sigaction(),這個系統調用會立即返回。但數據在準備好時,會發送SIGIO信號,進程收到這個信號就知道數據準備好了,於是發起操作數據的系統調用,如read()。

在發起信號處理的系統調用後,進程不會被阻塞,但是在read()將數據從kernel buffer複製到app buffer時,進程是被阻塞的。如圖:

2.5 Asynchronous I/O模型

即非同步IO模型。當設置為非同步IO模型時,httpd首先發起非同步系統調用(如aio_read(),aio_write()等),並立即返回。這個非同步系統調用告訴內核,不僅要準備好數據,還要把數據複製到app buffer中。

httpd從返回開始,直到數據複製到app buffer結束都不會被阻塞。當數據複製到app buffer結束,將發送一個信號通知httpd進程。

如圖:

看上去非同步很好,但是註意,在複製kernel buffer數據到app buffer中時是需要CPU參與的,這意味著不受阻的httpd會和非同步調用函數爭用CPU。如果併發量比較大,httpd接入的連接數可能就越多,CPU爭用情況就越嚴重,非同步函數返回成功信號的速度就越慢。如果不能很好地處理這個問題,非同步IO模型也不一定就好。

2.6 同步IO和非同步IO、阻塞和非阻塞的區分

阻塞、非阻塞、IO復用、信號驅動都是同步IO模型。因為在發起操作數據的系統調用(如本文的read())過程中是被阻塞的。這裡要註意,雖然在載入數據到kernel buffer的數據準備過程中可能阻塞、可能不阻塞,但kernel buffer才是read()函數的操作對象,同步的意思是讓kernel buffer和app buffer數據同步。顯然,在保持kernel buffer和app buffer同步的過程中,進程必須被阻塞,否則read()就變成非同步的read()。

只有非同步IO模型才是非同步的,因為發起的非同步類的系統調用(如aio_read())已經不管kernel buffer何時準備好數據了,就像後臺一樣read一樣,aio_read()可以一直等待kernel buffer中的數據,在準備好了之後,aio_read()自然就可以將其複製到app buffer。

如圖:

3 select()、poll()和epoll

前面說了,這三個函數是文件描述符狀態監控的函數,它們可以監控一系列文件的一系列事件,當出現滿足條件的事件後,就認為是就緒或者錯誤。事件大致分為3類:可讀事件、可寫事件和異常事件。它們通常都放在迴圈結構中進行迴圈監控。

select()和poll()函數處理方式的本質類似,只不過poll()稍微先進一點,而epoll處理方式就比這兩個函數先進多了。當然,就算是先進分子,在某些情況下性能也不一定就比老家伙們強。

3.1 select() & poll()

首先,通過FD_SET巨集函數創建待監控的描述符集合,並將此描述符集合作為select()函數的參數,可以在指定select()函數阻塞時間間隔,於是select()就創建了一個監控對象。

除了普通文件描述符,還可以監控套接字,因為套接字也是文件,所以select()也可以監控套接字文件描述符,例如recv buffer中是否收到了數據,也即監控套接字的可讀性,send buffer中是否滿了,也即監控套接字的可寫性。select()預設最大可監控1024個文件描述符。而poll()則沒有此限制。

select()的時間間隔參數分3種:
(1).設置為指定時間間隔內阻塞,除非之前有就緒事件發生。
(2).設置為永久阻塞,除非有就緒事件發生。
(3).設置為完全不阻塞,即立即返回。但因為select()通常在迴圈結構中,所以這是輪詢監控的方式。

當創建了監控對象後,由內核監控這些描述符集合,於此同時調用select()的進程被阻塞(或輪詢)。當監控到滿足就緒條件時(監控事件發生),select()將被喚醒(或暫停輪詢),於是select()返回滿足就緒條件的描述符數量,之所以是數量而不僅僅是一個,是因為多個文件描述符可能在同一時間滿足就緒條件。由於只是返回數量,並沒有返回哪一個或哪幾個文件描述符,所以通常在使用select()之後,還會在迴圈結構中的if語句中使用巨集函數FD_ISSET進行遍歷,直到找出所有的滿足就緒條件的描述符。最後將描述符集合通過指定函數拷貝回用戶空間,以便被進程處理。

監聽描述符集合的大致過程如下圖所示,其中select()只是其中的一個環節:

大概描述下這個迴圈監控的過程:

(1).首先通過FD_ZERO巨集函數初始化描述符集合。圖中每個小方格表示一個文件描述符。
(2).通過FD_SET巨集函數創建描述符集合,此時集合中的文件描述符都被打開,也就是稍後要被select()監控的對象。
(3).使用select()函數監控描述符集合。當某個文件描述符滿足就緒條件時,select()函數返回集合中滿足條件的數量。圖中標黃色的小方塊表示滿足就緒條件的描述符。
(4).通過FD_ISSET巨集函數遍歷整個描述符集合,並將滿足就緒條件的描述符發送給進程。同時,使用FD_CLR巨集函數將滿足就緒條件的描述符從集合中移除。
(5).進入下一個迴圈,繼續使用FD_SET巨集函數向描述符集合中添加新的待監控描述符。然後重覆(3)、(4)兩個步驟。

如果使用簡單的偽代碼來描述:

FD_ZERO
for() {
    FD_SET()
    select()
    if(){
        FD_ISSET()
        FD_CLR()
    }
    writen()
}

以上所說只是一種需要迴圈監控的示例,具體如何做卻是不一定的。不過從中也能看出這一系列的流程。

3.2 epoll

epoll比poll()、select()先進,考慮以下幾點,自然能看出它的優勢所在:

(1).epoll_create()創建的epoll實例可以隨時通過epoll_ctl()來新增和刪除感興趣的文件描述符,不用再和select()每個迴圈後都要使用FD_SET更新描述符集合的數據結構。
(2).在epoll_create()創建epoll實例時,還創建了一個epoll就緒鏈表list。而epoll_ctl()每次向epoll實例添加描述符時,還會註冊該描述符的回調函數。當epoll實例中的描述符滿足就緒條件時將觸發回調函數,被移入到就緒鏈表list中。
(3).當調用epoll_wait()進行監控時,它只需確定就緒鏈表中是否有數據即可,如果有,將複製到用戶空間以被進程處理,如果沒有,它將被阻塞。當然,如果監控的對象設置為非阻塞模式,它將不會被阻塞,而是不斷地去檢查。

也就是說,epoll的處理方式中,根本就無需遍歷描述符集合。

回到Linux系列文章大綱:http://www.cnblogs.com/f-ck-need-u/p/7048359.html

回到網站架構系列文章大綱:http://www.cnblogs.com/f-ck-need-u/p/7576137.html

回到資料庫系列文章大綱:http://www.cnblogs.com/f-ck-need-u/p/7586194.html

轉載請註明出處:http://www.cnblogs.com/f-ck-need-u/p/7624733.html

註:若您覺得這篇文章還不錯請點擊右下角推薦,您的支持能激發作者更大的寫作熱情,非常感謝!


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • (一)通過命令行的方式 1.linux伺服器端設置 在linux主機上,安裝上傳下載工具包rz及sz; 如果不知道你要安裝包的具體名稱,可以使用yum provides */name 進行查找系統自帶軟體包的信息; 一般會列出軟體包的名稱及版本,還有安裝路徑;查詢到軟體包名後,使用yum insta ...
  • 1 》模塊介紹: 模塊,用一坨代碼實現了某個功能的代碼集合,類似於函數式編程和麵向過程編程,函數式編程則完成一個功能,其他代碼用來調用即可,提供了代碼的 重用性和代碼間的吻合,對於一個複雜的功能來說,可能需要多個函數才能完成(函數又可以在不同的.py文件中),n個.py文件組成的代碼集合就稱為模塊; ...
  • ping命令: ping是用來檢測網路是否通暢或者查詢網路連接速度的一個基礎命令。作為一名對電腦痴迷的愛好者來說,ping命令是需要第一個掌握的DOS命令。它所利用的原理是這樣的:網路上的機器都有唯一確定的IP地址,我們給目標IP地址發送一個數據包,對方就要返回一個同樣大小的數據包,根據返回的數據 ...
  • 本文目錄:1. prefork模式 1.1 概述 1.2 prefork工作機制 1.3 prefork相關指令2. worker模式 2.1 概述 2.2 worker工作機制3. event模式 3.1 概述 3.2 和worker工作模式的關係 3.3 event工作機制 3.3.1 非同步連接 ...
  • 一、YUM(Yellowdog Updater, Modified) 1. yum 簡介 RPM 軟體包形式管理軟體雖然方便,但是需要手動解決軟體包的依賴問題。很多時候安裝一個軟體首先需要安裝 1 個或多個(有時多達上百個)其他軟體,手動解決依賴問題很複雜。使用 YUM 可以解決這個問題。Linux ...
  • 1》迭代器原理及使用: 1>原理: 迭代器是訪問集合元素的一種方式,迭代器對象從集合的第一個元素開始訪問,直到所有的元素被訪問完結束;迭代器只能往前不會後退,不過這也沒什 麽,人們很少在迭代途中往後退,另外,迭代器的一大優點是不要求事先準備好整個迭代過程中所有的元素;迭代器僅僅在迭代到某個元素時才計 ...
  • 本文目錄:1. CGI是什麼2. 各種術語解釋3. web server和CGI的交互模式 3.1 CGI模式 3.2 模塊模式 3.3 php-fpm模式 1. CGI是什麼 CGI是common gateway interface的縮寫,大家都譯作通用網關介面,但很不幸,我們無法見名知意。 我們 ...
  • 懶蟲windows系列(一) 首先是快捷鍵,因為自己太懶了,覺得用滑鼠很麻煩,下麵總結一下自己最常用的快捷鍵(windows10 ) Ctrl+Shift+N:新建文件夾 F2:重命名 Ctrl + L:鎖屏 Art + F4:關閉當前視窗 Win+Prt Sc:屏幕截圖。截圖自動存放至“圖片”文件 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...