使用者空間塊裝置驅動程式 (ublk driver)¶
概述¶
ublk 是一個通用框架,用於從使用者空間實現塊裝置邏輯。其背後的動機是將虛擬塊驅動程式(如 loop、nbd 和類似程式)移動到使用者空間,這會非常有幫助。它可以幫助實現新的虛擬塊裝置,例如 ublk-qcow2(已經有幾次嘗試在核心中實現 qcow2 驅動程式)。
使用者空間塊裝置很有吸引力,因為
它們可以用多種程式語言編寫。
它們可以使用核心中不可用的庫。
可以使用應用程式開發人員熟悉的工具進行除錯。
崩潰不會導致機器核心崩潰。
與核心程式碼中的錯誤相比,錯誤可能具有較低的安全影響。
它們可以獨立於核心進行安裝和更新。
它們可用於使用使用者指定的引數/設定為測試/除錯目的輕鬆地模擬塊裝置
ublk 塊裝置 (/dev/ublkb*) 由 ublk 驅動程式新增。裝置上的任何 IO 請求都將轉發到 ublk 使用者空間程式。為方便起見,本文件中,ublk 伺服器 指的是通用 ublk 使用者空間程式。ublksrv [1] 是此類實現之一。它提供了 libublksrv [2] 庫,用於方便地開發特定的使用者塊裝置,同時還包括通用型別的塊裝置,例如 loop 和 null。Richard W.M. Jones 基於 libublksrv [2] 編寫了使用者空間 nbd 裝置 nbdublk [3]。
在 IO 由使用者空間處理後,結果會提交回驅動程式,從而完成請求週期。透過這種方式,任何特定的 IO 處理邏輯都完全由使用者空間完成,例如 loop 的 IO 處理、NBD 的 IO 通訊或 qcow2 的 IO 對映。
/dev/ublkb* 由基於 blk-mq 請求的驅動程式驅動。每個請求都由一個佇列範圍內的唯一標籤分配。ublk 伺服器也為每個 IO 分配唯一標籤,該標籤與 /dev/ublkb* 的 IO 進行 1:1 對映。
IO 請求轉發和 IO 處理結果提交都透過 io_uring 直通命令完成;這就是 ublk 也是基於 io_uring 的塊驅動程式的原因。已經觀察到,使用 io_uring 直通命令可以提供比塊 IO 更好的 IOPS;這就是 ublk 是使用者空間塊裝置的高效能實現之一的原因:不僅 IO 請求通訊由 io_uring 完成,而且 ublk 伺服器中首選的 IO 處理也是基於 io_uring 的方法。
ublk 提供控制介面來設定/獲取 ublk 塊裝置引數。該介面是可擴充套件的,並且 kabi 相容:基本上任何 ublk 請求佇列的引數或 ublk 通用功能引數都可以透過該介面進行設定/獲取。因此,ublk 是通用的使用者空間塊裝置框架。例如,從使用者空間設定具有指定塊引數的 ublk 裝置很容易。
使用 ublk¶
ublk 需要使用者空間 ublk 伺服器來處理實際的塊裝置邏輯。
以下是使用 ublksrv 提供基於 ublk 的 loop 裝置的示例。
新增裝置
ublk add -t loop -f ublk-loop.img
使用 xfs 格式化,然後使用它
mkfs.xfs /dev/ublkb0 mount /dev/ublkb0 /mnt # do anything. all IOs are handled by io_uring ... umount /mnt
列出具有其資訊的裝置
ublk list
刪除裝置
ublk del -a ublk del -n $ublk_dev_id
有關使用詳情,請參見 ublksrv [4] 的 README。
設計¶
控制平面¶
ublk 驅動程式提供全域性 misc 裝置節點 (/dev/ublk-control),用於藉助幾個控制命令管理和控制 ublk 裝置
UBLK_CMD_ADD_DEV新增 ublk 字元裝置 (
/dev/ublkc*),該裝置與 ublk 伺服器就 IO 命令通訊進行對話。基本裝置資訊與此命令一起傳送。它設定ublksrv_ctrl_dev_info的 UAPI 結構,例如nr_hw_queues、queue_depth和最大 IO 請求緩衝區大小,這些資訊與驅動程式協商併發送回伺服器。完成此命令後,基本裝置資訊將不可變。UBLK_CMD_SET_PARAMS/UBLK_CMD_GET_PARAMS設定或獲取裝置的引數,這些引數可以是通用功能相關的,也可以是請求佇列限制相關的,但不能是 IO 邏輯特定的,因為驅動程式不處理任何 IO 邏輯。必須在傳送
UBLK_CMD_START_DEV之前傳送此命令。UBLK_CMD_START_DEV在伺服器準備好使用者空間資源(例如建立 I/O 處理程式執行緒 & io_uring 以處理 ublk IO)後,此命令將傳送到驅動程式以分配 & 公開
/dev/ublkb*。透過UBLK_CMD_SET_PARAMS設定的引數將應用於建立裝置。UBLK_CMD_STOP_DEV停止
/dev/ublkb*上的 IO 並刪除裝置。當此命令返回時,ublk 伺服器將釋放資源(例如銷燬 I/O 處理程式執行緒 & io_uring)。UBLK_CMD_DEL_DEV刪除
/dev/ublkc*。當此命令返回時,可以重用分配的 ublk 裝置號。UBLK_CMD_GET_QUEUE_AFFINITY新增
/dev/ublkc時,驅動程式會建立塊層 tagset,以便每個佇列的親和性資訊都可用。伺服器傳送UBLK_CMD_GET_QUEUE_AFFINITY以檢索佇列親和性資訊。它可以有效地設定每個佇列的上下文,例如將仿射 CPU 與 IO pthread 繫結,並嘗試在 IO 執行緒上下文中分配緩衝區。UBLK_CMD_GET_DEV_INFO用於透過
ublksrv_ctrl_dev_info檢索裝置資訊。伺服器有責任在使用者空間中儲存 IO 目標特定資訊。UBLK_CMD_GET_DEV_INFO2與UBLK_CMD_GET_DEV_INFO的用途相同,但是 ublk 伺服器必須提供/dev/ublkc*的字元裝置路徑,供核心執行許可權檢查,並且此命令是為支援非特權 ublk 裝置而新增的,並與UBLK_F_UNPRIVILEGED_DEV一起引入。只有擁有請求裝置的使用者才能檢索裝置資訊。如何處理使用者空間/核心相容性
如果核心能夠處理
UBLK_F_UNPRIVILEGED_DEV
如果 ublk 伺服器支援
UBLK_F_UNPRIVILEGED_DEVublk 伺服器應傳送
UBLK_CMD_GET_DEV_INFO2,因為每當非特權應用程式需要查詢當前使用者擁有的裝置時,應用程式都不知道是否設定了UBLK_F_UNPRIVILEGED_DEV,因為功能資訊是無狀態的,並且應用程式應始終透過UBLK_CMD_GET_DEV_INFO2檢索它如果 ublk 伺服器不支援
UBLK_F_UNPRIVILEGED_DEVUBLK_CMD_GET_DEV_INFO始終傳送到核心,並且 UBLK_F_UNPRIVILEGED_DEV 的功能對於使用者不可用如果核心無法處理
UBLK_F_UNPRIVILEGED_DEV
如果 ublk 伺服器支援
UBLK_F_UNPRIVILEGED_DEV首先嚐試
UBLK_CMD_GET_DEV_INFO2,並且將失敗,然後由於無法設定UBLK_F_UNPRIVILEGED_DEV,因此需要重試UBLK_CMD_GET_DEV_INFO如果 ublk 伺服器不支援
UBLK_F_UNPRIVILEGED_DEVUBLK_CMD_GET_DEV_INFO始終傳送到核心,並且UBLK_F_UNPRIVILEGED_DEV的功能對於使用者不可用UBLK_CMD_START_USER_RECOVERY如果啟用了
UBLK_F_USER_RECOVERY功能,則此命令有效。在舊程序已退出、ublk 裝置已靜默並且/dev/ublkc*已釋放後,將接受此命令。使用者應在他啟動重新開啟/dev/ublkc*的新程序之前傳送此命令。當此命令返回時,ublk 裝置已準備好用於新程序。UBLK_CMD_END_USER_RECOVERY如果啟用了
UBLK_F_USER_RECOVERY功能,則此命令有效。在 ublk 裝置已靜默並且新程序已開啟/dev/ublkc*並且所有 ublk 佇列都已準備好之後,將接受此命令。當此命令返回時,ublk 裝置將被取消靜默,並且新的 I/O 請求將傳遞到新程序。使用者恢復功能描述
為使用者恢復添加了三個新功能:
UBLK_F_USER_RECOVERY、UBLK_F_USER_RECOVERY_REISSUE和UBLK_F_USER_RECOVERY_FAIL_IO。要在 ublk 伺服器退出後啟用 ublk 裝置的恢復,ublk 伺服器應在建立裝置時指定UBLK_F_USER_RECOVERY標誌。ublk 伺服器還可以指定UBLK_F_USER_RECOVERY_REISSUE和UBLK_F_USER_RECOVERY_FAIL_IO中的至多一個,以修改在 ublk 伺服器正在死亡/已死亡時如何處理 I/O(這在驅動程式程式碼中稱為nosrv情況)。僅設定
UBLK_F_USER_RECOVERY後,在 ublk 伺服器退出後,ublk 在整個恢復階段都不會刪除/dev/ublkb*,並且 ublk 裝置 ID 將保留。ublk 伺服器有責任透過自己的知識恢復裝置上下文。尚未傳送到使用者空間的請求將被重新排隊。已傳送到使用者空間的請求將被中止。另外設定
UBLK_F_USER_RECOVERY_REISSUE後,在 ublk 伺服器退出後,與UBLK_F_USER_RECOVERY相反,已傳送到使用者空間的請求將被重新排隊,並且在處理UBLK_CMD_END_USER_RECOVERY後將重新發送到新程序。UBLK_F_USER_RECOVERY_REISSUE專為容忍雙寫的後端而設計,因為驅動程式可能會兩次發出相同的 I/O 請求。它可能對只讀 FS 或 VM 後端有用。另外設定
UBLK_F_USER_RECOVERY_FAIL_IO後,在 ublk 伺服器退出後,已傳送到使用者空間的請求將失敗,隨後發出的任何請求也是如此。應用程式會看到一系列 I/O 錯誤,直到新的 ublk 伺服器恢復裝置為止,並且會持續針對設定此標誌的裝置發出 I/O。
非特權 ublk 裝置透過傳遞 UBLK_F_UNPRIVILEGED_DEV 支援。設定此標誌後,所有控制命令都可以由非特權使用者傳送。除了 UBLK_CMD_ADD_DEV 命令之外,ublk 驅動程式還會對所有其他控制命令執行對指定字元裝置 (/dev/ublkc*) 的許可權檢查,為此,字元裝置的路徑必須在這些命令的有效負載中從 ublk 伺服器提供。透過這種方式,ublk 裝置成為容器感知的,並且在一個容器中建立的裝置只能在此容器內部進行控制/訪問。
資料平面¶
ublk 伺服器應建立專用執行緒來處理 I/O。每個執行緒都應具有其自己的 io_uring,透過該 io_uring 通知它有新的 I/O,並且透過該 io_uring 它可以完成 I/O。這些專用執行緒應專注於 IO 處理,並且不應處理任何控制和管理任務。
The's IO 由唯一標籤分配,該標籤與 /dev/ublkb* 的 IO 請求進行 1:1 對映。
ublksrv_io_desc 的 UAPI 結構被定義為描述來自驅動程式的每個 IO。在 /dev/ublkc* 上提供了一個固定的 mmapped 區域(陣列),用於將 IO 資訊匯出到伺服器;例如 IO 偏移量、長度、OP/標誌和緩衝區地址。每個 ublksrv_io_desc 例項都可以透過佇列 ID 和 IO 標籤直接索引。
以下 IO 命令透過 io_uring 直通命令進行通訊,並且每個命令僅用於轉發 IO 並使用命令資料中指定的 IO 標籤提交結果
UBLK_IO_FETCH_REQ從伺服器 IO pthread 傳送,用於提取傳送到
/dev/ublkb*的未來傳入 IO 請求。此命令僅從伺服器 IO pthread 傳送一次,用於 ublk 驅動程式設定 IO 轉發環境。一旦執行緒針對給定的 (qid,tag) 對發出此命令,該執行緒就會將自己註冊為該 I/O 的守護程序。將來,只有該 I/O 的守護程序才允許針對 I/O 發出命令。如果任何其他執行緒嘗試針對該執行緒不是守護程序的 (qid,tag) 對發出命令,則該命令將失敗。只有透過恢復才能重置守護程序。
每個 (qid,tag) 對都具有其自己的獨立守護程序任務的能力由
UBLK_F_PER_IO_DAEMON功能指示。如果驅動程式不支援此功能,則守護程序必須是每個佇列的 - 即,與單個 qid 關聯的所有 I/O 都必須由同一任務處理。UBLK_IO_COMMIT_AND_FETCH_REQ當 IO 請求傳送到
/dev/ublkb*時,驅動程式會將 IO 的ublksrv_io_desc儲存到指定的對映區域;然後,此 IO 標籤的先前接收到的 IO 命令(UBLK_IO_FETCH_REQ或UBLK_IO_COMMIT_AND_FETCH_REQ))完成,因此伺服器透過 io_uring 獲得 IO 通知。在伺服器處理 IO 後,其結果透過發回
UBLK_IO_COMMIT_AND_FETCH_REQ提交回驅動程式。一旦 ublkdrv 收到此命令,它就會解析結果並完成對/dev/ublkb*的請求。同時,設定環境以使用相同的 IO 標籤提取將來的請求。也就是說,UBLK_IO_COMMIT_AND_FETCH_REQ被重用於提取請求和提交回 IO 結果。UBLK_IO_NEED_GET_DATA啟用
UBLK_F_NEED_GET_DATA後,WRITE 請求將首先發送到 ublk 伺服器,而無需資料複製。然後,ublk 伺服器的 IO 後端接收請求,並且它可以分配資料緩衝區並將地址嵌入到此新 IO 命令中。在核心驅動程式獲取命令後,資料複製將從請求頁面完成到此後端的緩衝區。最後,後端再次接收到要寫入資料的請求,並且它可以真正處理該請求。UBLK_IO_NEED_GET_DATA添加了一個額外的往返過程和一個 io_uring_enter() 系統呼叫。任何認為這可能會降低效能的使用者都不應啟用 UBLK_F_NEED_GET_DATA。預設情況下,ublk 伺服器為每個 IO 預分配 IO 緩衝區。任何新專案都應嘗試使用此緩衝區與 ublk 驅動程式通訊。但是,現有專案可能會中斷或無法使用新的緩衝區介面;這就是新增此命令以實現向後相容性的原因,以便現有專案仍然可以使用現有緩衝區。ublk 伺服器 IO 緩衝區和 ublk 塊 IO 請求之間的資料複製
驅動程式需要首先將塊 IO 請求頁面複製到伺服器緩衝區(頁面)中,以便在將即將到來的 IO 通知伺服器之前用於 WRITE,以便伺服器可以處理 WRITE 請求。
當伺服器處理 READ 請求並將
UBLK_IO_COMMIT_AND_FETCH_REQ傳送到伺服器時,ublkdrv 需要將讀取的伺服器緩衝區(頁面)複製到 IO 請求頁面。
零複製¶
ublk 零複製依賴於 io_uring 的固定核心緩衝區,該緩衝區提供了兩個 API:io_buffer_register_bvec() 和 io_buffer_unregister_bvec。
ublk 添加了 UBLK_IO_REGISTER_IO_BUF 的 IO 命令來呼叫 io_buffer_register_bvec(),以便 ublk 伺服器將客戶端請求緩衝區註冊到 io_uring 緩衝區表中,然後 ublk 伺服器可以使用已註冊的緩衝區索引提交 io_uring IO。IO 命令 UBLK_IO_UNREGISTER_IO_BUF 呼叫 io_buffer_unregister_bvec() 來登出緩衝區,保證在呼叫 io_buffer_register_bvec() 和 io_buffer_unregister_bvec() 之間是活動的。任何支援這種核心緩衝區的 io_uring 操作都將獲取對緩衝區的一個引用,直到操作完成。
實現零複製或使用者複製的 ublk 伺服器必須具有 CAP_SYS_ADMIN 並且是可信的,因為 ublk 伺服器有責任確保 IO 緩衝區填充了資料以處理讀取命令,並且 ublk 伺服器必須在處理 READ 命令時將正確的結果返回給 ublk 驅動程式,並且結果必須與填充到 IO 緩衝區的位元組數匹配。否則,未初始化的核心 IO 緩衝區將被公開給客戶端應用程式。
ublk 伺服器需要將 struct ublk_param_dma_align 的引數與後端對齊,以便零複製能夠正確工作。
為了達到最佳 IO 效能,ublk 伺服器應將其 struct ublk_param_segment 的分段引數與後端對齊,以避免不必要的 IO 拆分,這通常會損害 io_uring 效能。
自動緩衝區註冊¶
UBLK_F_AUTO_BUF_REG 功能自動處理 I/O 請求的緩衝區註冊和登出,從而簡化了緩衝區管理過程並減少了 ublk 伺服器實現中的開銷。
這是用於零複製的另一個功能標誌,它與 UBLK_F_SUPPORT_ZERO_COPY 相容。
功能概述¶
此功能會在將 I/O 命令傳遞到 ublk 伺服器之前,自動將請求緩衝區註冊到 io_uring 上下文中,並在完成 I/O 命令時登出它們。這消除了透過 UBLK_IO_REGISTER_IO_BUF 和 UBLK_IO_UNREGISTER_IO_BUF 命令手動進行緩衝區註冊/登出的需要,然後 ublk 伺服器中的 IO 處理可以避免依賴於兩個 uring_cmd 操作。
如果這些 IO 之間存在任何依賴關係,則無法同時向 io_uring 發出 IO。因此,這種方式不僅簡化了 ublk 伺服器的實現,而且透過消除對緩衝區註冊和登出命令的依賴關係,使併發 IO 處理成為可能。
使用要求¶
ublk 伺服器必須在用於
UBLK_IO_FETCH_REQ和UBLK_IO_COMMIT_AND_FETCH_REQ的同一io_ring_ctx上建立稀疏緩衝區表。如果在不同的io_ring_ctx上發出 uring_cmd,則需要手動登出緩衝區。緩衝區註冊資料必須透過 uring_cmd 的
sqe->addr傳遞,並具有以下結構struct ublk_auto_buf_reg { __u16 index; /* Buffer index for registration */ __u8 flags; /* Registration flags */ __u8 reserved0; /* Reserved for future use */ __u32 reserved1; /* Reserved for future use */ };ublk_auto_buf_reg_to_sqe_addr() 用於將上述結構轉換為
sqe->addr。ublk_auto_buf_reg中的所有保留欄位都必須清零。可選標誌可以透過
ublk_auto_buf_reg.flags傳遞。
回退行為¶
如果自動緩衝區註冊失敗
啟用
UBLK_AUTO_BUF_REG_FALLBACK時uring_cmd 已完成
在
ublksrv_io_desc.op_flags中設定了UBLK_IO_F_NEED_REG_BUFublk 伺服器必須手動處理故障,例如手動註冊緩衝區,或者使用使用者複製功能檢索資料以處理 ublk IO
如果未啟用回退
ublk I/O 請求靜默失敗
uring_cmd 將不會完成
限制¶
所有操作都需要相同的
io_ring_ctx在回退情況下可能需要手動緩衝區管理
io_ring_ctx 緩衝區表的最大大小為 16K,如果此單個 io_ring_ctx 處理過多的 ublk 裝置,並且每個裝置都具有非常大的佇列深度,則可能不夠