不可變 biovec 和 biovec 迭代器¶
Kent Overstreet <kmo@daterainc.com>
從 3.13 開始,biovec 在提交 bio 後不應再修改。相反,我們有一個新的結構 bvec_iter,它表示 biovec 的一個範圍 - 迭代器會隨著 bio 的完成而修改,而不是 biovec。
更具體地說,舊的程式碼需要部分完成 bio,會更新 bi_sector 和 bi_size,並將 bi_idx 前進到下一個 biovec。 如果最終停留在 biovec 的中間位置,它將遞增 bv_offset 並遞減 bv_len 以表示在該 biovec 中完成的位元組數。
在新的方案中,為了部分完成 bio 而必須更改的所有內容都隔離到結構 bvec_iter 中:bi_sector、bi_size 和 bi_idx 已移動到那裡;並且,struct bvec_iter 沒有修改 bv_offset 和 bv_len,而是具有 bi_bvec_done,它表示當前 bvec 中已完成的位元組數。
這裡有很多新的 helper 宏用於隱藏 gory 細節 - 特別是,呈現部分完成的 biovec 的錯覺,以便普通程式碼不必處理 bi_bvec_done。
驅動程式程式碼不應再直接引用 biovec; 我們現在有 bio_iovec() 和 bio_iter_iovec() 宏,它們返回字面量的 struct biovec,由原始 biovec 構建,但考慮到 bi_bvec_done 和 bi_size。
bio_for_each_segment() 已更新為採用 bvec_iter 引數而不是整數(該整數對應於 bi_idx); 對於很多程式碼,轉換隻需要更改 bio_for_each_segment() 引數的型別。
bvec_iter 的前進使用 bio_advance_iter() 完成;
bio_advance()是 bio_advance_iter() 的包裝器,它在 bio->bi_iter 上執行,並且如果存在,還會前進 bio 完整性的 iter。有一個較低級別的提前函式 - bvec_iter_advance() - 它接受指向 biovec 的指標,而不是 bio; 這由 bio 完整性程式碼使用。
從 5.12 開始,不支援 bv_len 為零的 bvec 段。
這一切對我們有什麼好處?¶
擁有一個真正的迭代器,並使 biovec 不可變,有很多優點
以前,當您沒有一次處理一個 bvec 時,迭代 bio 非常笨拙 - 例如,block/bio.c 中的
bio_copy_data(),它將一個 bio 的內容複製到另一個 bio 中。 因為 biovec 的大小不一定相同,所以舊程式碼非常棘手 - 它必須同時遍歷兩個不同的 bio,併為每個 bio 保留 bi_idx 和當前 biovec 的偏移量。新程式碼更加直接 - 看一看。 這種模式出現在很多地方; 很多驅動程式以前本質上是開放編碼 bvec 迭代器,並且具有通用實現大大簡化了大量程式碼。
以前,在 bio 完成後可能需要使用 biovec 的任何程式碼(可能將資料複製到其他地方,或者如果在發生錯誤時將其重新提交到其他地方)都必須儲存整個 bvec 陣列 - 同樣,這在很多地方都在做。
Biovec 可以在多個 bio 之間共享 - bvec iter 可以表示現有 biovec 的任意範圍,從 biovec 的中間開始和結束。 這使得可以有效地拆分任意 bio。 請注意,這意味著我們_只_使用 bi_size 來確定何時到達 bio 的末尾,而不是 bi_vcnt - 並且 bio_iovec() 宏在構造 biovec 時會考慮 bi_size。
現在拆分 bio 更加簡單。 舊的
bio_split()甚至不能在具有多個 bvec 的 bio 上執行! 現在,我們可以有效地拆分任意大小的 bio - 因為新的 bio 可以共享舊 bio 的 biovec。但是,必須小心確保在拆分後的 bio 仍在使用的同時不會釋放 biovec,以防原始 bio 首先完成。 拆分 bio 時使用
bio_chain()有助於解決此問題。現在可以完美地提交部分完成的 bio - 這偶爾會在堆疊塊驅動程式中出現,並且各種程式碼(例如 md 和 bcache)對此有一些難看的解決方法。
過去,提交部分完成的 bio 可以完美地用於_大多數_裝置,但是由於訪問原始 bvec 陣列是常態,因此並非所有驅動程式都會尊重 bi_idx,並且這些驅動程式會崩潰。 現在,由於所有驅動程式_必須_透過 bvec 迭代器 - 並且已經過稽核以確保它們是 - 因此可以完美地提交部分完成的 bio。
其他含義:¶
現在幾乎所有對 bi_idx 的使用都是不正確的並且已被刪除; 相反,之前您使用 bi_idx 的地方,現在可以使用 bvec_iter,可能會將其傳遞給其中一個 helper 宏。
即,而不是使用 bio_iovec_idx()(或 bio->bi_iovec[bio->bi_idx]),現在可以使用 bio_iter_iovec(),它接受 bvec_iter 並返回字面量的 struct bio_vec - 從原始 biovec 動態構造,但考慮到 bi_bvec_done(和 bi_size)。
驅動程式程式碼不能信任或依賴 bi_vcnt - 即任何實際上不擁有 bio 的程式碼。 原因有兩方面:首先,不再需要它來迭代 bio - 我們只使用 bi_size。 其次,當克隆 bio 並重用(原始 bio 的一部分)biovec 時,為了計算新 bio 的 bi_vcnt,我們將不得不迭代新 bio 中的所有 biovec - 這很愚蠢,因為它不是必需的。
所以,不要再使用 bi_vcnt 了。
當前的介面允許塊層根據需要拆分 bio,因此我們可以消除大量的複雜性,尤其是在堆疊驅動程式中。 建立 bio 的程式碼可以建立任何方便大小的 bio,更重要的是,堆疊驅動程式不必同時處理它們自己的 bio 大小限制和底層裝置的限制。 因此,無需為單個塊驅動程式定義 ->merge_bvec_fn() 回撥。
helper 的使用:¶
名稱字尾為 _all 的以下 helper 只能在非 BIO_CLONED bio 上使用。 它們通常由檔案系統程式碼使用。 驅動程式不應使用它們,因為 bio 可能在到達驅動程式之前已被拆分。
bio_for_each_segment_all()
bio_for_each_bvec_all()
bio_first_bvec_all()
bio_first_page_all()
bio_first_folio_all()
bio_last_bvec_all()
以下 helper 迭代單頁段。 傳遞的 ‘struct bio_vec’ 將在迭代期間包含一個單頁 IO 向量
bio_for_each_segment() bio_for_each_segment_all()
以下 helper 迭代多頁 bvec。 傳遞的 ‘struct bio_vec’ 將在迭代期間包含一個多頁 IO 向量
bio_for_each_bvec() bio_for_each_bvec_all() rq_for_each_bvec()