英語

Linux 中的 XZ 資料壓縮

簡介

XZ 是一種通用的資料壓縮格式,具有很高的壓縮率。Linux 中的 XZ 解壓縮器稱為 XZ Embedded。它支援 LZMA2 過濾器,並可選地支援可執行程式碼的分支/呼叫/跳轉 (BCJ) 過濾器。CRC32 支援用於完整性檢查。

有關最新版本,請參見 XZ Embedded 主頁,其中包含 Linux 核心中不需要的一些可選額外功能以及有關在 Linux 核心之外使用程式碼的資訊。

對於使用者空間,XZ Utils 提供了一個類似 zlib 的壓縮庫和一個類似 gzip 的命令列工具。

壓縮選項說明

由於 XZ Embedded 僅支援使用 CRC32 或不進行完整性檢查的流,因此請確保在編碼將由核心解碼的檔案時不要使用其他完整性檢查型別。使用 XZ Utils 中的 liblzma 時,編碼時需要使用 LZMA_CHECK_CRC32LZMA_CHECK_NONE。使用 xz 命令列工具時,請使用 --check=crc32--check=none 覆蓋預設的 --check=crc64

強烈建議使用 CRC32,除非存在其他層可以驗證未壓縮資料的完整性。重複檢查完整性可能會浪費 CPU 週期。請注意,標頭始終具有 CRC32,該 CRC32 將由解碼器驗證;您只能更改實際未壓縮資料的完整性檢查型別(或停用它)。

在使用者空間中,LZMA2 通常與幾兆位元組的字典大小一起使用。解碼器需要在 RAM 中具有字典

  • 在多次呼叫模式下,字典作為解碼器狀態的一部分進行分配。核心中使用的合理最大字典大小取決於目標硬體:對於桌面系統,幾兆位元組很好,而在某些嵌入式系統上,64 KiB 到 1 MiB 可能更合適。

  • 在單次呼叫模式下,輸出緩衝區用作字典緩衝區。也就是說,字典的大小根本不影響解壓縮器的記憶體使用量。僅分配基本資料結構,這些資料結構佔用略少於 30 KiB 的記憶體。為了獲得最佳壓縮效果,字典應至少與未壓縮資料一樣大。單次呼叫模式的一個顯著例子是解壓縮核心本身(在 PowerPC 上除外)。

在為核心建立檔案時,XZ Utils 中的壓縮預設可能不是最佳的,因此請不要猶豫使用自定義設定(例如,設定字典大小)。此外,xz 可能會在單執行緒模式下生成較小的檔案,因此建議顯式設定該模式。示例

xz --threads=1 --check=crc32 --lzma2=dict=512KiB inputfile

xz_dec API

這可以透過 #include <linux/xz.h> 獲得。

enum xz_mode

操作模式

常量

XZ_SINGLE

單次呼叫模式。與多次呼叫模式相比,此模式使用的 RAM 更少,因為 LZMA2 字典不需要作為解碼器狀態的一部分進行分配。所有必需的資料結構都在初始化時分配,因此 xz_dec_run() 無法返回 XZ_MEM_ERROR。

XZ_PREALLOC

多次呼叫模式,帶有預分配的 LZMA2 字典緩衝區。所有資料結構都在初始化時分配,因此 xz_dec_run() 無法返回 XZ_MEM_ERROR。

XZ_DYNALLOC

多次呼叫模式。LZMA2 字典在從流標頭解析出所需大小後分配一次。如果分配失敗,xz_dec_run() 將返回 XZ_MEM_ERROR。

描述

可以透過定義 XZ_DEC_SINGLE、XZ_DEC_PREALLOC 或 XZ_DEC_DYNALLOC 在編譯時僅啟用對上述模式的子集的支援。xz_dec 核心模組始終編譯為支援所有操作模式,但是可以使用較少的功能構建預啟動程式碼,以最大程度地減少程式碼大小。

enum xz_ret

返回值

常量

XZ_OK

到目前為止一切正常。需要更多輸入或更多輸出空間才能繼續。此返回值僅在多次呼叫模式(XZ_PREALLOC 或 XZ_DYNALLOC)下可能。

XZ_STREAM_END

操作已成功完成。

XZ_UNSUPPORTED_CHECK

不支援完整性檢查型別。透過簡單地再次呼叫 xz_dec_run(),仍然可以在多次呼叫模式下進行解碼。請注意,僅當在構建時定義了 XZ_DEC_ANY_CHECK 時才使用此返回值,該值未在核心中使用。如果未在構建時定義 XZ_DEC_ANY_CHECK,則不支援的檢查型別將返回 XZ_OPTIONS_ERROR。

XZ_MEM_ERROR

記憶體分配失敗。僅當使用 XZ_DYNALLOC 初始化解碼器時才可能出現此返回值。嘗試分配的記憶體量不超過傳遞給 xz_dec_init() 的 dict_max 引數。

XZ_MEMLIMIT_ERROR

將需要比傳遞給 xz_dec_init() 的 dict_max 引數允許的更大的 LZMA2 字典。此返回值僅在多次呼叫模式(XZ_PREALLOC 或 XZ_DYNALLOC)下可能;單次呼叫模式(XZ_SINGLE)忽略 dict_max 引數。

XZ_FORMAT_ERROR

無法識別檔案格式(魔數錯誤)。

XZ_OPTIONS_ERROR

此實現不支援請求的壓縮選項。在解碼器中,這意味著標頭 CRC32 匹配,但標頭本身指定了我們不支援的內容。

XZ_DATA_ERROR

壓縮資料已損壞。

XZ_BUF_ERROR

無法取得任何進展。多次呼叫模式和單次呼叫模式之間的詳細資訊略有不同;更多資訊如下。

描述

在多次呼叫模式下,當對 XZ 程式碼的兩次連續呼叫都無法消耗任何輸入並且無法產生任何新的輸出時,將返回 XZ_BUF_ERROR。當沒有新的輸入可用時,或者當輸出緩衝區已滿但至少仍有一個輸出位元組處於掛起狀態時,會發生這種情況。假設您的程式碼沒有錯誤,則只有在解碼被截斷或以其他方式損壞的壓縮流時,才能獲得此錯誤。

在單次呼叫模式下,僅當輸出緩衝區太小或壓縮輸入以某種方式損壞時才返回 XZ_BUF_ERROR,這使得解碼器產生的輸出超出呼叫方的預期。當(相對)清楚壓縮輸入已被截斷時,將使用 XZ_DATA_ERROR 代替 XZ_BUF_ERROR。

struct xz_buf

將輸入和輸出緩衝區傳遞給 XZ 程式碼

定義:

struct xz_buf {
    const uint8_t *in;
    size_t in_pos;
    size_t in_size;
    uint8_t *out;
    size_t out_pos;
    size_t out_size;
};

成員

in

輸入緩衝區的開頭。當且僅當 in_pos 等於 in_size 時,此值才可以為 NULL。

in_pos

輸入緩衝區中的當前位置。此值不得超過 in_size。

in_size

輸入緩衝區的大小

out

輸出緩衝區的開頭。當且僅當 out_pos 等於 out_size 時,此值才可以為 NULL。

out_pos

輸出緩衝區中的當前位置。此值不得超過 out_size。

out_size

輸出緩衝區的大小

描述

只有從 out[out_pos] 開始的輸出緩衝區的內容以及變數 in_pos 和 out_pos 被 XZ 程式碼修改。

struct xz_dec *xz_dec_init(enum xz_mode mode, uint32_t dict_max)

分配和初始化 XZ 解碼器狀態

引數

enum xz_mode mode

操作模式

uint32_t dict_max

多次呼叫解碼的 LZMA2 字典(歷史緩衝區)的最大大小。在單次呼叫模式(mode == XZ_SINGLE)下將忽略此引數。LZMA2 字典始終為 2^n 位元組或 2^n + 2^(n-1) 位元組(後一種大小在實踐中不太常見),因此 dict_max 的其他值沒有意義。在核心中,字典大小 64 KiB、128 KiB、256 KiB、512 KiB 和 1 MiB 可能是唯一合理的值,但核心和 initramfs 映象除外,在這些映象中更大的字典可能很好且有用。

描述

單次呼叫模式(XZ_SINGLE):xz_dec_run() 一次解碼整個流。呼叫方必須提供足夠的輸出空間,否則解碼將失敗。輸出空間用作字典緩衝區,這就是為什麼不需要將字典作為解碼器內部狀態的一部分進行分配的原因。

由於輸出緩衝區用作工作空間,因此使用大字典編碼的流在單次呼叫模式下不是問題。輸出緩衝區足夠大以容納實際的未壓縮資料就足夠了;它可以小於流標頭中儲存的字典大小。

具有預分配字典的多次呼叫模式(XZ_PREALLOC):為 LZMA2 字典預分配 dict_max 位元組的記憶體。這樣,就沒有 xz_dec_run() 可能耗盡記憶體的風險,因為 xz_dec_run() 永遠不會分配任何記憶體。相反,如果預分配的字典太小而無法解碼給定的輸入流,則 xz_dec_run() 將返回 XZ_MEMLIMIT_ERROR。因此,瞭解將解碼哪種資料以避免為字典分配過多的記憶體非常重要。

具有動態分配字典的多次呼叫模式(XZ_DYNALLOC):dict_max 指定 xz_dec_run() 在從流標頭解析出字典大小後可能分配的最大允許字典大小。這樣,可以避免過多的分配,同時仍然將最大記憶體使用量限制為合理的價值,以防止在解壓縮來自不受信任來源的流時耗盡系統記憶體。

成功後,xz_dec_init() 返回指向 struct xz_dec 的指標,該指標已準備好與 xz_dec_run() 一起使用。如果記憶體分配失敗,xz_dec_init() 返回 NULL。

enum xz_ret xz_dec_run(struct xz_dec *s, struct xz_buf *b)

執行 XZ 解碼器

引數

struct xz_dec *s

使用 xz_dec_init() 分配的解碼器狀態

struct xz_buf *b

輸入和輸出緩衝區

描述

可能的返回值取決於構建選項和操作模式。有關詳細資訊,請參見 enum xz_ret

請注意,如果在單次呼叫模式下發生錯誤(返回值不是 XZ_STREAM_END),則 b->in_pos 和 b->out_pos 不會被修改,並且輸出緩衝區中從 b->out[b->out_pos] 開始的內容未定義。即使在 XZ_BUF_ERROR 之後也是如此,因為對於某些過濾器鏈,可能需要對輸出緩衝區進行第二次傳遞,如果輸出緩衝區被截斷,則無法正確完成此傳遞。因此,您不能為單次呼叫解碼器提供太小的緩衝區,然後期望從流的開頭獲取該數量的有效資料。如果不希望解壓縮整個流,則必須使用多次呼叫解碼器。

void xz_dec_reset(struct xz_dec *s)

重置已分配的解碼器狀態

引數

struct xz_dec *s

使用 xz_dec_init() 分配的解碼器狀態

描述

此函式可用於重置多次呼叫解碼器狀態,而無需使用 xz_dec_end()xz_dec_init() 釋放和重新分配記憶體。

在單次呼叫模式下,始終在 xz_dec_run() 的開頭呼叫 xz_dec_reset()。因此,僅在多次呼叫模式下,顯式呼叫 xz_dec_reset() 才有用。

void xz_dec_end(struct xz_dec *s)

釋放為解碼器狀態分配的記憶體

引數

struct xz_dec *s

使用 xz_dec_init() 分配的解碼器狀態。如果 s 為 NULL,則此函式不執行任何操作。

MicroLZMA 解壓縮器

建立此 MicroLZMA 標頭格式是為了在 EROFS 中使用,但也可能被其他人使用。在大多數情況下,需要上面的 XZ API。

此解碼器支援的壓縮格式是一個原始 LZMA 流,該流的第一個位元組(始終為 0x00)已被 LZMA 屬性(lc/lp/pb)位元組的按位取反替換。例如,如果 lc/lp/pb 為 3/0/2,則第一個位元組為 0xA2。這樣,第一個位元組永遠不能為 0x00。就像 LZMA2 一樣,lc + lp <= 4 必須為真。不得使用 LZMA 流結束標記。未使用的值保留供將來使用。

struct xz_dec_microlzma *xz_dec_microlzma_alloc(enum xz_mode mode, uint32_t dict_size)

為 MicroLZMA 解碼器分配記憶體

引數

enum xz_mode mode

XZ_SINGLE 或 XZ_PREALLOC

uint32_t dict_size

LZMA 字典大小。此值必須至少為 4 KiB,至多為 3 GiB。

描述

xz_dec_init() 相比,此函式僅分配記憶體並記住字典大小。必須在呼叫 xz_dec_microlzma_run() 之前使用 xz_dec_microlzma_reset()

使用 XZ_SINGLE 時,分配的記憶體量略小於 30 KiB。使用 XZ_PREALLOC 時,還會分配一個 dict_size 位元組的字典緩衝區。

成功後,xz_dec_microlzma_alloc() 返回指向 struct xz_dec_microlzma 的指標。如果記憶體分配失敗或 dict_size 無效,則返回 NULL。

void xz_dec_microlzma_reset(struct xz_dec_microlzma *s, uint32_t comp_size, uint32_t uncomp_size, int uncomp_size_is_exact)

重置 MicroLZMA 解碼器狀態

引數

struct xz_dec_microlzma *s

使用 xz_dec_microlzma_alloc() 分配的解碼器狀態

uint32_t comp_size

輸入流的壓縮大小

uint32_t uncomp_size

輸入流的未壓縮大小。如果將 uncomp_size_is_exact 設定為 false,則可以指定小於輸入流的實際未壓縮大小的值。uncomp_size 永遠不能設定為大於預期實際未壓縮大小的值,因為最終會導致 XZ_DATA_ERROR。

int uncomp_size_is_exact

這是一個 int 而不是 bool,以避免需要 stdbool.h。通常應將其設定為 true。將其設定為 false 時,錯誤檢測會減弱。

enum xz_ret xz_dec_microlzma_run(struct xz_dec_microlzma *s, struct xz_buf *b)

執行 MicroLZMA 解碼器

引數

struct xz_dec_microlzma *s

使用 xz_dec_microlzma_reset() 初始化的解碼器狀態

struct xz_buf *b

輸入和輸出緩衝區

描述

這與 xz_dec_run() 的工作方式類似,但有一些重要的區別。此處僅記錄差異。

唯一可能的返回值是 XZ_OK、XZ_STREAM_END 和 XZ_DATA_ERROR。此函式無法返回 XZ_BUF_ERROR:如果由於缺少輸入資料或輸出空間而無法取得進展,此函式將繼續返回 XZ_OK。因此,呼叫程式碼必須編寫為最終提供與傳遞給 xz_dec_microlzma_reset() 的 comp_size 和 uncomp_size 引數匹配(或超過)的輸入和輸出空間。如果呼叫方無法做到這一點(例如,如果輸入檔案被截斷或以其他方式損壞),則呼叫方必須自行檢測此錯誤以避免無限迴圈。

如果壓縮資料似乎已損壞,則返回 XZ_DATA_ERROR。當指定了不正確的字典大小、未壓縮大小或壓縮大小時,也會發生這種情況。

僅使用 XZ_PREALLOC:作為一項額外功能,b->out 可能為 NULL,以跳過未壓縮資料。這樣,呼叫方不需要為將被忽略的位元組提供臨時輸出緩衝區。

僅使用 XZ_SINGLE:與 xz_dec_run() 相比,返回值 XZ_OK 也是可能的,因此 XZ_SINGLE 實際上是一種有限的多次呼叫模式。在 XZ_OK 之後,可以從輸出緩衝區讀取到目前為止解碼的位元組。可以繼續解碼,但呼叫方不得更改變數 b->out 和 b->out_pos。允許增加 b->out_size 的值以提供更多輸出空間;無需在第一次呼叫時為整個未壓縮資料提供空間。與 XZ_PREALLOC 一樣,可以正常更改輸入緩衝區。這樣,可以從非連續記憶體中提供輸入資料。

void xz_dec_microlzma_end(struct xz_dec_microlzma *s)

釋放為解碼器狀態分配的記憶體

引數

struct xz_dec_microlzma *s

使用 xz_dec_microlzma_alloc() 分配的解碼器狀態。如果 s 為 NULL,則此函式不執行任何操作。