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_CRC32 或 LZMA_CHECK_NONE。使用 xz 命令行工具时,请使用 --check=crc32 或 --check=none 来覆盖默认的 --check=crc64。
强烈建议使用 CRC32,除非无论如何都有其他层会验证未压缩数据的完整性。双重检查完整性可能会浪费 CPU 周期。请注意,头部始终具有由解码器验证的 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 内核模块始终在编译时支持所有操作模式,但引导前(preboot)代码可能会构建较少的功能以尽量减小代码大小。
-
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。当没有可用的新输入,或者输出缓冲区已满而至少还有一个输出字节处于挂起状态时,就会发生这种情况。假设你的代码没有 bug,你只有在解码被截断或以其他方式损坏的压缩流时才会得到此错误。
在单次调用模式下,仅当输出缓冲区太小,或者压缩输入损坏导致解码器生成的输出多于调用者预期时,才会返回 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。
参数
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。
参数
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 之后也是如此,因为对于某些过滤器链,可能会对输出 buffer 进行第二遍扫描,如果输出缓冲区被截断,这一遍扫描就无法正确完成。因此,你不能给单次调用解码器一个太小的缓冲区,然后期望从流的开头获取相应数量的有效数据。如果你不想解压整个流,必须使用多次调用解码器。
-
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 modeXZ_SINGLE 或 XZ_PREALLOC
uint32_t dict_sizeLZMA 字典大小。这必须至少为 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 时,错误检测会变弱。
参数
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,则此函数不执行任何操作。