nginx IO模型

今天下班早些來普及下nginx io模型:linux

用戶空間與內核空間:nginx

    如今操做系統都是採用虛擬存儲器,那麼對32位操做系統而言,它的尋址空間(虛擬存儲空間)爲4G(2的32次方)。操做系統的核心是內核,獨立於普通的應用程序,能夠訪問受保護的內存空間,也有訪問底層硬件設備的全部權限。爲了保證用戶進程不能直接操做內核(kernel),保證內核的安全,操做系統將虛擬空間劃分爲兩部分,一部分爲內核空間,一部分爲用戶空間。針對linux操做系統而言,將最高的1G字節(從虛擬地址0xC0000000到0xFFFFFFFF),供內核使用,稱爲內核空間,而將較低的3G字節(從虛擬地址0x00000000到0xBFFFFFFF),供各個進程使用,稱爲用戶空間。編程

進程切換:瀏覽器

    爲了控制進程的執行,內核必須有能力掛起正在CPU上運行的進程,並恢復之前掛起的某個進程的執行。這種行爲被稱爲進程切換。所以能夠說,任何進程都是在操做系統內核的支持下運行的,是與內核緊密相關的。
從一個進程的運行轉到另外一個進程上運行,這個過程當中通過下面這些變化:
保存處理機上下文,包括程序計數器和其餘寄存器。
更新PCB信息。
把進程的PCB移入相應的隊列,如就緒、在某事件阻塞等隊列。
選擇另外一個進程執行,並更新其PCB。
更新內存管理的數據結構。
恢復處理機上下文。緩存

注:總而言之就是很耗資源,具體的能夠參考這篇文章:安全

http://guojing.me/linux-kernel-architecture/posts/process-switch/
bash

進程阻塞:服務器

    正在執行的進程,因爲期待的某些事件未發生,如請求系統資源失敗、等待某種操做的完成、新數據還沒有到達或無新工做作等,則由系統自動執行阻塞原語(Block),使本身由運行狀態變爲阻塞狀態。可見,進程的阻塞是進程自身的一種主動行爲,也所以只有處於運行態的進程(得到CPU),纔可能將其轉爲阻塞狀態。當進程進入阻塞狀態,是不佔用CPU資源的。網絡

文件描述符:數據結構

    文件描述符(File descriptor)是計算機科學中的一個術語,是一個用於表述指向文件的引用的抽象化概念。
文件描述符在形式上是一個非負整數。實際上,它是一個索引值,指向內核爲每個進程所維護的該進程打開文件的記錄表。當程序打開一個現有文件或者建立一個新文件時,內核向進程返回一個文件描述符。在程序設計中,一些涉及底層的程序編寫每每會圍繞着文件描述符展開。可是文件描述符這一律念每每只適用於UNIX、Linux這樣的操做系統。

緩存IO:

    緩存 IO 又被稱做標準 IO,大多數文件系統的默認 IO 操做都是緩存 IO。在 Linux 的緩存 IO 機制中,操做系統會將 IO 的數據緩存在文件系統的頁緩存( page cache )中,也就是說,數據會先被拷貝到操做系統內核的緩衝區中,而後纔會從操做系統內核的緩衝區拷貝到應用程序的地址空間。

緩存IO的缺點:

    數據在傳輸過程當中須要在應用程序地址空間和內核進行屢次數據拷貝操做,這些數據拷貝操做所帶來的 CPU 以及內存開銷是很是大的。

Linux IO模型:

    網絡IO的本質是socket的讀取,socket在linux系統中被抽象爲流,IO能夠理解爲對流的操做.對於一次IO訪問,數據會先被拷到操做系統內核的緩衝區中,而後纔會從操做系統內核的緩衝區拷貝到應用程序的地址空間,因此說當一個read操做發生時,它會經理兩個階段:

1
2
第一階段:等待數據準備 (Waiting  for  the data to be ready)。
第二階段:將數據從內核拷貝到進程中 (Copying the data from the kernel to the process)。

    對socket流而言:

1
2
第一步:一般涉及等待網絡上的數據分組到達,而後被複制到內核的某個緩衝區。
第二步:把數據從內核緩衝區複製到應用進程緩衝區。

網絡應用須要處理的無非就是兩大類問題,網絡IO,數據計算。相對於後者,網絡IO的延遲,給應用帶來的性能瓶頸大於後者。網絡IO的模型大體有以下幾種:

1
2
3
4
5
6
同步模型(synchronous IO)
    阻塞IO(bloking IO)
    非阻塞IO(non-blocking IO)
    多路複用IO(multiplexing IO)
    信號驅動式IO(signal-driven IO)
異步IO(asynchronous IO)

同步4個:阻塞IO、非阻塞IO、多路複用IO、信號驅使式IO

異步1個:異步IO

注:因爲信號驅動式(signal driven IO)在實際中並不經常使用,因此我這隻說起剩下的四種IO Model。

在深刻介紹Linux IO各類模型以前,讓咱們先來探索一下基本 Linux IO 模型的簡單矩陣。以下圖所示:

 

每一個 IO 模型都有本身的使用模式,它們對於特定的應用程序都有本身的優勢。

同步和異步主要針對C(client)端
同步:
所謂的同步,就是在c端發出一個功能調用時,在沒有獲得結果以前,該調用步返回,也就是說必須一件一件事作,等前一件事完了以後才作後一件事。
如:普通的B/S模式(同步):提交請求->等待服務器處理->處理完畢返回,這期間客戶端瀏覽器不能幹任何事

異步:
與同步相對。當C端一個異步過程調用發出以後,調用者不能當即獲得結果,實際處理這個調用的部件在完成後,經過狀態,通知和回調來通知調用者。
如:請求經過事件觸發->服務器處理(瀏覽器仍然能夠作其餘事情)->處理完畢

阻塞和非阻塞主要針對S端(server)

阻塞:
阻塞調用是指調用結果返回以前,當前線程會被掛起(線程進入非可執行狀態,在這個狀態,cpu不會分配時間片,線程暫停運行)函數只有獲得結果返回。
阻塞調用和同步調用的區別:對同步來講,不少時候當前線程仍是激活的,只是邏輯上沒有返回,如,在socket編程中調用recv函數,若是緩衝區沒有數據,這個函數就會一直等待,直到有數據返回。而此前當前線程還有可能繼續處理各類各樣的消息。

阻塞的例子:好比去取A樓一層(假設是內核緩衝區)取快遞,可是比不知道何時來,你有不能幹別的事情,只能死等着可是能夠睡覺(進程處於休眠狀態),由於你知道快遞把貨送來時必定會給比大電話

非阻塞:
非阻塞與阻塞概念想對應,指在不能當即獲得結果以前,該函數不會阻塞當前線程,而會當即返回。

非阻塞的例子:仍是等快遞,若是用輪詢的方式,每隔5分鐘去A樓一層(內核緩衝區)去看快遞來了沒,沒來,當即返回,若是快遞來了,就放到A樓一層,等你去取。

對象是否處於阻塞模式和函數是否是阻塞調用有很強的相關性,但不是一一對應的。阻塞對象上能夠有非阻塞的調用方式,咱們能夠經過輪詢狀態,在適當的時候調用阻塞函數,就能夠避免阻塞,而對於非阻塞對象,調用函數能夠進入阻塞調用,對於select:
1:同步 

1
我客戶端(C端調用者)一個功能,該功能沒有結束前,我死等結果。

2:異步

1
2
我(c端調用者)調用一個功能,不知道該功能結果,該功能有結果後通知我,即回調通知
同步和異步主要針對c端,可是跟s端不是徹底不要緊,同步和異步必須s端配合才能實現,同步和異步由c端控制,可是s端是否爲阻塞仍是非阻塞,c端不關心。

3:阻塞

1
就是調用我(s端被調用者,函數),我(s端被調用者,函數)沒有徹底接受完數據或者沒有獲得結果以前,我不會返回。

4:非阻塞

1
就是調用我(s端被調用者,函數),我(s端被調用者,函數)當即返回,經過 select 通知調用者

同步I/O與異步I/O的區別在與數據訪問的時候進程是否阻塞
阻塞I/O與非阻塞I/O的區別在與:應該程序的調用是否當即返回。

阻塞和非阻塞是指server端的進程訪問的數據若是還沒有就緒,進程是否須要等待,簡單說這至關於函數內部的實現區別,也就是未就緒時時直接返回仍是等待就緒。

就同步和異步是指client端訪問數據的機制,同步通常指主動請求並等待I/O操做完畢的方式,當數據就緒後再讀寫額時候必須阻塞,異步則指主動請求數據後即可以繼續處理其餘任務,隨後等待I/O,操做完畢的通知。

1、阻塞I/O模型:
簡介:進程會一直阻塞,直到數據拷貝完成
應用程序調用一個I/O函數,致使應用程序阻塞,等待數據準備好,若是數據沒有準備好,一直等待。。數據準備好,從內核拷貝到用戶空間,I/O函數返回成功

阻塞I/O模型圖:在調用recv()/recvfrom(),發生在內核中等待數據和複製數據過程。

 

當調用recv()函數時,系統首先檢查是否有準備好的數據,若是數據沒有準備好,那麼系統就處於等待狀態,當數據準備好後,將數據從系統緩衝區複製到用戶空間,而後函數返回。在套接應用程序中,當調用recv()函數時,未必用戶空間就已經存在數據,那麼此時recv()函數處於等待狀態

2、非阻塞I/O模型:
簡介:咱們把一個套接口設置爲非阻塞就是告訴內存,當所請求的I/O操做沒法完成時,不要驚進程睡眠,而是返回一個錯誤,河陽I/O函數會不斷的測試數據是否準備好,沒有準備好,繼續測試,直到數據準備好爲止。在測試的過程當中會佔用大量的CPU時間。

 

3、I/O複用模型:
簡介:主要是select和epoll;對於一個I/O端口,兩次調用,兩次返回,比阻塞I/O並無什麼優點,只是能實現同時對多個I/O端口進行監聽。

I/O複用模型會調用select,poll函數,這幾個函數也會使進程阻塞,可是和阻塞I/O不一樣的,這個函數能夠同時阻塞多個I/O操做,並且能夠同時對多個讀操做,多個寫操做的I/O函數進行檢測,直到有數據可讀或可寫時,才真正調用I/O操做函數。

4、信號驅動I/O:
簡介:兩次調用,兩次返回
首先容許套接口進行信號驅動I/O,並安裝一個信號處理函數,進程繼續運行並不阻塞。昂數據準備好時,進程會收到一個SIGIO信號,能夠在信號處理函數中調用I/O操做函數處理數據。

5、異步I/O模型:
簡介:數據拷貝的時候進程無需阻塞
當一個異步過程調用發出後,調用者不能馬上獲得結果。實際處理這個調用的部件在完成後,經過狀態,通知和回調通知調用者輸入輸出操做。

同步I/O引發進程阻塞,直到I/O操做完成 
異步I/O不會引發進程阻塞 
I/O複用先經過select調用阻塞

 

NGINX:

nginx 支持多種併發模型,併發模型的具體實現根據系統平臺而有所不一樣。
在支持多種併發模型的平臺上,nginx 自動選擇最高效的模型。但咱們也可使用 use 指令在配置文件中顯式地定義某個併發模型。

NGINX中支持的併發模型:

select:

1
IO多路複用、標準併發模型。在編譯 nginx 時,若是所使用的系統平臺沒有更高效的併發模型, select  模塊將被自動編譯。configure 腳本的選項:--with-select_module 和 --without-select_module 可被用來強制性地開啓或禁止  select  模塊的編譯

poll:

1
IO多路複用、標準併發模型。與  select  相似,在編譯 nginx 時,若是所使用的系統平臺沒有更高效的併發模型,poll 模塊將被自動編譯。configure 腳本的選項:--with-poll_module 和 --without-poll_module 可用於強制性地開啓或禁止 poll 模塊的編譯

epoll:

1
IO多路複用、高效併發模型,可在 Linux 2.6+ 及以上內核可使用

kqueue:

1
IO多路複用、高效併發模型,可在 FreeBSD 4.1+, OpenBSD 2.9+, NetBSD 2.0, and Mac OS X 平臺中使用

/dev/poll:

1
高效併發模型,可在 Solaris 7 11 /99 +, HP /UX  11.22+ (eventport), IRIX 6.5.15+, and Tru64 UNIX 5.1A+ 平臺使用

eventport:

1
高效併發模型,可用於 Solaris 10 平臺,PS:因爲一些已知的問題,建議 使用 /dev/poll 替代。

 

爲何epoll快?

比較一下Apache經常使用的select,和Nginx經常使用的epoll

select:

1
2
3
一、最大併發數限制,由於一個進程所打開的 FD (文件描述符)是有限制的,由 FD_SETSIZE 設置,默認值是 1024 /2048  ,所以 Select 模型的最大併發數就被相應限制了。本身改改這個 FD_SETSIZE ?想法雖好,但是先看看下面吧 …
二、效率問題,  select  每次調用都會線性掃描所有的 FD 集合,這樣效率就會呈現線性降低,把 FD_SETSIZE 改大的後果就是,你們都慢慢來,什麼?都超時了。
三、內核 / 用戶空間 內存拷貝問題,如何讓內核把 FD 消息通知給用戶空間呢?在這個問題上  select  採起了內存拷貝方法,在FD很是多的時候,很是的耗費時間。

總結爲:一、鏈接數受限 二、查找配對速度慢 三、數據由內核拷貝到用戶態消耗時間

epoll:

1
2
3
一、Epoll 沒有最大併發鏈接的限制,上限是最大能夠打開文件的數目,這個數字通常遠大於 2048, 通常來講這個數目和系統內存關係很大 ,具體數目能夠  cat  /proc/sys/fs/file-max  查看。
二、效率提高, Epoll 最大的優勢就在於它只管你「活躍」的鏈接 ,而跟鏈接總數無關,所以在實際的網絡環境中, Epoll 的效率就會遠遠高於  select  和 poll 。
三、內存共享, Epoll 在這點上使用了「共享內存 」,這個內存拷貝也省略了。

 

還不懂?

舉個栗子

假設你在大學中讀書,要等待一個朋友來訪,而這個朋友只知道你在A號樓,可是不知道你具體住在哪裏,因而大家約好了在A號樓門口見面.

若是你使用的阻塞IO模型來處理這個問題,那麼你就只能一直守候在A號樓門口等待朋友的到來,在這段時間裏你不能作別的事情,不難知道,這種方式的效率是低下的.

如今時代變化了,開始使用多路複用IO模型來處理這個問題.你告訴你的朋友來了A號樓找樓管大媽,讓她告訴你該怎麼走.這裏的樓管大媽扮演的就是多路複用IO的角色.

select版大媽作的是以下的事情:好比同窗甲的朋友來了,select版大媽比較笨,她帶着朋友挨個房間進行查詢誰是同窗甲,你等的朋友來了,因而在實際的代碼中,select版大媽作的是如下的事情:

1
2
3
4
5
6
7
8
9
10
int n =  select (&readset,NULL,NULL,100);
 
for  (int i = 0; n > 0; ++i)
{
     if  (FD_ISSET(fdarray[i], &readset))
     {
         do_something(fdarray[i]);
         --n;
     }
}

epoll版大媽就比較先進了,她記下了同窗甲的信息,好比說他的房間號,那麼等同窗甲的朋友到來時,只須要告訴該朋友同窗甲在哪一個房間便可,不用本身親自帶着人滿大樓的找人了.因而epoll版大媽作的事情能夠用以下的代碼表示:

1
2
3
4
5
6
n=epoll_wait(epfd,events,20,500);
 
for (i=0;i<n;++i)
{
     do_something(events[n]);
}

在epoll中,關鍵的數據結構epoll_event定義以下:

1
2
3
4
5
6
7
8
9
10
typedef union epoll_data {
     void *ptr;
     int fd;
     __uint32_t u32;
     __uint64_t u64;
} epoll_data_t;
struct epoll_event {
     __uint32_t events;     /* Epoll events */
     epoll_data_t data;     /* User data variable */
};

能夠看到,epoll_data是一個union結構體,它就是epoll版大媽用於保存同窗信息的結構體,它能夠保存不少類型的信息:fd,指針,等等.有了這個結構體,epoll大媽能夠不用吹灰之力就能夠定位到同窗甲.別小看了這些效率的提升,在一個大規模併發的服務器中,輪詢IO是最耗時間的操做之一.再回到那個例子中,若是每到來一個朋友樓管大媽都要全樓的查詢同窗,那麼處理的效率必然就低下了,過不久樓底就有很多的人了.
對比最先給出的阻塞IO的處理模型, 能夠看到採用了多路複用IO以後, 程序能夠自由的進行本身除了IO操做以外的工做, 只有到IO狀態發生變化的時候由多路複用IO進行通知, 而後再採起相應的操做, 而不用一直阻塞等待IO狀態發生變化了.

從上面的分析也能夠看出,epoll比select的提升其實是一個用空間換時間思想的具體應用.

相關文章
相關標籤/搜索