##PIO與DMA 有必要簡單地說說慢速I/O設備和內存之間的數據傳輸方式。linux
###具體步驟: 當應用程序調用read接口時,操做系統檢查在內核的高速緩存有沒有須要的數據,若是已經緩存了,那麼就直接從緩存中返回,若是沒有,則從磁盤中讀取,而後緩存在操做系統的緩存中。nginx
應用程序調用write接口時,將數據從用戶地址空間複製到內核地址空間的緩存中,這時對用戶程序來講,寫操做已經完成,至於何時再寫到磁盤中,由操做系統決定,除非顯示調用了sync同步命令。 數據庫
##內存映射(減小數據在用戶空間和內核空間之間的拷貝操做,適合大量數據傳輸
) Linux內核提供一種訪問磁盤文件的特殊方式,它能夠將內存中某塊地址空間和咱們要指定的磁盤文件相關聯,從而把咱們對這塊內存的訪問轉換爲對磁盤文件的訪問,這種技術稱爲內存映射(Memory Mapping)。緩存
操做系統將內存中的某一塊區域與磁盤中的文件關聯起來,當要訪問內存中的一段數據時,轉換爲訪問文件的某一段數據。這種方式的目的一樣是減小數據從內核空間緩存到用戶空間緩存的數據複製操做,由於這兩個空間的數據是共享的。服務器
內存映射是指將硬盤上文件的位置與進程邏輯地址空間中一塊大小相同的區域一一對應,當要訪問內存中一段數據時,轉換爲訪問文件的某一段數據。這種方式的目的一樣是減小數據在用戶空間和內核空間之間的拷貝操做。當大量數據須要傳輸的時候,採用內存映射方式去訪問文件會得到比較好的效率。網絡
使用內存映射文件處理存儲於磁盤上的文件時,將沒必要再對文件執行I/O操做,這意味着在對文件進行處理時將沒必要再爲文件申請並分配緩存,全部的文件緩存操做均由系統直接管理,因爲取消了將文件數據加載到內存、數據從內存到文件的回寫以及釋放內存塊等步驟,使得內存映射文件在處理大數據量的文件時能起到至關重要的做用。併發
###訪問步驟 app
在大多數狀況下,使用內存映射能夠提升磁盤I/O的性能,它無須使用read()或write()等系統調用來訪問文件,而是經過mmap()系統調用來創建內存和磁盤文件的關聯,而後像訪問內存同樣自由地訪問文件。 有兩種類型的內存映射,共享型和私有型,前者能夠將任何對內存的寫操做都同步到磁盤文件,並且全部映射同一個文件的進程都共享任意一個進程對映射內存的修改;後者映射的文件只能是隻讀文件,因此不能夠將對內存的寫同步到文件,並且多個進程不共享修改。顯然,共享型內存映射的效率偏低,由於若是一個文件被不少進程映射,那麼每次的修改同步將花費必定的開銷。異步
##直接I/O(繞過內核緩衝區,本身管理I/O緩存區
) 在Linux 2.6中,內存映射和直接訪問文件沒有本質上差別,由於數據從進程用戶態內存空間到磁盤都要通過兩次複製,即在磁盤與內核緩衝區之間以及在內核緩衝區與用戶態內存空間。 引入內核緩衝區的目的在於提升磁盤文件的訪問性能,由於當進程須要讀取磁盤文件時,若是文件內容已經在內核緩衝區中,那麼就不須要再次訪問磁盤;而當進程須要向文件中寫入數據時,實際上只是寫到了內核緩衝區便告訴進程已經寫成功,而真正寫入磁盤是經過必定的策略進行延遲的。socket
然而,對於一些較複雜的應用,好比數據庫服務器,它們爲了充分提升性能,但願繞過內核緩衝區,由本身在用戶態空間實現並管理I/O緩衝區,包括緩存機制和寫延遲機制等,以支持獨特的查詢機制,好比數據庫能夠根據更加合理的策略來提升查詢緩存命中率。另外一方面,繞過內核緩衝區也能夠減小系統內存的開銷,由於內核緩衝區自己就在使用系統內存。
應用程序直接訪問磁盤數據,不通過操做系統內核數據緩衝區,這樣作的目的是減小一次從內核緩衝區到用戶程序緩存的數據複製。這種方式一般是在對數據的緩存管理由應用程序實現的數據庫管理系統中。 直接I/O的缺點就是若是訪問的數據不在應用程序緩存中,那麼每次數據都會直接從磁盤進行加載,這種直接加載會很是緩慢。一般直接I/O跟異步I/O結合使用會獲得較好的性能。
###訪問步驟
Linux提供了對這種需求的支持,即在open()系統調用中增長參數選項O_DIRECT,用它打開的文件即可以繞過內核緩衝區的直接訪問,這樣便有效避免了CPU和內存的多餘時間開銷。
順便提一下,與O_DIRECT相似的一個選項是O_SYNC,後者只對寫數據有效,它將寫入內核緩衝區的數據當即寫入磁盤,將機器故障時數據的丟失減小到最小,可是它仍然要通過內核緩衝區。
##sendfile/零拷貝(網絡I/O,kafka用到此特性
) ###普通的網絡傳輸步驟以下: 1)操做系統將數據從磁盤複製到操做系統內核的頁緩存中 2)應用將數據從內核緩存複製到應用的緩存中 3)應用將數據寫回內核的Socket緩存中 4)操做系統將數據從Socket緩存區複製到網卡緩存,而後將其經過網絡發出
一、當調用read系統調用時,經過DMA(Direct Memory Access)將數據copy到內核模式 二、而後由CPU控制將內核模式數據copy到用戶模式下的 buffer中 三、read調用完成後,write調用首先將用戶模式下 buffer中的數據copy到內核模式下的socket buffer中 四、最後經過DMA copy將內核模式下的socket buffer中的數據copy到網卡設備中傳送。
從上面的過程能夠看出,數據白白從內核模式到用戶模式走了一圈,浪費了兩次copy,而這兩次copy都是CPU copy,即佔用CPU資源。
###sendfile
經過sendfile傳送文件只須要一次系統調用,當調用 sendfile時: 一、首先經過DMA copy將數據從磁盤讀取到kernel buffer中 二、而後經過CPU copy將數據從kernel buffer copy到sokcet buffer中 三、最終經過DMA copy將socket buffer中數據copy到網卡buffer中發送 sendfile與read/write方式相比,少了 一次模式切換一次CPU copy。可是從上述過程當中也能夠發現從kernel buffer中將數據copy到socket buffer是不必的。
爲此,Linux2.4內核對sendfile作了改進,下圖所示
改進後的處理過程以下: 一、DMA copy將磁盤數據copy到kernel buffer中 二、向socket buffer中追加當前要發送的數據在kernel buffer中的位置和偏移量 三、DMA gather copy根據socket buffer中的位置和偏移量直接將kernel buffer中的數據copy到網卡上。 通過上述過程,數據只通過了2次copy就從磁盤傳送出去了。(事實上這個Zero copy是針對內核來說的,數據在內核模式下是Zero-copy的)。 當前許多高性能http server都引入了sendfile機制,如nginx,lighttpd等。
###FileChannel.transferTo(Java中的零拷貝
) Java NIO中FileChannel.transferTo(long position, long count, WriteableByteChannel target)方法將當前通道中的數據傳送到目標通道target中,在支持Zero-Copy的linux系統中,transferTo()的實現依賴於 sendfile()調用。
傳統方式對比零拷貝方式:
整個數據通路涉及4次數據複製和2個系統調用,若是使用sendfile則能夠避免屢次數據複製,操做系統能夠直接將數據從內核頁緩存中複製到網卡緩存,這樣能夠大大加快整個過程的速度。
大多數時候,咱們都在向Web服務器請求靜態文件,好比圖片、樣式表等,根據前面的介紹,咱們知道在處理這些請求的過程當中,磁盤文件的數據先要通過內核緩衝區,而後到達用戶內存空間,由於是不須要任何處理的靜態數據,因此它們又被送到網卡對應的內核緩衝區,接着再被送入網卡進行發送。
數據從內核出去,繞了一圈,又回到內核,沒有任何變化,看起來真是浪費時間。在Linux 2.4的內核中,嘗試性地引入了一個稱爲khttpd的內核級Web服務器程序,它只處理靜態文件的請求。引入它的目的便在於內核但願請求的處理儘可能在內核完成,減小內核態的切換以及用戶態數據複製的開銷。
同時,Linux經過系統調用將這種機制提供給了開發者,那就是sendfile()系統調用。它能夠將磁盤文件的特定部分直接傳送到表明客戶端的socket描述符,加快了靜態文件的請求速度,同時也減小了CPU和內存的開銷。
在OpenBSD和NetBSD中沒有提供對sendfile的支持。經過strace的跟蹤看到了Apache在處理151字節的小文件時,使用了mmap()系統調用來實現內存映射,可是在Apache處理較大文件的時候,內存映射會致使較大的內存開銷,得不償失,因此Apache使用了sendfile64()來傳送文件,sendfile64()是sendfile()的擴展實現,它在Linux 2.4以後的版本中提供。
這並不意味着sendfile在任何場景下都能發揮顯著的做用。對於請求較小的靜態文件,sendfile發揮的做用便顯得不那麼重要,經過壓力測試,咱們模擬100個併發用戶請求151字節的靜態文件,是否使用sendfile的吞吐率幾乎是相同的,可見在處理小文件請求時,發送數據的環節在整個過程當中所佔時間的比例相比於大文件請求時要小不少,因此對於這部分的優化效果天然不十分明顯。
##docs