腦圖系列-作業系統IO

梦醒点灯發表於2024-03-15

同步、非同步、阻塞、非阻塞

同步與非同步描述的是被呼叫者的

如果是同步,B在接到A的呼叫後,會立即執行要做的事。A的本次呼叫可以得到結果。

  • 讓我幹活立馬就幹,立即反饋結果

如果是非同步,B在接到A的呼叫後,不保證會立即執行要做的事,但是保證會去做,B在做好了之後會通知A。A的本次呼叫得不到結果,但是B執行完之後會通知A。

  • 讓我幹活,等會幹,幹完通知你

阻塞與非阻塞描述的是呼叫者的

如果是阻塞,A在發出呼叫後,要一直等待,等著B返回結果。

  • 找人幹活,等著他幹完

如果是非阻塞,A在發出呼叫後,不需要等待,可以去做自己的事情。

  • 找人幹活,自己去忙

同步不一定阻塞,非同步也不一定非阻塞。沒有必然關係。

舉例

  • 老張把水壺放到火上,一直在水壺旁等著水開。(同步阻塞)
  • 老張把水壺放到火上,去客廳看電視,時不時去廚房看看水開沒有。(同步非阻塞)
  • 老張把響水壺放到火上,一直在水壺旁等著水開。(非同步阻塞)
  • 老張把響水壺放到火上,去客廳看電視,水壺響之前不再去看它了,響了再去拿壺。(非同步非阻塞)
  • 概要
  • 1和2的區別是,呼叫方在得到返回之前所做的事情不一樣,一個是等,一個是看電視
  • 1和3的區別是,被呼叫方對於燒水的處理不一樣。一個不會響,一個會響

IO

為了保護作業系統的安全,透過快取加快系統讀寫,會將記憶體分為使用者空間和核心空間兩個部分。如果使用者想要操作核心空間的資料,則需要把資料從核心空間複製到使用者空間(資料會放到核心空間的page cache中,這種也叫快取IO)。

客戶端處理請求步驟

  • 1、伺服器的網路驅動接受到訊息之後,向核心申請空間,並在收到完整的資料包(這個過程會產生延時,因為有可能是透過分組傳送過來的)後,將其複製到核心空間;
  • 2、資料從核心空間複製到使用者空間;
  • 3、使用者程式進行處理。

linux讀操作

  • 1、透過read系統呼叫,向核心傳送讀請求
  • 2、核心向硬體傳送讀指令,並等待讀就緒
  • 3、DMA把將要讀取的資料複製到指定的核心快取區中
  • 4、核心將資料從核心快取區複製到使用者程序空間中

由此誕生了5種IO方式

同步阻塞型IO模型

同步非阻塞型IO模型

  • 在這裡recv不管有沒有獲得到資料都返回,如果沒有資料的話就過段時間再呼叫recv看看,如此迴圈

IO複用模型

  • 呼叫recv之前會先呼叫select或poll,這兩個系統呼叫都可以在核心準備好資料(網路資料已經到達核心了)時告知使用者程序,它準備好了,這時候再呼叫recv時是一定有資料的。因此在這一模型中,程序阻塞於select或poll,而沒有阻塞在recv上
  • 就相當於,小J來銀行辦理業務,大堂經理告訴他現在所有櫃檯都有人在辦理業務,等有空位再告訴他。於是小J就等啊等(select或poll呼叫中),過了一會兒大堂經理告訴他有櫃檯空出來可以辦理業務了,但是具體是幾號櫃檯,你自己找下吧,於是小J就只能挨個櫃檯地找。
  • select、poll、epoll
  • select
  • 是最原始的 I/O 多路複用技術,幾乎在所有的平臺中都支援,它的缺點是最多隻能監聽 1024 個檔案描述符
  • select函式可以監聽read,write,except的fd。當select返回後,可以遍歷對應的fd_set來尋找就緒的fd,從而進行業務處理
  • 包含大量fd的陣列被整體複製於使用者態和核心的地址空間之間,而不論這些檔案描述符是否就緒,其開銷也隨著檔案描述符數量增加而線性增大
  • poll
  • 在 select 的基礎上增加了支援監聽更多的檔案描述符的能力,但是複雜度隨著監聽的檔案描述符數量的增加而增加
  • 同select一樣,poll返回後,也是需要輪詢pollfd來獲取就緒的fd。不僅如此,所有的fds也是在核心態和使用者態中來回切換,也會影響效率
  • 但是因為fds基於連結串列,所以就沒有了最長1024的限制
  • epoll
  • 在 poll 的基礎上進一步最佳化了複雜度,可以支援更多的檔案描述符,並且具有更高的效率
  • 每次註冊新的事件呼叫epoll_ctl時,epoll會把所有的fd複製進核心,而不是在epoll_wait的時候重複複製。epoll保證了每個fd在整個過程中只會複製一次。
  • epoll會透過epoll_wait檢視是否有就緒的fd,如果有就緒的fd,就會直接使用(O(1))。而不是像之前兩個一樣,每次需要手動遍歷才能得到就緒的fd(O(n))
  • epoll是透過epoll_wait來獲取就緒的fd,那麼如果就緒的fd一直沒有被消費,該如何處理呢?
  • LT(level trigger)(預設)模式
  • 當epoll_wait檢測到描述符事件發生並將此事件通知應用程式,應用程式可以不立即處理該事件。下次呼叫epoll_wait時,會再次響應應用程式並通知此事件
  • ET(edge trigger)模式
  • 當epoll_wait檢測到描述符事件發生並將此事件通知應用程式,應用程式必須立即處理該事件。如果不處理,下次呼叫epoll_wait時,不會再次響應應用程式並通知此事件。

訊號驅動模型

  • 此處會透過呼叫sigaction註冊訊號函式,在核心資料準備好的時候系統就中斷當前程式,執行訊號函式(在這裡呼叫recv)
  • 小J讓大堂經理在櫃檯有空位的時候通知他(註冊訊號函式),等沒多久大堂經理通知他,因為他是銀行的VIPPP會員,所以專門給他開了一個櫃檯來辦理業務,小J就去特席櫃檯辦理業務了。但即使在等待的過程中是非阻塞的,但在辦理業務的過程中依然是同步的。

非同步IO模型

  • 呼叫aio_read令核心把資料準備好,並且複製到使用者程序空間後執行事先指定好的函式
  • 小J交代大堂經理把業務給辦理好了就通知他來驗收,在這個過程中小J可以去做自己的事情。這就是真正的非同步IO。

零複製

  • 如果不考慮使用者態的記憶體複製和物理裝置到驅動的資料複製,我們會發現,這其中會涉及4次資料複製。同時也會涉及到4次程序上下文的切換
  • 對於Java程式,還會多了一個堆外記憶體和堆記憶體之間的copy

所謂的零複製,作用就是透過各種方式,在特殊情況下,減少資料複製的次數/減少CPU參與資料複製的次數

常見的零複製方式有mmap,sendfile,dma,directI/O等

擴充套件

  • DMA
  • 正常的IO流程中,不管是物理裝置之間的資料複製,如磁碟到記憶體,還是記憶體之間的資料複製,如使用者態到核心態,都是需要CPU參與的
  • 如果是比較大的檔案,這樣無意義的copy顯然會極大的浪費CPU的效率,所以就誕生了DMA
  • DMA的全稱是Direct Memory Access,顧名思義,DMA的作用就是直接將IO裝置的資料複製到核心緩衝區中。使用DMA的好處就是IO裝置到核心之間的資料複製不需要CPU的參與,CPU只需要給DMA傳送copy指令即可,提高了處理器的利用效率
  • mmap
  • mmap,全稱是memory map,翻譯過來就是記憶體對映,顧名思義,就是將核心態和使用者態的記憶體對映到一起,避免來回複製
  • 採用mmap + write的方式,記憶體複製的次數會變為3次,上下文切換則依舊是4次。
  • 問題
  • mmap 使用時必須實現指定好記憶體對映的大小,因此 mmap 並不適合變長檔案;
  • 因為mmap在檔案更新後會透過OS自動將髒頁回寫到disk中,所以在隨機寫很多的情況下,mmap 方式在效率上不一定會比帶緩衝區的一般寫快
  • 因為mmap必須要在記憶體中找到一塊連續的地址塊,如果在 32-bits 的作業系統上,虛擬記憶體總大小也就 2GB左右(32位系統的地址空間最大為4G,除去1G系統,使用者能使用的記憶體最多為3G左右(windows核心較大,一般使用者只剩下2G可用)。),此時就很難對 4GB 大小的檔案完全進行 mmap,所以對於超大檔案來講,mmap並不適合
  • sendfile
  • 如果只是傳輸資料,並不對資料作任何處理,可以透過sendfile的方式,只做檔案傳輸,而不透過使用者態進行干預
  • 為什麼核心要複製兩次(page cache -> socket cache),能不能省略這個步驟?
  • sendfile + DMA Scatter/Gather
  • DMA gather是LInux2.4新引入的功能,它可以讀page cache中的資料描述資訊(記憶體地址和偏移量)記錄到socket cache中,由 DMA 根據這些將資料從讀緩衝區複製到網路卡,相比之前版本減少了一次CPU複製的過程
  • direct I/O
  • 之前的mmap可以讓使用者態和核心態共用一個記憶體空間來減少複製,其實還有一個方式,就是硬體資料不經過核心態的空間,直接到使用者態的記憶體中,這種方式就是Direct I/O。換句話說,Direct I/O不會經過核心態,而是使用者態和裝置的直接互動,使用者態的寫入就是直接寫入到磁碟,不會再經過作業系統刷盤處理。
  • 這樣確實複製次數減少,讀取速度會變快,但是因為作業系統不再負責快取之類的管理,這就必須交由應用程式自己去做,譬如MySql就是自己透過Direct I/O完成的,同時MySql也有一套自己的快取系統
  • 同時,雖然direct I/O可以直接將檔案寫入磁碟中,但是檔案相關的元資訊還是要透過fsync快取到核心空間中

相關文章