英语

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,则此函数不执行任何操作。