資料完整性

1. 簡介

現代檔案系統支援對資料和元資料進行校驗和計算,以防止資料損壞。然而,損壞的檢測是在讀取時進行的,這可能在資料寫入數月後才發生。屆時,應用程式試圖寫入的原始資料很可能已經丟失。

解決方案是確保磁碟實際儲存了應用程式想要儲存的資料。SCSI 系列協議(SBC 資料完整性欄位,SCC 保護提案)以及 SATA/T13(外部路徑保護)的最新增補都試圖透過增加對 I/O 中附加完整性元資料的支援來彌補這一不足。完整性元資料(或 SCSI 術語中的保護資訊)包括每個扇區的校驗和,以及一個遞增計數器,以確保各個扇區以正確的順序寫入。對於某些保護方案,還包括確保 I/O 寫入磁碟上的正確位置。

當前的儲存控制器和裝置實現了各種保護措施,例如校驗和和擦洗。但這些技術在各自獨立的域中工作,或者充其量只是在 I/O 路徑中的相鄰節點之間工作。DIF 和其他完整性擴充套件的有趣之處在於,保護格式定義良好,並且 I/O 路徑中的每個節點都可以驗證 I/O 的完整性,如果檢測到損壞則拒絕它。這不僅可以防止損壞,還可以隔離故障點。

2. 資料完整性擴充套件

如前所述,協議擴充套件只保護控制器和儲存裝置之間的路徑。然而,許多控制器實際上允許作業系統與完整性元資料(IMD)互動。我們已經與多家 FC/SAS HBA 供應商合作,使保護資訊能夠在它們控制器之間傳輸。

SCSI 資料完整性欄位的工作方式是,將 8 位元組的保護資訊附加到每個扇區。資料 + 完整性元資料以 520 位元組扇區儲存在磁碟上。資料 + IMD 在控制器和目標之間傳輸時是交錯的。T13 提案類似。

由於作業系統處理 520(和 4104)位元組扇區非常不便,我們與多家 HBA 供應商接洽,鼓勵他們允許分離資料和完整性元資料的分散-聚集列表。

控制器在寫入時將緩衝區交錯,在讀取時將它們拆分。這意味著 Linux 可以將資料緩衝區透過 DMA 傳輸到宿主記憶體和從宿主記憶體傳輸,而無需更改頁快取。

此外,SCSI 和 SATA 規範強制要求的 16 位 CRC 校驗和在軟體中計算起來有些繁重。基準測試發現,計算此校驗和對許多工作負載的系統性能產生了顯著影響。一些控制器允許在與作業系統互動時使用更輕量級的校驗和。例如,Emulex 支援 TCP/IP 校驗和。從作業系統接收到的 IP 校驗和在寫入時會轉換為 16 位 CRC,反之亦然。這使得 Linux 或應用程式能夠以非常低的成本生成完整性元資料(與軟體 RAID5 相當)。

就檢測位錯誤而言,IP 校驗和比 CRC 弱。然而,其真正的優勢在於資料緩衝區和完整性元資料的分離。這兩個不同的緩衝區必須匹配才能完成 I/O。

資料和完整性元資料緩衝區的分離以及校驗和的選擇被稱為資料完整性擴充套件。由於這些擴充套件超出了協議機構(T10, T13)的範圍,甲骨文及其合作伙伴正試圖在儲存網路行業協會中將其標準化。

3. 核心變更

Linux 中的資料完整性框架使得保護資訊能夠附加到 I/O 上,併發送到支援它的控制器或從其接收。

SCSI 和 SATA 中完整性擴充套件的優點是它們使我們能夠保護從應用程式到儲存裝置的整個路徑。然而,與此同時,這也是最大的缺點。這意味著保護資訊必須是磁碟可以理解的格式。

通常,Linux/POSIX 應用程式對它們訪問的儲存裝置的複雜性一無所知。虛擬檔案系統開關和塊層使硬體扇區大小和傳輸協議等內容對應用程式完全透明。

然而,在準備傳送到磁碟的保護資訊時,需要這種級別的細節。因此,端到端保護方案的概念本身就是分層違規。應用程式瞭解它是在訪問 SCSI 磁碟還是 SATA 磁碟是完全不合理的。

Linux 中實現的資料完整性支援試圖嚮應用程式隱藏這一點。就應用程式(以及某種程度上核心)而言,完整性元資料是附加到 I/O 的不透明資訊。

當前實現允許塊層自動為任何 I/O 生成保護資訊。最終的目的是將使用者資料的完整性元資料計算移至使用者空間。元資料和源自核心的其他 I/O 仍將使用自動生成介面。

一些儲存裝置允許每個硬體扇區標記一個 16 位的值。此標籤空間的所有者是塊裝置的所有者,即大多數情況下的檔案系統。檔案系統可以利用此額外空間根據需要標記扇區。由於標籤空間有限,塊介面允許透過交錯方式標記更大的塊。這樣,可以將 8*16 位的資訊附加到典型的 4KB 檔案系統塊上。

這也意味著 fsck 和 mkfs 等應用程式需要從使用者空間訪問和操作這些標籤。目前正在開發一個用於此目的的直通介面。

4. 塊層實現細節

4.1 Bio

當 CONFIG_BLK_DEV_INTEGRITY 啟用時,資料完整性補丁會在 struct bio 中新增一個新欄位。bio_integrity(bio) 返回一個指向 struct bip 的指標,該結構包含 bio 完整性負載。本質上,bip 是一個精簡的 struct bio,它包含一個 bio_vec,其中包含完整性元資料和所需的內部管理資訊(bvec 池、向量計數等)。

核心子系統可以透過呼叫 bio_integrity_alloc(bio) 來啟用 bio 上的資料完整性保護。這將分配 bip 並將其附加到 bio。

包含完整性元資料的單個頁面可以隨後使用 bio_integrity_add_page() 附加。

bio_free() 將自動釋放 bip。

4.2 塊裝置

塊裝置可以在 queue_limits 結構的 integrity 子結構中設定完整性資訊。

分層塊裝置需要選擇一個適用於所有子裝置的配置檔案。queue_limits_stack_integrity() 可以提供幫助。DM 和 MD 線性、RAID0 和 RAID1 目前受支援。RAID4/5/6 由於應用程式標籤的原因,將需要額外的工作。

5.0 塊層完整性 API

5.1 普通檔案系統

普通檔案系統並不知道底層塊裝置能夠傳送/接收完整性元資料。在寫入操作時,IMD 將在 submit_bio() 呼叫時由塊層自動生成。讀取請求將導致 I/O 完整性在完成後被驗證。

IMD 的生成和驗證可以透過

/sys/block/<bdev>/integrity/write_generate

/sys/block/<bdev>/integrity/read_verify

標誌進行切換。

5.2 完整性感知檔案系統

一個完整性感知檔案系統可以準備附加了 IMD 的 I/O。如果塊裝置支援,它還可以使用應用程式標籤空間。

bool bio_integrity_prep(bio);

為了生成寫入操作的 IMD 併為讀取操作設定緩衝區,檔案系統必須呼叫 bio_integrity_prep(bio)。

在呼叫此函式之前,必須設定 bio 的資料方向和起始扇區,並且 bio 應已新增所有資料頁面。呼叫者有責任確保在 I/O 進行期間 bio 不發生改變。如果因某種原因準備失敗,則以錯誤完成 bio。

5.3 傳遞現有完整性元資料

生成自身完整性元資料或能夠從使用者空間傳輸 IMD 的檔案系統可以使用以下呼叫:

struct bip * bio_integrity_alloc(bio, gfp_mask, nr_pages);

分配 bio 完整性負載並將其附加到 bio。nr_pages 指示需要在完整性 bio_vec 列表中儲存多少頁保護資料(類似於 bio_alloc())。

完整性負載將在 bio_free() 呼叫時被釋放。

int bio_integrity_add_page(bio, page, len, offset);

將包含完整性元資料的頁面附加到現有 bio。該 bio 必須已有一個現有的 bip,即必須已呼叫 bio_integrity_alloc()。對於寫入操作,頁面中的完整性元資料必須是目標裝置能夠理解的格式,但值得注意的是,當請求遍歷 I/O 棧時,扇區號將被重新對映。這意味著使用此呼叫新增的頁面在 I/O 期間將被修改!完整性元資料中的第一個引用標籤必須具有 bip->bip_sector 的值。

只要 bip bio_vec 陣列(nr_pages)中有空間,就可以使用 bio_integrity_add_page() 新增頁面。

讀取操作完成後,附加的頁面將包含從儲存裝置接收到的完整性元資料。接收方有責任在完成後處理它們並驗證資料完整性。


2007-12-24 Martin K. Petersen <martin.petersen@oracle.com>