LWN: 基于io_uring的用戶空間塊設(shè)備驅(qū)動!
關(guān)注了就能看到更多這么棒的文章哦~
An io_uring-based user-space block driver
By Jonathan Corbet
August 8, 2022
DeepL assisted translation
https://lwn.net/Articles/903855/
在 6.0 合并窗口期間,很容易會忽略掉新加入的 ublk 驅(qū)動;它被深埋在 io_uring 的 pull request 之中,而且完全沒有任何文檔能提示我們需要對它進(jìn)行額外的研究。Ublk 的目標(biāo)是促進(jìn)在用戶空間中實(shí)現(xiàn)高性能的 block driver。因此,它使用了 io_uring 跟內(nèi)核進(jìn)行通信。這個驅(qū)動目前被認(rèn)為是實(shí)驗(yàn)性的;如果最終成功了,那么可能會是一個預(yù)兆,預(yù)示著將來內(nèi)核會有巨變。
編者已經(jīng)花了相當(dāng)多的時間去研究 ublk 驅(qū)動的源代碼,以及實(shí)現(xiàn)了用戶空間組件的 ubdsrv server。從這個未加注釋以及缺乏元音的代碼中探索出來的景象,很可能在某些細(xì)節(jié)上是不正確的,不過整體理解應(yīng)該相當(dāng)接近現(xiàn)實(shí)了。
How ublk works
ublk 驅(qū)動首先創(chuàng)建了一個叫做 /dev/ublk-control 的特殊設(shè)備。用戶空間的服務(wù)器進(jìn)程(可以是很多個進(jìn)程)通過打開該設(shè)備并建立一個 io_uring 的 ring 來與之通信。在這個層面的操作,基本上都是 ioctl() 命令,但是/dev/ublk-control 并沒有 ioctl() 處理程序。相反,所有的操作都是通過 io_uring 的命令來發(fā)送的。既然最終目的是在 io_uring 的基礎(chǔ)上實(shí)現(xiàn)一個設(shè)備,那么確實(shí)沒有理由不從一開始就直接使用 io_uring 的功能。
服務(wù)器進(jìn)程通常會以 UBLK_CMD_ADD_DEV 命令作為開始;正如人們所期望的,它可以將一個新的 ublk 設(shè)備添加到系統(tǒng)中。server 進(jìn)程可以對這個設(shè)備的各個方面進(jìn)行描述,包括它聲稱要實(shí)現(xiàn)的硬件隊(duì)列有幾個、塊大小、最大傳輸大小以及設(shè)備可以容納的 block 數(shù)量。在這個命令成功之后,在 ublk 驅(qū)動看來,就已經(jīng)有這個設(shè)備了,并且可以通過/dev/ublkcN 來訪問到,其中 N 是創(chuàng)建設(shè)備時所返回的設(shè)備 ID。但是,該設(shè)備此時尚未被添加到 block layer 中。
server 進(jìn)程應(yīng)該打開新增的 /dev/ublkcN 設(shè)備來進(jìn)行如下步驟,其中第一個步驟是用 mmap()調(diào)用將設(shè)備上的一個區(qū)域映射到 server 的地址空間內(nèi)。這個區(qū)域是描述 I/O 請求的 ublksrv_io_desc 結(jié)構(gòu)的數(shù)組:
struct ublksrv_io_desc {/* op: bit 0-7, flags: bit 8-31 */__u32 op_flags;__u32 nr_sectors;__u64 start_sector;__u64 addr; };
后續(xù)的 I/O 請求通知將從 io_uring 接收到。為了達(dá)到這個目的,服務(wù)器必須在新創(chuàng)建的設(shè)備上排隊(duì)等候一組 UBLK_IO_FETCH_REQ 請求;通常情況下,為設(shè)備所聲明的每個 "hardware queue"都會有一個隊(duì)列,這也可能是跟 server 內(nèi)運(yùn)行的線程數(shù)量一一對應(yīng)的。這個請求中還必須提供一個 memory buffer,用來容納設(shè)備創(chuàng)建時所聲明的 max request size 的數(shù)量。
在這些設(shè)置完成之后,可以使用 UBLK_CMD_START_DEV 操作來讓 ublk 驅(qū)動真正創(chuàng)建一個對系統(tǒng)其他部分可見的塊設(shè)備。當(dāng) block 子系統(tǒng)向這個設(shè)備發(fā)送一個請求時,隊(duì)列中的某個 UBLK_IO_FETCH_REQ 操作就會完成。返回給用戶空間 server 進(jìn)程的 completion 數(shù)據(jù)中就包括了描述該請求的 ublkserv_io_desc 結(jié)構(gòu)的索引,server 進(jìn)程現(xiàn)在應(yīng)該執(zhí)行該請求。對于一個寫請求來說,需要寫入的數(shù)據(jù)就會放在 server 所提供的 buffer 中;對于讀請求來說,數(shù)據(jù)應(yīng)該會放在同一個 buffer 中。
在這個操作完成后,server 必須通知內(nèi)核這個進(jìn)展;也就是通過在 ring 中放一個 UBLK_IO_COMMIT_AND_FETCH_REQ 操作來進(jìn)行通知的。它會把操作的結(jié)果反饋給 block 子系統(tǒng),但同時也會把 buffer 放到隊(duì)列里去準(zhǔn)備接收下一個請求,從而避免了還需要專門進(jìn)行這個操作。
還有 UBLK_CMD_STOP_DEV 和 UBLK_CMD_DEL_DEV 操作來讓現(xiàn)有的設(shè)備消失,還有幾個其他操作用來查詢現(xiàn)有設(shè)備的信息。還有一些細(xì)節(jié)在這里并沒有涉及到,這主要是為了提高性能。此外,配置 ublk 協(xié)議的目的是要實(shí)現(xiàn) zero-copy I/O,但在目前的代碼中沒有實(shí)現(xiàn)。
server 代碼中實(shí)現(xiàn)了兩個 target:null 和 loop。正如人們所期望的,null 這個 target 是一個過于復(fù)雜的、針對 block 設(shè)備的/dev/null;它沒有什么用,但使人們可以摒棄不相干的細(xì)節(jié)來直接看到這個驅(qū)動的工作效果。loop 這個 target 使用了一個現(xiàn)有的文件作為虛擬 block 設(shè)備的備份存儲。根據(jù)作者 Ming Lei 的說法,使用這種循環(huán)實(shí)現(xiàn),"性能甚至優(yōu)于具有相同設(shè)置的內(nèi)核 loop 設(shè)備"。
Implications
人們可能會問,我們?yōu)槭裁匆鲞@項(xiàng)工作(而且顯然得到了 Red Hat 的支持);如果世界一直在吵著要一個基于 io_uring 的、用戶空間的、更快的 loop block 設(shè)備,其實(shí)它就已經(jīng)悄悄地出現(xiàn)了。patch 的 cover letter 中提到的一個好處是,block 驅(qū)動代碼的開發(fā)可以更容易地在用戶空間完成。另一個好處是高性能的 qcow2 支持。patch cover letter 還引用了其他開發(fā)者的說法,希望有一個快速的用戶空間 block 設(shè)備的機(jī)制。
不過,一個有趣的問題是,這種機(jī)制是否最終會促進(jìn)一些設(shè)備驅(qū)動挪到內(nèi)核之外,也許不僅僅局限在 block 設(shè)備驅(qū)動領(lǐng)域。將設(shè)備驅(qū)動放到用戶空間的代碼中,是一些 security-system 設(shè)計(jì)中的基本觀點(diǎn),包括 microkernel 系統(tǒng)。但是,這些設(shè)計(jì)方案總是有兩個組件之間的通信開銷的問題,尤其是它們不再在同一地址空間內(nèi)運(yùn)行。Io_uring 可能是解決這個問題的一個令人信服的答案。
如果今后真的如此發(fā)展了,那么未來的內(nèi)核可能與我們現(xiàn)在的內(nèi)核有很大的不同;它們可能更小,許多復(fù)雜的邏輯在獨(dú)立的用戶空間組件中運(yùn)行。這是否是 Lei 對 ublk 的愿景的一部分?目前還不得而知,而且可能最終完全不會走到這一步。但 ublk 顯然是一個有趣的實(shí)驗(yàn),可能會導(dǎo)致下一步的重大變動。不過,在今后統(tǒng)治世界之前,還是需要先把自己的文檔補(bǔ)充起來。
全文完
LWN 文章遵循 CC BY-SA 4.0 許可協(xié)議。
長按下面二維碼關(guān)注,關(guān)注 LWN 深度文章以及開源社區(qū)的各種新近言論~
