英文

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

需要的 LZMA2 字典比传递给 xz_dec_init() 的 dict_max 参数允许的要大。此返回值仅在多调用模式(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

输出缓冲区的大小

描述

XZ 代码仅修改从 out[out_pos] 开始的输出缓冲区的内容以及变量 in_pos 和 out_pos。

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_reset() 始终在 xz_dec_run() 的开头调用。因此,仅在多调用模式下显式调用 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,此函数不执行任何操作。