Nginx系列(5):Web服務器分析之Linux I/O(理論中)

目錄

一、Linux I/O 模型

1.I/O模型分類

2.I/O模型的相關術語

二、Linux I/O 模型具體說明

1.阻塞I/O

2.非阻塞I/O

3.I/O複用(select和poll)

4.信號驅動I/O(SIGIO)

5.異步I/O(Posix.1的aio_系列函數)

6.I/O 模型總結(如下圖)

三、Linux I/O模型的具體實現

1.主要實現方式有以下幾種:

2.爲什麼epoll、kqueue、/dev/poll比select高級?

3.Windows or *nix (IOCP or kqueue、epoll、/dev/poll)?

4.總結一些重點


一、Linux I/O 模型

1.I/O模型分類

說明:我們都知道web服務器的進程響應用戶請求,但無法直接操作I/O設備,其必須通過系統調用,請求kernel來協助完成I/O動作,內核會爲每個I/O設備維護一個buffer,如下圖:

對於數據輸入而言,即等待(wait)數據輸入至buffer需要時間,而從buffer複製(copy)數據至進程也需要時間。

根據等待模式不同,I/O動作可分爲五種模式。

  • 阻塞I/O
  • 非阻塞I/O
  • I/O複用(select和poll)
  • 信號(事件)驅動I/O(SIGIO)
  • 異步I/O(Posix.1的aio_系列函數)

2.I/O模型的相關術語

這裏有必要先解釋一下阻塞、非阻塞,同步、異步、I/O概念。

(1).阻塞和非阻塞

阻塞和非阻塞指的是執行一個操作是等操作結束再返回,還是馬上返回。比如你去車站接朋友,這是一個操作。可以有兩種執行方式。第一種,你這人特實誠,老早就到了車站一直等到車來了接到朋友爲止。第二種,你到了車站,問值班的那趟車來了沒有,“還沒有”,你出去逛一圈,可能過會回來再問。第一種就是阻塞方式,第二種則是非阻塞的。我認爲阻塞和非阻塞講得是做事方法,是針對做事的人而言的。

(2).同步和異步

       同步和異步又是另外一個概念,它是事件本身的一個屬性。比如老闆讓你去搬一堆石頭,而且只讓你一個人幹,你只好自己上陣,最後的結果是搬完了,還是你砸到腳了,只有搬完了你才知道。這就是同步的事件。如果老闆還給你個小弟,你就可以讓小弟去搬,搬完了告你一聲。這就變成異步的了。其實異步還可以分爲兩種:帶通知的和不帶通知的。前面說的那種屬於帶通知的。有些小弟幹活可能主動性不是很夠,不會主動通知你,你就需要時不時的去關注一下狀態。這種就是不帶通知的異步。

       對於同步的事件,你只能以阻塞的方式去做。而對於異步的事件,阻塞和非阻塞都是可以的。非阻塞又有兩種方式:主動查詢和被動接收消息。被動不意味着一定不好,在這裏它恰恰是效率更高的,因爲在主動查詢裏絕大部分的查詢是在做無用功。對於帶通知的異步事件,兩者皆可。而對於不帶通知的,則只能用主動查詢。

(3).I/O

       回到I/O,不管是I還是O,對外設(磁盤)的訪問都可以分成請求和執行兩個階段。請求就是看外設的狀態信息(比如是否準備好了),執行纔是真正的I/O操作。在Linux 2.6之前,只有“請求”是異步事件,2.6之後才引入AIO把“執行”異步化。別看Linux/Unix是用來做服務器的,這點上比Windows落後了好多,IOCP(Windows上的AIO)在Win2000上就有了,呵呵。

(4).總結

       Linux上的前四種I/O模型的“執行”階段都是同步的只有最後一種才做到了真正的全異步第一種阻塞式是最原始的方法,也是最累的辦法。當然累與不累要看針對誰。應用程序是和內核打交道的。對應用程序來說,這種方式是最累的,但對內核來說這種方式恰恰是最省事的。還拿接人這事爲例,你就是應用程序,值班員就是內核,如果你去了一直等着,值班員就省事了。當然現在計算機的設計,包括操作系統,越來越爲終端用戶考慮了,爲了讓用戶滿意,內核慢慢的承擔起越來越多的工作,IO模型的演化也是如此。

       非阻塞I/O ,I/O複用,信號驅動式I/O其實都是非阻塞的,當然是針對“請求”這個階段。非阻塞式是主動查詢外設狀態。I/O複用裏的select,poll也是主動查詢,不同的是select和poll可以同時查詢多個fd(文件句柄)的狀態,另外select有fd個數的限制。epoll是基於回調函數的。信號驅動式I/O則是基於信號消息的。這兩個應該可以歸到“被動接收消息”那一類中。最後就是偉大的AIO的出現,內核把什麼事都幹了,對上層應用實現了全異步,性能最好,當然複雜度也最高。好了,下面我們就來詳細說一說,這幾種模式。


二、Linux I/O 模型具體說明

首先,我們先來看一下,基本 Linux I/O 模型的簡單矩陣

從圖中我們可以看到的模型有,同步阻塞I/O(阻塞I/O)、同步非阻塞I/O(非阻塞I/O )、異步阻塞I/O(I/O複用),異步非阻塞I/O(有兩種,信號驅動I/O和異步I/O)。好了現在就來具體說一說吧。

1.阻塞I/O

說明:應用程序調用一個IO函數,導致應用程序阻塞,等待數據準備好。 如果數據沒有準備好,一直等待數據準備好了,從內核拷貝到用戶空間,IO函數返回成功指示。這個不用多解釋吧,阻塞套接字。

下圖是它調用過程的圖示:(注,一般網絡I/O都是阻塞I/O,客戶端發出請求,Web服務器進程響應,在進程沒有返回頁面之前,這個請求會處於一直等待狀態)

2.非阻塞I/O

我們把一個套接口設置爲非阻塞就是告訴內核,當所請求的I/O操作無法完成時,不要將進程睡眠,而是返回一個錯誤。這樣我們的I/O操作函數將不斷的測試數據是否已經準備好,如果沒有準備好,繼續測試,直到數據準備好爲止。在這個不斷測試的過程中,會大量的佔用CPU的時間,所有一般Web服務器都不使用這種I/O模型。具體過程如下圖:

3.I/O複用(select和poll)

I/O複用模型會用到select或poll函數或epoll函數(Linux2.6以後的內核開始支持),這兩個函數也會使進程阻塞,但是和阻塞I/O所不同的的,這兩個函數可以同時阻塞多個I/O操作。而且可以同時對多個讀操作,多個寫操作的I/O函數進行檢測,直到有數據可讀或可寫時,才真正調用I/O操作函數。具體過程如下圖:

4.信號驅動I/O(SIGIO)

首先,我們允許套接口進行信號驅動I/O,並安裝一個信號處理函數,進程繼續運行並不阻塞。當數據準備好時,進程會收到一個SIGIO信號,可以在信號處理函數中調用I/O操作函數處理數據。具體過程如下圖:

5.異步I/O(Posix.1的aio_系列函數)

當一個異步過程調用發出後,調用者不能立刻得到結果。實際處理這個調用的部件在完成後,通過狀態、通知和回調來通知調用者的輸入輸出操作。具體過程如下圖:

6.I/O 模型總結(如下圖)

從上圖中我們可以看出,可以看出,越往後,阻塞越少,理論上效率也是最優。其五種I/O模型中,前三種屬於同步I/O,後兩者屬於異步I/O。

同步I/O:

  • 阻塞I/O
  • 非阻塞I/O
  • I/O複用(select和poll)      

異步I/O:

  • 信號驅動I/O(SIGIO) (半異步)
  • 異步I/O(Posix.1的aio_系列函數)(真正的異步)

異步 I/O 和 信號驅動I/O的區別:

  • 信號驅動 I/O 模式下,內核可以複製的時候通知給我們的應用程序發送SIGIO 消息。
  • 異步 I/O 模式下,內核在所有的操作都已經被內核操作結束之後纔會通知我們的應用程序。

OK,5種模型的比較比較清晰了,下面我們來說一下五種模型的具體實現。


三、Linux I/O模型的具體實現

1.主要實現方式有以下幾種:

  • select
  • poll
  • epoll
  • kqueue
  • /dev/poll
  • iocp

注:其中iocp是Windows實現的,select、poll、epoll是Linux實現的,kqueue是FreeBSD實現的,/dev/poll是SUN的Solaris實現的。select、poll對應第3種(I/O複用)模型,iocp對應第5種(異步I/O)模型,那麼epoll、kqueue、/dev/poll呢?其實也同select屬於同一種模型,只是更高級一些,可以看作有了第4種(信號驅動I/O)模型的某些特性,如callback機制。

2.爲什麼epoll、kqueue、/dev/poll比select高級?

       答案是,它們無輪詢。因爲他們用callback取代了。想想看,當套接字比較多的時候,每次select()都要通過遍歷FD_SETSIZE個Socket來完成調度,不管哪個Socket是活躍的,都遍歷一遍。這會浪費很多CPU時間。如果能給套接字註冊某個回調函數,當他們活躍時,自動完成相關操作,那就避免了輪詢,這正是epoll、kqueue、/dev/poll做的。

這樣子說可能不好理解,那麼我說一個現實中的例子,假設你在大學讀書,住的宿舍樓有很多間房間,你的朋友要來找你。select版宿管大媽就會帶着你的朋友挨個房間去找,直到找到你爲止。而epoll版宿管大媽會先記下每位同學的房間號,你的朋友來時,只需告訴你的朋友你住在哪個房間即可,不用親自帶着你的朋友滿大樓找人。如果來了10000個人,都要找自己住這棟樓的同學時,select版和epoll版宿管大媽,誰的效率更高,不言自明。同理,在高併發服務器中,輪詢I/O是最耗時間的操作之一,select、epoll、/dev/poll的性能誰的性能更高,同樣十分明瞭。

3.Windows or *nix (IOCP or kqueue、epoll、/dev/poll)?

       誠然,Windows的IOCP非常出色,目前很少有支持asynchronous I/O的系統,但是由於其系統本身的侷限性,大型服務器還是在UNIX下。而且正如上面所述,kqueue、epoll、/dev/poll 與 IOCP相比,就是多了一層從內核copy數據到應用層的阻塞,從而不能算作asynchronous I/O類。但是,這層小小的阻塞無足輕重,kqueue、epoll、/dev/poll 已經做得很優秀了。

4.總結一些重點

  • 只有IOCP(windows實現)是asynchronous I/O,其他機制或多或少都會有一點阻塞。
  • select(Linux實現)低效是因爲每次它都需要輪詢。但低效也是相對的,視情況而定,也可通過良好的設計改善
  • epoll(Linux實現)、kqueue(FreeBSD實現)、/dev/poll(Solaris實現)是Reacor模式,IOCP是Proactor模式。
  • Apache 2.2.9之前只支持select模型,2.2.9之後支持epoll模型
  • Nginx 支持epoll模型
  • Java nio包是select模型

願你就像早晨八九點鐘的太陽,活力十足,永遠年輕。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章