7層網絡以及5種Linux IO模型以及相應IO基礎


一、七層網絡模型

  OSI是Open System Interconnection的縮寫,意為開放式系統互聯。國際標准化組織(ISO)制定了OSI模型,該模型定義了不同計算機互聯的標准,它是一個七層的、抽象的模型體。

      

  1、物理層

  並不是物理媒體本身,它只是開放系統中利用物理媒體實現物理連接的功能描述和執行連接的規程,建立、維護、斷開物理連接,傳輸單位是比特(bit)

  物理層的媒體包括架空明線、平衡電纜、光纖、無線信道等。通信用的互連設備指DTE(Data Terminal Equipment)和DCE(Data Communications Equipment)間的互連設備。DTE即數據終端設備,又稱物理設備,如計算機、終端等都包括在內。而DCE則是數據通信設備或電路連接設備,如調制解調器等。數據傳輸通常是經過DTE-DCE,再經過DCE-DTE的路徑。互連設備指將DTE、DCE連接起來的裝置,如各種插頭、插座。LAN中的各種粗、細同軸電纜、T型接頭、插頭、接收器、發送器、中繼器等都屬物理層的媒體和連接器。

  物理層的主要功能是:

  ① 為數據端設備提供傳送數據的通路,數據通路可以是一個物理媒體,也可以是多個物理媒體連接而成。一次完整的數據傳輸,包括激活物理連接、傳送數據和終止物理連接。所謂激活,就是不管有多少物理媒體參與,都要在通信的兩個數據終端設備間連接起來,形成一條通路。

  ② 傳輸數據。物理層要形成適合數據傳輸需要的實體,為數據傳送服務。一是要保證數據能在其上正確通過,二是要提供足夠的帶寬(帶寬是指每秒鍾內能通過的比特(Bit)數),以減少信道上的擁塞。傳輸數據的方式能滿足點到點,一點到多點,串行或並行,半雙工或全雙工,同步或異步傳輸的需要。

  2、數據鏈路層

  可以粗略地理解為數據通道,傳輸單位是幀(Frame)物理層要為終端設備間的數據通信提供傳輸介質及其連接。介質是長期的,連接是有生存期的。在連接生存期內,收發兩端可以進行不等的一次或多次數據通信。每次通信都要經過建立通信聯絡和拆除通信聯絡兩個過程。這種建立起來的數據收發關系就叫做數據鏈路。而在物理媒體上傳輸的數據難免受到各種不可靠因素的影響而產生差錯,為了彌補物理層上的不足,為上層提供無差錯的數據傳輸,就要能對數據進行檢錯和糾錯。鏈路層應具備如下功能:

  ① 鏈路連接的建立、拆除和分離;

  ② 差錯檢測和恢復。還有鏈路標識,流量控制等等。

  獨立的鏈路產品中最常見的當屬網卡、網橋、二路交換機等。

  3、網絡層

  在網絡層: 有IP (IPV4、IPV6)協議、ICMP協議、ARP協議、RARP協議和BOOTP協議,負責建立“主機”到“主機”的通訊,傳輸單位是分組(數據包Packet)

  當數據終端增多時。它們之間有中繼設備相連,此時會出現一台終端要求不只是與惟一的一台而是能和多台終端通信的情況,這就產生了把任意兩台數據終端設備的數據鏈接起來的問題,也就是路由或者叫尋徑。另外,當一條物理信道建立之后,被一對用戶使用,往往有許多空閑時間被浪費掉。人們自然會希望讓多對用戶共用一條鏈路,為解決這一問題就出現了邏輯信道技術和虛擬電路技術。

  4、傳輸層

  在傳輸層: 有TCP協議與UDP協議,負責建立“端口”到“端口”的通信,傳輸單位是數據段(Segment)。 

  有一個既存事實,即世界上各種通信子網在性能上存在着很大差異。例如電話交換網,分組交換網,公用數據交換網,局域網等通信子網都可互連,但它們提供的吞吐量,傳輸速率,數據延遲通信費用各不相同。對於會話層來說,卻要求有一性能恆定的界面。傳輸層就承擔了這一功能。

  5、會話層

  會話單位的控制層,其主要功能是按照在應用進程之間約定的原則,按照正確的順序收、發數據,進行各種形態的對話。會話層規定了會話服務用戶間會話連接的建立和拆除規程以及數據傳送規程。

  會話層提供的服務是應用建立和維持會話,並能使會話獲得同步。會話層使用校驗點可使通信會話在通信失效時從校驗點繼續恢復通信。這種能力對於傳送大的文件極為重要。

  6、表示層

  其主要功能是把應用層提供的信息變換為能夠共同理解的形式,提供字符代碼、數據格式、控制信息格式、加密等的統一表示。表示層的作用之一是為異種機通信提供一種公共語言,以便能進行互操作。這種類型的服務之所以需要,是因為不同的計算機體系結構使用的數據表示法不同。例如,IBM主機使用EBCDIC編碼,而大部分PC機使用的是ASCII碼。在這種情況下,便需要表示層來完成這種轉換。

  7、應用層

  向應用程序提供服務,這些服務按其向應用程序提供的特性分成組,並稱為服務元素。有些可為多種應用程序共同使用,有些則為較少的一類應用程序使用。應用層是開放系統的最高層,是直接為應用進程提供服務的。其作用是在實現多個系統應用進程相互通信的同時,完成一系列業務處理所需的服務。

  在應用層: 有FTP、HTTP、TELNET、SMTP、DNS等協議。

二、七層網絡模型傳輸過程

            

            

  TCP/IP中的數據包傳輸過程如下:

  每個分層中,都會對所發送的數據附加一個首部,在這個首部中包含了該層必要的信息,如發送的目標地址以及協議相關信息。通常,為協議提供的信息為包首部,所要發送的內容為數據。在下一層的角度看,從上一層收到的包全部都被認為是本層的數據。

  網絡中傳輸的數據包由兩部分組成:一部分是協議所要用到的首部,另一部分是上一層傳過來的數據。首部的結構由協議的具體規范詳細定義。在數據包的首部,明確標明了協議應該如何讀取數據。反過來說,看到首部,也就能夠了解該協議必要的信息以及所要處理的數據。

  ① 應用程序處理

  首先應用程序會進行編碼處理,這些編碼相當於 OSI 的表示層功能;編碼轉化后,郵件不一定馬上被發送出去,這種何時建立通信連接何時發送數據的管理功能,相當於 OSI 的會話層功能。

  ② TCP 模塊的處理

  TCP 根據應用的指示,負責建立連接、發送數據以及斷開連接。TCP 提供將應用層發來的數據順利發送至對端的可靠傳輸。為了實現這一功能,需要在應用層數據的前端附加一個 TCP 首部。

  ③ IP 模塊的處理

  IP 將 TCP 傳過來的 TCP 首部和 TCP 數據合起來當做自己的數據,並在 TCP 首部的前端加上自己的 IP 首部。IP 包生成后,參考路由控制表決定接受此 IP 包的路由或主機。

  ④ 網絡接口(以太網驅動)的處理

  從 IP 傳過來的 IP 包對於以太網來說就是數據。給這些數據附加上以太網首部並進行發送處理,生成的以太網數據包將通過物理層傳輸給接收端。

  ⑤ 網絡接口(以太網驅動)的處理

  主機收到以太網包后,首先從以太網包首部找到 MAC 地址判斷是否為發送給自己的包,若不是則丟棄數據。

  如果是發送給自己的包,則從以太網包首部中的類型確定數據類型,再傳給相應的模塊,如 IP、ARP 等。這里的例子則是 IP 。

  ⑥ IP 模塊的處理

  IP 模塊接收到 數據后也做類似的處理。從包首部中判斷此 IP 地址是否與自己的 IP 地址匹配,如果匹配則根據首部的協議類型將數據發送給對應的模塊,如 TCP、UDP。這里的例子則是 TCP。
  另外,對於有路由器的情況,接收端地址往往不是自己的地址,此時,需要借助路由控制表,在調查應該送往的主機或路由器之后再進行轉發數據。

  ⑦ TCP 模塊的處理

  在 TCP 模塊中,首先會計算一下校驗和,判斷數據是否被破壞。然后檢查是否在按照序號接收數據。最后檢查端口號,確定具體的應用程序。數據被完整地接收以后,會傳給由端口號識別的應用程序。

  ⑧ 應用程序的處理

  接收端應用程序會直接接收發送端發送的數據。通過解析數據,展示相應的內容。

  傳輸過程中協議如下:

    

 三、什么是SOCKET 

  Socket是應用層與TCP/IP協議族通信的中間軟件抽象層,它是一組接口。在設計模式中,Socket其實就是一個門面模式,它把復雜的TCP/IP協議族隱藏在Socket接口后面,對用戶來說,一組簡單的接口就是全部,讓Socket去組織數據,以符合指定的協議。

  Socket 接口是TCP/IP網絡的API,Socket接口定義了許多函數或例程,用以開發TCP/IP網絡上的應用程序。

  Socket為了實現以上的通信過程而建立成來的通信管道,其真實的代表是客戶端和服務器端的一個通信進程,雙方進程通過socket進行通信,而通信的規則采用指定的協議。socket只是一種連接模式,不是協議,tcp,udp,簡單的說(雖然不准確)是兩個最基本的協議,很多其它協議都是基於這兩個協議如,http就是基於tcp的,用socket可以創建tcp連接,也可以創建udp連接,這意味着,用socket可以創建任何協議的連接,因為其它協議都是基於此的。

  綜上所述:需要IP協議來連接網絡;TCP是一種允許我們安全傳輸數據的機制,使用TCP協議來傳輸數據的HTTP是Web服務器和客戶端使用的特殊協議。HTTP基於TCP協議,但是卻可以使用socket去建立一個TCP連接。

  如圖:

          

四、長短連接

  短連接:連接->傳輸數據->關閉連接

  也可以這樣說:短連接是指SOCKET連接后發送后接收完數據后馬上斷開連接。

  長連接:連接->傳輸數據->保持連接 -> 傳輸數據-> 。。。 ->關閉連接。

  長連接指建立SOCKET連接后不管是否使用都保持連接,但安全性較差。

  什么時候用長連接,短連接?

  長連接多用於操作頻繁,點對點的通訊,而且連接數不能太多情況。每個TCP連接都需要三步握手,這需要時間,如果每個操作都是先連接,再操作的話那么處理速度會降低很多,所以每個操作完后都不斷開,下次處理時直接發送數據包就OK了,不用建立TCP連接。例如:數據庫的連接用長連接, 如果用短連接頻繁的通信會造成socket錯誤,而且頻繁的socket 創建也是對資源的浪費。

  而像WEB網站的http服務一般都用短鏈接,因為長連接對於服務端來說會耗費一定的資源,而像WEB網站這么頻繁的成千上萬甚至上億客戶端的連接用短連接會更省一些資源,如果用長連接,而且同時有成千上萬的用戶,如果每個用戶都占用一個連接的話,那可想而知吧。所以並發量大,但每個用戶無需頻繁操作情況下需用短連好。
總之,長連接和短連接的選擇要視情況而定。

五、三次握手四次分手

        

  SYN,ACK,FIN存放在TCP的標志位,一共有6個字符,這里就介紹這三個:

SYN:代表請求創建連接,所以在三次握手中前兩次要SYN=1,表示這兩次用於建立連接,至於第三次什么用,在疑問三里解答。

FIN:表示請求關閉連接,在四次分手時,我們發現FIN發了兩遍。這是因為TCP的連接是雙向的,所以一次FIN只能關閉一個方向。

ACK:代表確認接受,從上面可以發現,不管是三次握手還是四次分手,在回應的時候都會加上ACK=1,表示消息接收到了,並且在建立連接以后的發送數據時,都需加上ACK=1,來表示數據接收成功。

seq: 序列號,什么意思呢?當發送一個數據時,數據是被拆成多個數據包來發送,序列號就是對每個數據包進行編號,這樣接受方才能對數據包進行再次拼接。初始序列號是隨機生成的,這樣不一樣的數據拆包解包就不會連接錯了。(例如:兩個數據都被拆成1,2,3和一個數據是1,2,3一個是101,102,103,很明顯后者不會連接錯誤)

ack: 這個代表下一個數據包的編號,這也就是為什么第二請求時,ack是seq+1

   TCP是雙向的,所以需要在兩個方向分別關閉,每個方向的關閉又需要請求和確認,所以一共就4次分手。

六、文件描述符

  在UNIX、Linux的系統調用中,內核系統把應用程序可以操作的資源都抽象成了文件概念,比如說硬件設備,socket,流,磁盤,進程,線程;文件描述符就是索引(指針)。

  文件描述符就是內核為了高效管理已被打開的文件所創建的索引,用於指向被打開的文件,所有執行I/O操作的系統調用都通過文件描述符;文件描述符是一個簡單的非負整數,用以表明每個被進程打開的文件。程序剛剛啟動時,第一個打開的文件是0,第二個是1,以此類推。也可以理解為文件的身份ID。如:

         

  標准輸入輸出說明

  stdin,標准輸入,默認設備是鍵盤,文件編號為0

  stdout,標准輸出,默認設備是顯示器,文件編號為1,也可以重定向到文件

  stderr,標准錯誤,默認設備是顯示器,文件編號為2,也可以重定向到文件

  /proc/[進程ID]/fd  這個目錄專門用於存放文件描述符,可以到目錄下查看文件描述符使用情況,同時也可以通過ulimit查看文件描述符限制,如:

192:~ XXX$ ulimit -n  //-n打開文件描述符的最大個數
256
192:~ XXX$ ulimit -Sn  //-S是軟性限額
256
192:~ XXX$ ulimit -Hn  //-H是硬性限額
unlimited

  Linux中最大文件描述符的限制有兩個方面,一個是用戶級限制,一個是系統級限制,文件描述符限制均可進行修改,但是也有一個限制,規則如下:

  a.  所有進程打開的文件描述符數不能超過/proc/sys/fs/file-max

  b.  單個進程打開的文件描述符數不能超過user limit中nofile的soft limit

  c.  nofile的soft limit不能超過其hard limit

  d.  nofile的hard limit不能超過/proc/sys/fs/nr_open

七、零拷貝

  應用程序獲取數據的兩個階段:

  數據准備:應用程序無法直接操作我們的硬件資源,需要操作系統資源時,先通知我們的內核,內核檢查是否有就緒的資源,如果有則先把對應數據加載到內核空間。

  數據拷貝:把數據資源從內核空間復制到應用程序的用戶空間。

  補充知識 -> 零拷貝

  現代操作系統都使用虛擬內存,使用虛擬的地址取代物理地址,這樣做的好處是:

  1.一個以上的虛擬地址可以指向同一個物理內存地址,

  2.虛擬內存空間可大於實際可用的物理地址;

  利用第一條特性可以把內核空間地址和用戶空間的虛擬地址映射到同一個物理地址,這樣DMA就可以填充對內核和用戶空間進程同時可見的緩沖區了,大致如下圖所示:

            

  關於mmap以及sendfile零拷貝,可以參考:如何實現高性能的IO及其原理? 

八、Linux 網絡IO模型

  什么是同步和異步,阻塞和非阻塞?

  同步和異步關注的是結果消息的通信機制

  同步:同步的意思就是調用方需要主動等待結果的返回

  異步:異步的意思就是不需要主動等待結果的返回,而是通過其他手段比如,狀態通知,回調函數等。

  阻塞和非阻塞主要關注的是等待結果返回時調用方的狀態

  阻塞:是指結果返回之前,當前線程被掛起,不做任何事

  非阻塞:是指結果在返回之前,線程可以做一些其他事,不會被掛起。

  Linux有5種IO模型,如下圖所示:

        

  1、阻塞I/O模型

  應用程序調用一個IO函數,導致應用程序阻塞,等待數據准備好。 如果數據沒有准備好,一直等待….數據准備好了,從內核拷貝到用戶空間,IO函數返回成功指示。

  當調用recv()函數時,系統首先查是否有准備好的數據。如果數據沒有准備好,那么系統就處於等待狀態。當數據准備好后,將數據從系統緩沖區復制到用戶空間,然后該函數返回。在套接應用程序中,當調用recv()函數時,未必用戶空間就已經存在數據,那么此時recv()函數就會處於等待狀態。

            

  2、非阻塞IO模型

  我們把一個SOCKET接口設置為非阻塞就是告訴內核,當所請求的I/O操作無法完成時,不要將進程睡眠,而是返回一個錯誤。這樣我們的I/O操作函數將不斷的測試數據是否已經准備好,如果沒有准備好,繼續測試,直到數據准備好為止。在這個不斷測試的過程中,會大量的占用CPU的時間。上述模型絕不被推薦。

  把SOCKET設置為非阻塞模式,即通知系統內核:在調用Windows Sockets API時,不要讓線程睡眠,而應該讓函數立即返回。在返回時,該函數返回一個錯誤代碼。如圖所示,一個非阻塞模式套接字多次調用recv()函數的過程。前三次調用recv()函數時,內核數據還沒有准備好。因此,該函數立即返回WSAEWOULDBLOCK錯誤代碼。第四次調用recv()函數時,數據已經准備好,被復制到應用程序的緩沖區中,recv()函數返回成功指示,應用程序開始處理數據。

            

  3、IO復用模型

  簡介:主要是select和epoll;對一個IO端口,兩次調用,兩次返回,比阻塞IO並沒有什么優越性;關鍵是能實現同時對多個IO端口進行監聽;

  I/O復用模型會用到select、poll、epoll函數,這幾個函數也會使進程阻塞,但是和阻塞I/O所不同的的,這兩個函數可以同時阻塞多個I/O操作。而且可以同時對多個讀操作,多個寫操作的I/O函數進行檢測,直到有數據可讀或可寫時,才真正調用I/O操作函數。

  當用戶進程調用了select,那么整個進程會被block;而同時,kernel會“監視”所有select負責的socket;當任何一個socket中的數據准備好了,select就會返回。這個時候,用戶進程再調用read操作,將數據從kernel拷貝到用戶進程。

  這個圖和blocking IO的圖其實並沒有太大的不同,事實上還更差一些。因為這里需要使用兩個系統調用(select和recvfrom),而blocking IO只調用了一個系統調用(recvfrom)。但是,用select的優勢在於它可以同時處理多個connection。(select/epoll的優勢並不是對於單個連接能處理得更快,而是在於能處理更多的連接。)

           

  在這種模型中,這時候並不是進程直接發起資源請求的系統調用去請求資源,進程不會被“全程阻塞”,進程是調用select或poll函數。進程不是被阻塞在真正IO上了,而是阻塞在select或者poll上了。Select或者poll幫助用戶進程去輪詢那些IO操作是否完成。

  不過你可以看到之前都只使用一個系統調用,在IO復用中反而是用了兩個系統調用,但是使用IO復用你就可以等待多個描述符也就是通過單進程單線程實現並發處理,同時還可以兼顧處理套接字描述符和其他描述符。

   4、信號驅動IO 

  簡介:兩次調用,兩次返回;

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

           

  5、異步IO模型

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

  在linux的異步IO模型中,並沒有真正實現異步通道,最終的實現還是等同於調用Epoll。

           

   LInux IO模型總結如圖所示:

          

 九、多路復用IO原理詳解

  在linux 沒有實現epoll事件驅動機制之前,我們一般選擇用select或者poll等IO多路復用的方法來實現並發服務程序。但在大數據、高並發、集群出現后,select和poll的性能瓶頸無法在支撐,於是epoll出現了。

  1、select

  首先來說說select,select 函數監視的文件描述符分3類,分別是writefds、readfds、和exceptfds。調用后select函數會阻塞,直到有描述符就緒(有數據 可讀、可寫、或者有except),或者超時(timeout指定等待時間,如果立即返回設為null即可),函數返回。當select函數返回后,可以通過遍歷fd_set,來找到就緒的描述符。

int select(int nfds, fd_set *restrict readfds, fd_set *restrict writefds, fd_set *restrict errorfds, struct timeval *restrict timeout);

  具體select步驟如圖所示:

            

  1. 使用copy_from_user從用戶空間拷貝fd_set到內核空間。
  2. 注冊回調函數__pollwait。
  3. 遍歷所有FD,調用其對應的poll方法(對於socket,這個poll方法是sock_poll,sock_poll根據情況會調用到tcp_poll, udp_poll或者datagram_poll)
  4. 以tcp_poll為例,其核心實現就是__pollwait,也就是上面注冊的回調函數。
  5. __pollwait的主要工作就是把當前進程掛到設備的等待隊列中,不同的設備有不同的等待隊列,對於tcp_poll來說,其等待隊列是sk->sk_sleep(注意把進程掛到等待隊列中並不代表進程已經睡眠了)。在設備收到一條消息(網絡IO)或填寫完文件數據(磁盤IO)后,會喚醒設備等待隊列上睡眠的進程,這時當前進程便被喚醒了。
  6. poll方法返回時會返回一個描述讀寫操作是否就緒的mask掩碼,根據這個mask掩碼給fd_set賦值。
  7. 如果遍歷完所有的FD,還沒有返回一個可讀寫的mask掩碼,則會調用schedule_timeout讓調用select的當前進程進入睡眠。當設備驅動發生自身資源可讀寫后,會喚醒其等待隊列上睡眠的進程。如果超過設定的超時時間,還是沒人喚醒,則調用select的進程會重新被喚醒獲得CPU,進而重新遍歷FD,判斷有沒有就緒的FD。
  8. 把fd_set從內核空間拷貝到用戶空間。
  9. select的觸發方式是水平觸發,應用程序如果沒有完成對一個已經就緒的文件描述符進行IO操作,那么之后每次select調用還是會將這些文件描述符通知進程。

  注意:select的實現依賴於文件的驅動函數poll,在unix中無論是調用 select、poll 還是epoll,最終都會調用該函數。

  2、poll

int poll(struct pollfd fds[], nfds_t nfds, int timeout);

  不同與select使用三個位圖來表示三個fdset的方式,poll使用一個 pollfd的指針實現。

struct pollfd {
    int fd; /* file descriptor */
    short events; /* requested events to watch */
    short revents; /* returned events witnessed */
};

  和select函數一樣,poll返回后,需要輪詢pollfd來獲取就緒的描述符。select和poll都需要在返回后,通過遍歷文件描述符來獲取已經就緒的socket。事實上,同時連接的大量客戶端在一時刻可能只有很少的處於就緒狀態,因此隨着監視的描述符數量的增長,其效率也會線性下降。

  3、epoll

  在select/poll時代,服務器進程每次都把這100萬個連接告訴操作系統(從用戶態復制句柄數據結構到內核態),讓操作系統內核去查詢這些套接字上是否有事件發生,輪詢完后,再將句柄數據復制到用戶態,讓服務器應用程序輪詢處理已發生的網絡事件,這一過程資源消耗較大,因此,select/poll一般只能處理幾千的並發連接。

  epoll的設計和實現與select完全不同。epoll通過在Linux內核中申請一個簡易的文件系統(文件系統一般用什么數據結構實現?紅黑樹)。epoll提供了三個函數,epoll_create, epoll_ctl和epoll_wait,epoll_create是創建一個epoll句柄;epoll_ctl是注冊要監聽的事件類型;epoll_wait則是等待事件的產生。

int epoll_create(int size);//創建一個epoll的句柄,size用來告訴內核這個監聽的數目一共有多大
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
struct epoll_event {
  __uint32_t events;  /* Epoll events */
  epoll_data_t data;  /* User data variable */
};
// events可以是以下幾個宏的集合:
// EPOLLIN :表示對應的文件描述符可以讀(包括對端SOCKET正常關閉);
// EPOLLOUT:表示對應的文件描述符可以寫;
// EPOLLPRI:表示對應的文件描述符有緊急的數據可讀(這里應該表示有帶外數據到來);
// EPOLLERR:表示對應的文件描述符發生錯誤;
// EPOLLHUP:表示對應的文件描述符被掛斷;
// EPOLLET: 將EPOLL設為邊緣觸發(Edge Triggered)模式,這是相對於水平觸發(Level Triggered)來說的。
// EPOLLONESHOT:只監聽一次事件,當監聽完這次事件之后,如果還需要繼續監聽這個socket的話,需要再次把這個socket加入到EPOLL隊列里

  一棵紅黑樹,一張准備就緒句柄鏈表,少量的內核cache,就幫我們解決了大並發下的socket處理問題。

  ① 執行 epoll_create
    內核在epoll文件系統中建了個file結點,(使用完,必須調用close()關閉,否則導致fd被耗盡)
       在內核cache里建了紅黑樹存儲epoll_ctl傳來的socket,
       在內核cache里建了rdllist雙向鏈表存儲准備就緒的事件。
  ② 執行 epoll_ctl
    如果增加socket句柄,檢查紅黑樹中是否存在,存在立即返回,不存在則添加到樹干上,然后向內核注冊回調函數,告訴內核如果這個句柄的中斷到了,就把它放到准備就緒list鏈表里。所有添加到epoll中的事件都會與設備(如網卡)驅動程序建立回調關系,相應的事件發生時,會調用回調方法。

  ③ 執行 epoll_wait

    立刻返回准備就緒表里的數據即可(將內核cache里雙向列表中存儲的准備就緒的事件  復制到用戶態內存),當調用epoll_wait檢查是否有事件發生時,只需要檢查eventpoll對象中的rdlist雙鏈表中是否有epitem元素即可。如果rdlist不為空,則把發生的事件復制到用戶態,同時將事件數量返回給用戶。 

        

   對於select的三個缺點以及epoll的解決方案:

  (1)每次調用select,都需要把fd集合從用戶態拷貝到內核態,這個開銷在fd很多時會很大。

  (2)同時每次調用select都需要在內核遍歷傳遞進來的所有fd,這個開銷在fd很多時也很大。

  (3)select支持的文件描述符數量太小了,默認是1024。

  對於第一個缺點,epoll的解決方案在epoll_ctl函數中。每次注冊新的事件到epoll句柄中時(在epoll_ctl中指定EPOLL_CTL_ADD),會把所有的fd拷貝進內核,而不是在epoll_wait的時候重復拷貝。epoll保證了每個fd在整個過程中只會拷貝一次。

  對於第二個缺點,epoll的解決方案不像select或poll一樣每次都把current輪流加入fd對應的設備等待隊列中,而只在epoll_ctl時把current掛一遍(這一遍必不可少)並為每個fd指定一個回調函數,當設備就緒,喚醒等待隊列上的等待者時,就會調用這個回調函數,而這個回調函數會把就緒的fd加入一個就緒鏈表)。epoll_wait的工作實際上就是在這個就緒鏈表中查看有沒有就緒的fd(利用schedule_timeout()實現睡一會,判斷一會的效果,和select實現中的第7步是類似的)。

  對於第三個缺點,epoll沒有這個限制,它所支持的FD上限是最大可以打開文件的數目,這個數字一般遠大於2048,舉個例子,在1GB內存的機器上大約是10萬左右,具體數目可以cat /proc/sys/fs/file-max察看,一般來說這個數目和系統內存關系很大。

  4、select、poll、epoll優缺點對比

        

          

  5、epoll 的水平觸發與邊緣觸發

  Level_triggered(水平觸發):

  當被監控的文件描述符上有可讀寫事件發生時,epoll_wait()會通知處理程序去讀寫。如果這次沒有把數據一次性全部讀寫完(如讀寫緩沖區太小),那么下次調用 epoll_wait()時,它還會通知你在上沒讀寫完的文件描述符上繼續讀寫,當然如果你一直不去讀寫,它會一直通知你!!!如果系統中有大量你不需要讀寫的就緒文件描述符,而它們每次都會返回,這樣會大大降低處理程序檢索自己關心的就緒文件描述符的效率!!!

  Edge_triggered(邊緣觸發):

  當被監控的文件描述符上有可讀寫事件發生時,epoll_wait()會通知處理程序去讀寫。如果這次沒有把數據全部讀寫完(如讀寫緩沖區太小),那么下次調用epoll_wait()時,它不會通知你,也就是它只會通知你一次,直到該文件描述符上出現第二次可讀寫事件才會通知你!!!這種模式比水平觸發效率高,系統不會充斥大量你不關心的就緒文件描述符!!

  select(),poll()模型都是水平觸發模式,信號驅動IO是邊緣觸發模式,epoll()模型即支持水平觸發,也支持邊緣觸發,默認是水平觸發。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM