Android核心技術—核心(Linux) 的IO棧

語言: CN / TW / HK

theme: smartblue

簡述

Linux的IO路徑可能是Linux系統中最紛繁複雜的模組了,而它又是如此的重要,直接決定了系統的效能。 接下來我們來看一張熟悉的老圖:

image.png

由圖可見,從系統呼叫的介面再往下,Linux下的IO棧致大致有幾個層次:

應用程式

這沒什麼好說的,通過相關係統呼叫(如open/read/write)發起IO請求,屬於IO請求的源頭;

檔案系統

應用程式的請求直接到達檔案系統層。檔案系統又分為VFS和具體檔案系統(ext3、ext4等),VFS對應用層提供統一的訪問介面,而ext3等檔案系統則具體實現了這些介面。另外,為了提供IO效能,在該層還實現了諸如page cache等功能。同時,使用者也可以選擇繞過page cache,而是直接使用direct模式進行IO(如資料庫)。

塊裝置層

檔案系統將IO請求打包提交給塊裝置層,該層會對這些IO請求作合併、排序、排程等,然後以新的格式發往更底層。在該層次上實現了多種電梯排程演算法,如cfq、deadline等。

SCSI層:

塊裝置層將請求發往SCSI層,SCSI就開始真實處理這些IO請求,但是SCSI層又對其內部按照功能劃分了不同層次: * SCSI高層:高層驅動負責管理disk,接收塊裝置層發出的IO請求,打包成SCSI層可識別的命令格式,繼續往下發; * SCSI中層:中層負責通用功能,如錯誤處理,超時重試等; * SCSI低層:底層負責識別物理裝置,將其抽象提供給高層,同時接收高層派發的scsi命令,交給物理裝置處理。

各層介面

清晰的介面能讓複雜的系統變得容易理解和維護。

應用程式 => 檔案系統

做開發的人可能都應該瞭解,通過諸如open/read/pread/write/writev等POSIX介面來呼叫檔案系統各種功能。由於其普遍性,在這裡就不一一描述介面形式了。

檔案系統 => 塊裝置層

這裡我們將檔案系統當成一個整體,並不區分VFS和具體檔案系統。塊裝置層對檔案系統提供的介面為submit_bio(),介面形式如下:

void submit_bio(int rw, struct bio *bio)

檔案系統向塊裝置提交的每個bio請求都設定了完成回撥函式,記錄在bio->bi_end_io。bio請求完成後,通過該欄位通知檔案系統。

塊裝置層 => SCSI上層

scsi_reuqest_fn()和struct request_queue。 老實來說,塊裝置層和SCSI上層之間分的沒有那麼清楚,耦合的稍微緊密,塊裝置層看到的IO請求結構是request。而SCSI層看到的IO命令則是scsi_cmnd

每個scsi裝置(如scsi disk)均維護了一個請求佇列request_queue,而每個scsi裝置對上層呈現的其實是一個塊裝置。因此,塊裝置和scsi裝置有著天然的聯絡,request_queue則是連線塊裝置層和SCSI層的紐帶。塊裝置層對request請求最終會派發至request_queue中。而在特定條件下通過洩流機制將request_queue中積攢的request派發至SCSI層處理。而洩流的實際處理過程就是scsi_request_fn()函式,因此說它是塊裝置層和SCSI上層的介面也不為過,雖然不是特別準確。在scsi_reuqest_fn內會進行request至scsi_cmnd的轉換。

SCSI上層 => SCSI中間層

SCSI上層在收到塊裝置層發起的scsi命令後馬不停蹄又將其轉發至SCSI中間層。SCSI上層至SCSI中間層的介面是 scsi_dispatch_cmd

static void scsi_request_fn(struct request_queue *q) { ...... // 設定scsi命令完成回撥函式 cmd->scsi_done = scsi_done; rtn = scsi_dispatch_cmd(cmd); ...... }

SCSI中間層 => SCSI低層

SCSI中間層收到塊裝置層發下來的scsi_cmnd命令後,中間層作自己處理後,然後再將該命令繼續往下傳遞,接下來該命令到了scsi底層,而傳遞的介面是 queuecommand()

static int scsi_dispatch_cmd(struct scsi_cmnd *cmd) { ...... rtn = host->hostt->queuecommand(host, cmd); ...... }

host為該裝置所屬的主機介面卡結構。任何一個SCSI主機介面卡都需要實現queuecommand介面。注意這個提交過程是非同步的,無需等待該命令完成便直接返回。 scsi 命令完成後,會通過記錄在命令內完成函式回撥上層處理,具體是cmd->scsi_done

linux io棧(讀寫流程)

linux IO 儲存棧分為7層:

VFS 虛擬檔案層: 在各個具體的檔案系統上建立一個抽象層,遮蔽不同檔案系統的差異。 PageCache 層: 為了緩解核心與磁碟速度的巨大差異。 對映層 Mapping Layer: 核心必須從塊裝置上讀取資料,Mapping layer 要確定在物理裝置上的位置。 通用塊層: 通用塊層處理來自系統其他元件發出塊裝置請求。包含了塊裝置操作的一些通用函式和資料結構。 IO 排程層: IO 排程層主要是為了減少磁碟IO 的次數,增大磁碟整體的吞吐量,佇列中多個bio 進行排序和合並。 塊裝置驅動層: 每一類裝置都有其驅動程式,負責裝置的讀寫。 物理裝置層: 物理裝置層有 HDD,SSD,Nvme 等磁碟裝置。 PageCache 層: 兩種策略: write back : 寫入PageCache 便返回,不等資料落盤。 write through: 同步等待資料落盤。

讀流程

  • (1)系統呼叫read()會觸發相應的VFS(Virtual Filesystem Switch)函式,傳遞的引數 有檔案描述符和檔案偏移量。
  • (2)VFS確定請求的資料是否已經在記憶體緩衝區中;若資料不在記憶體中,確定如何執行讀 操作。
  • (3)假設核心必須從塊裝置上讀取資料,這樣核心就必須確定資料在物理裝置上的位置。 這由對映層(Mapping Layer)來完成。
  • (4)此時核心通過通用塊裝置層(Generic Block Layer)在塊裝置上執行讀操作,啟動I/O 操作,傳輸請求的資料。
  • (5)在通用塊裝置層之下是I/O排程層(I/O Scheduler Layer),根據核心的排程策略,對 等待的I/O等待佇列排序。
  • (6)最後,塊裝置驅動(Block Device Driver)通過向磁碟控制器傳送相應的命令,執行 真正的資料傳輸。

寫流程

write()—>sys_write()—>vfs_write()—>通用塊層—>IO排程層—>塊裝置驅動層—>塊裝置

塊裝置

系統中能夠隨機訪問固定大小資料片(chunk)的裝置稱為塊裝置,這些資料片就稱作 塊。最常見的塊裝置是硬碟,除此之外,還有CD-ROM驅動器和SSD等。它們通常安裝文 件系統的方式使用。

有關Android開發中的核心(Linux) 的IO棧學習,這裡就做一部分淺析,如需深入瞭解或其他問題可以點下方連結

傳送直達↓↓↓ docs.qq.com/doc/DUkNRVF…

總結

  1. IO 棧:VFS - 檔案系統 - 塊層 - SCSI 驅動層
  2. VFS 負責通用的檔案抽象語義,管理並切換檔案系統;
  3. 檔案系統負責抽象出“檔案的概念”,維護“檔案”資料到塊層的位置對映,怎麼擺放資料,怎麼抽象檔案都是檔案系統說了算;
  4. 塊層對底層硬體裝置做一層統一的抽象,最重要的是做一些IO 排程的策略。比如,儘可能收集批量 IO 聚合下發,讓 IO 儘可能的順序,合併 IO 請求減少 IO 次數等等;
  5. SCSI 層則是負責最後對硬體磁碟的對接,驅動層,本質就是個翻譯器
  6. 檔案的 buffer write 要實現 .write_begin,.write_page 等介面,前者用於分配 page 並繫結塊層物理空間,後者使用者非同步回刷的時候呼叫(注意,非常規的優化在回刷的時候才去繫結物理空間);
  7. 檔案系統 .write_begin 呼叫分配物理位置的時候依賴於get_block的實現,物理位置分配好之後,page 會對應到特定的 buffer head 結構,buffer head 結構則對應到具體的塊裝置位置;
  8. direct IO 直接在使用者路徑上刷資料到磁碟,不走 PageCache 的邏輯,但並不是所有檔案系統都會實現它。