首页
/ zstd 在 Linux 内核中的集成与升级全流程:基于 contrib/linux-kernel 的自动化转换指南

zstd 在 Linux 内核中的集成与升级全流程:基于 contrib/linux-kernel 的自动化转换指南

2026-09-15 00:00:22作者:裴麒琰

导读

本指南以 zstd 官方仓库(本项目随附于 third-party/zstd)中 contrib/linux-kernel 目录的 README.md 为主线,系统讲解将上游 zstd 库转换为 Linux 内核版本并完成导入、测试、回归验证的完整流程。读完本文,你将掌握 make libzstd / make test / make import 三个核心目标的内部机制,理解内核版 zstd 的无 libc 依赖层、内核风格 API 封装与 Kbuild 集成方式,并能在内核升级 zstd 时照此流程落地。本目录同样随附在 mold 链接器仓库中,作为 zstd 官方为内核维护者准备的移植工具集,具有独立的实践价值。

一、为什么内核版 zstd 需要一套专门的转换脚本

Linux 内核的运行环境与用户态存在根本差异:

  • 无 libc 可用:内核不能调用 mallocmemcpy 等标准库函数,必须替换为内核自身提供的实现;
  • 符号可见性受限:内核模块只导出显式声明的符号(如 EXPORT_SYMBOL_GPL),上游 zstd 的符号无法直接使用;
  • 头文件与宏定义冲突limits.hstddef.hxxhash.h 等必须重定向到 <linux/...> 版本;
  • 汇编优化不可移植:部分架构相关的汇编实现(如 huf_decompress_amd64.S)不能带入通用内核树。

因此 contrib/linux-kernel/Makefile 的核心任务不是"复制代码",而是自动改写——把上游 lib/ 源码变换为内核风格的 linux/lib/zstd/ 树。README 明确指出:"This directory contains the scripts needed to transform upstream zstd into the version imported into the kernel. All the transforms are automated and tested by our continuous integration."(本目录包含将上游 zstd 转换为内核导入版本的脚本,所有转换均由 CI 自动化测试。)

二、make libzstd:自动化转换的入口与产物布局

contrib/linux-kernel 目录下执行:

make libzstd

该目标执行的核心逻辑如下(摘自 Makefile):

libzstd:
	rm -rf linux
	mkdir -p linux
	mkdir -p linux/include/linux
	mkdir -p linux/lib/zstd
	../freestanding_lib/freestanding.py \
		--source-lib ../../lib \
		--output-lib linux/lib/zstd \
		...

转换完成后的产物布局为:

linux/
├── include/linux/
│   ├── zstd.h            # 内核风格 API 头(由 linux_zstd.h 复制而来)
│   ├── zstd_lib.h        # 上游 zstd.h 改名而来(由 zstd.h mv 得到)
│   └── zstd_errors.h
└── lib/zstd/
    ├── Makefile          # 由 linux.mk 复制
    ├── zstd_common_module.c / zstd_compress_module.c / zstd_decompress_module.c
    ├── decompress_sources.h
    ├── common/ compress/ decompress/   # 转换后的上游源码

其中 decompress_sources.hMakefile 复制到 linux/lib/zstd,用于解压场景下选择性编译源码。转换脚本位于 third-party/zstd/contrib/freestanding_lib/freestanding.py,是这套自动化流程的真正执行者。

2.1 freestanding.py 的关键参数

make libzstd 通过大量参数驱动 freestanding.py,理解这些参数就理解了整个转换策略:

参数类别 示例 作用
源/目标 --source-lib ../../lib--output-lib linux/lib/zstd 指定上游库目录与输出目录
头文件重定向 --rewrite-include '<limits\.h>=<linux/limits.h>''<stddef\.h>=<linux/types.h>''<xxhash.h>=<linux/xxhash.h>' 将 libc 头文件替换为内核头文件
相对路径重写 --rewrite-include '"\.\./zstd.h"=<linux/zstd.h>'"zstd_errors.h"=<linux/zstd_errors.h>' 处理 zstd 内部 #include "../zstd.h" 等写法
xxhash 适配 --xxhash '<linux/xxhash.h>'--xxh64-state 'struct xxh64_state'--xxh64-prefix 'xxh64' 让 zstd 调用内核自带的 xxh64 实现
注释归一化 --sed 's,/\*\*\*,/* *,g'--sed 's,/\*\*,/*,g' 去掉 doxygen 风格的 /** 注释,规避内核 -Wcomment 等告警
许可证标注 --spdx 为生成文件添加 SPDX 许可证头
宏定义 -DZSTD_NO_INTRINSICS-DZSTD_LINUX_KERNEL-DZSTD_NO_UNUSED_FUNCTIONS-DZSTD_LEGACY_SUPPORT=0-DZSTD_COMPRESS_HEAPMODE=1 关闭内核中不需要的特性(legacy 格式、未用函数、汇编、追踪、多线程等)
宏取消 -U_MSC_VER-U_WIN32-U__cplusplus-UZSTD_MULTITHREAD 清除 Windows / C++ / 多线程相关分支
符号重命名 -RZSTDLIB_VISIBLE=-RZSTDERRORLIB_VISIBLE= 移除上游的可见性宏,避免符号导出冲突
断言宏替换 -RZSTD_FALLTHROUGH=fallthrough 适配内核 fallthrough 关键字

转换完成后还有几个收尾动作(Makefile):

rm linux/lib/zstd/decompress/huf_decompress_amd64.S   # 删除 x86 专用汇编解码
mv linux/lib/zstd/zstd.h linux/include/linux/zstd_lib.h       # 上游头改名
mv linux/lib/zstd/zstd_errors.h linux/include/linux/
cp linux_zstd.h linux/include/linux/zstd.h                    # 内核风格 API 头
cp zstd_common_module.c linux/lib/zstd
cp zstd_compress_module.c linux/lib/zstd
cp zstd_decompress_module.c linux/lib/zstd
cp decompress_sources.h linux/lib/zstd
cp linux.mk linux/lib/zstd/Makefile

其中删除 huf_decompress_amd64.S 是因为内核不允许将架构专用汇编无条件带入,通用 huf_decompress.o 会作为替代(见下文 linux.mk)。

三、zstd_deps.h:面向内核的无 libc 依赖层

内核 zstd 编译不依赖 libc,关键支撑是 zstd_deps.h。它按需(通过 ZSTD_DEPS_NEED_* 宏)提供 zstd 源码需要的底层原语:

  • 内存操作ZSTD_memcpy / ZSTD_memmove / ZSTD_memset 直接映射为 __builtin_memcpy 等编译器内建函数,同时提供 NULL / INT_MAX / UINT_MAX
  • 内存分配(ZSTD_DEPS_NEED_MALLOCZSTD_malloc / ZSTD_calloc 被定义为恒返回 NULLZSTD_free 为空操作。这意味着内核调用方必须使用 ZSTD_customMem 自定义内存分配器,或按下面的工作区(workspace)模式静态分配内存;
  • 64 位除法(ZSTD_DEPS_NEED_MATH64ZSTD_div64 包装内核的 div_u64()
  • 断言(ZSTD_DEPS_NEED_ASSERTassert(x) 映射为内核 WARN_ON(!(x)),仅调试级别启用;
  • 调试输出(ZSTD_DEPS_NEED_IOZSTD_DEBUG_PRINT(...) 映射为 pr_debug(...)

文件头注释明确写道:"The purpose is to allow replacing this file with a custom implementation to compile zstd without libc support."(允许以自定义实现替换本文件,从而在无 libc 环境下编译 zstd。)

3.1 工作区(workspace)分配模式

内核 API 不依赖 malloc,其典型用法是"先查边界、再静态分配":

size_t wkspSize = zstd_cctx_workspace_bound(&params.cParams);
void *wksp = kmalloc(wkspSize, GFP_KERNEL);
zstd_cctx *cctx = zstd_init_cctx(wksp, wkspSize);

这一点在 test/test.c 中得到了完整演示。由于 Makefile 指定了 -DZSTD_COMPRESS_HEAPMODE=1,压缩内部不依赖堆分配,配合 ZSTD_malloc 恒失败的定义,从机制上杜绝了内核路径上的隐式内存分配。

四、linux_zstd.h:内核风格的 zstd API 封装

上游 zstd API 符号未导出,内核不能直接使用。因此 linux_zstd.h 提供了一套 zstd_* 前缀的内核风格 API,头文件注释说明:"This is a kernel-style API that wraps the upstream zstd API, which cannot be used directly because the symbols aren't exported."(这是内核风格 API,包装了无法直接使用、符号未导出的上游 zstd API。)

4.1 API 分组总览

分组 代表函数 说明
辅助函数 zstd_compress_bound()zstd_is_error()zstd_get_error_code()zstd_get_error_name() 尺寸上界、错误判定与翻译
参数选择 zstd_get_params(level, estimated_src_size)zstd_min_clevel()zstd_max_clevel()zstd_cctx_set_param() 由压缩级别推导全套参数,或逐项覆盖
单趟压缩 zstd_cctx_workspace_bound()zstd_init_cctx()zstd_compress_cctx() 一次调用完成压缩
单趟解压 zstd_dctx_workspace_bound()zstd_init_dctx()zstd_decompress_dctx() 支持拼接帧与可跳过帧
流式压缩 zstd_cstream_workspace_bound()zstd_init_cstream()zstd_reset_cstream()zstd_compress_stream()zstd_flush_stream()zstd_end_stream() 任意大小缓冲的分块压缩
流式解压 zstd_dstream_workspace_bound()zstd_init_dstream()zstd_reset_dstream()zstd_decompress_stream() 任意大小缓冲的分块解压
帧检查 zstd_find_frame_compressed_size()zstd_get_frame_header() 读取帧头、定位压缩帧边界
块级外部序列 zstd_register_sequence_producer()zstd_compress_sequences_and_literals() 暴露上游 block-level external sequence producer API

4.2 核心参数结构体

linux_zstd.h 复用了上游类型并给出了内核语境下的语义解释:

  • zstd_compression_parameterswindowLog(最大匹配距离的对数,越大压缩率越高、解压内存越大)、chainLog(全量搜索段大小,对 fast 策略无效)、hashLog(哈希表大小)、searchLog(搜索次数)、searchLength(匹配长度下限)、targetLength(optimal 解析器可接受匹配大小)、strategy(搜索策略,从快到强);
  • zstd_frame_parameterscontentSizeFlag(帧头是否写入内容大小)、checksumFlag(帧尾 32 位校验和)、noDictIDFlag(字典 ID 是否写入帧头),默认全部为 0。

4.3 模块实现:参数如何落到上游 CCtx

zstd_compress_module.c 中的 zstd_cctx_init() 展示了封装的实质——把 zstd_parameters 逐项翻译为上游的 ZSTD_CCtx_setParameter() 调用:

ZSTD_FORWARD_IF_ERR(ZSTD_CCtx_setParameter(cctx, ZSTD_c_windowLog, parameters->cParams.windowLog));
ZSTD_FORWARD_IF_ERR(ZSTD_CCtx_setParameter(cctx, ZSTD_c_hashLog,   parameters->cParams.hashLog));
ZSTD_FORWARD_IF_ERR(ZSTD_CCtx_setParameter(cctx, ZSTD_c_chainLog,  parameters->cParams.chainLog));
...
ZSTD_FORWARD_IF_ERR(ZSTD_CCtx_setParameter(cctx, ZSTD_c_contentSizeFlag, parameters->fParams.contentSizeFlag));
ZSTD_FORWARD_IF_ERR(ZSTD_CCtx_setParameter(cctx, ZSTD_c_checksumFlag,    parameters->fParams.checksumFlag));
ZSTD_FORWARD_IF_ERR(ZSTD_CCtx_setParameter(cctx, ZSTD_c_dictIDFlag,      !parameters->fParams.noDictIDFlag));

每个 ZSTD_FORWARD_IF_ERR 宏(同文件顶部定义)在遇到错误码时立即返回,保证参数组合非法时压缩不会带病继续。zstd_min_clevel() / zstd_max_clevel() 则直接转发 ZSTD_minCLevel() / ZSTD_maxCLevel()

五、linux.mk:内核 Kbuild 集成方式

转换后的 linux.mk 会被复制为 linux/lib/zstd/Makefile,作为内核 Kbuild 构建脚本:

obj-$(CONFIG_ZSTD_COMPRESS) += zstd_compress.o
obj-$(CONFIG_ZSTD_DECOMPRESS) += zstd_decompress.o
obj-$(CONFIG_ZSTD_COMMON) += zstd_common.o

zstd 在内核中被拆成三个可独立配置的模块:

  • zstd_compress-yzstd_compress_module.ocompress/ 下的 fse、hist、huf、zstd_compress、zstd_double_fast、zstd_fast、zstd_lazy、zstd_ldm、zstd_opt、zstd_preSplit 等对象;
  • zstd_decompress-yzstd_decompress_module.odecompress/ 下的 huf_decompress、zstd_ddict、zstd_decompress、zstd_decompress_block;
  • zstd_common-yzstd_common_module.ocommon/ 下的 debug、entropy_common、error_private、fse_decompress、zstd_common。

zstd_common_module.czstd_common_module.c)负责将压缩/解压共用的符号导出为 GPL 内核符号,例如 EXPORT_SYMBOL_GPL(FSE_readNCount)EXPORT_SYMBOL_GPL(HUF_readStats)EXPORT_SYMBOL_GPL(ZSTD_isError) 等,并声明 MODULE_LICENSE("Dual BSD/GPL")。压缩与解压模块因此可以只按需编译,显著缩小内核镜像体积。

六、make test:在用户态仿真内核环境验证转换结果

内核代码不能直接在本机运行,因此该目录用一套"桩(stub)"头文件在用户态模拟内核环境。执行:

make test

会先构建 libzstd,再进入 test/ 子目录运行测试(Makefile):

test: libzstd
	$(MAKE) -C test run-test CFLAGS="-O3 $(CFLAGS) $(DEBUGFLAGS) -Werror" -j

DEBUGFLAGSMakefile)是一组严格告警开关,包括 -Wall -Wextra -Wcast-qual -Wshadow -Wstrict-aliasing=1 -Wswitch-enum -Wdeclaration-after-statement -Wstrict-prototypes -Wundef -Wpointer-arith -Wvla -Wformat=2 -Wfloat-equal -Wwrite-strings -Wredundant-decls -Wmissing-prototypes -Wc++-compat -Wimplicit-fallthrough,并叠加 -Werror 让任何告警都升级为失败——保证转换后的代码满足内核编码规范。

6.1 用户态内核头文件桩

test/include/linux/ 提供了 compiler.herrno.hkernel.hlimits.hmath64.hmodule.hprintk.hstddef.hswab.htypes.hunaligned.hxxhash.h 等内核头文件的轻量替代实现,测试时通过 -I$(LINUX)/include -I$(LINUX_ZSTDLIB) -Iincludetest/Makefile)引入,并额外定义 -DZSTD_ASAN_DONT_POISON_WORKSPACE 以便在 AddressSanitizer 下复用工作区。

6.2 测试覆盖的场景

test/test.c 直接 #include <linux/zstd.h>,按内核调用方的姿势编写:

  • test_btrfs():模拟 btrfs 的用法——对每个压缩级别(-1 到 15)用 zstd_get_params(level, size) 取参数、zstd_cstream_workspace_bound / zstd_dstream_workspace_bound 取工作区大小、zstd_init_cstream + zstd_compress_stream + zstd_end_stream 压缩、再 zstd_init_dstream + zstd_decompress_stream 解压,最后校验逐字节一致;测试中还断言 params.cParams.windowLog <= 17,即 btrfs 场景下的窗口上限;
  • test_decompress_unzstd():模拟用户态 unzstd 工具的单趟压缩/解压路径(zstd_compress_cctx / zstd_decompress_dctx);
  • test_f2fs():校验 f2fs 依赖的级别范围,断言 zstd_max_clevel() == 22
  • test_stack_usage():先填满 8 KiB 栈,再执行上述各用例,最后检查栈消耗不超过约 2.5 KiB,确保内核栈(通常 8–16 KiB)不会溢出。

此外 run-test 还会执行 test/macro-test.shtest/static_test.c:前者校验转换后宏定义的正确性,后者验证仅包含头文件的静态使用场景。README 强调"Run make test and ensure that it passes",即 CI 也会持续跑这套测试来守护转换脚本。

七、make import:将转换结果导入内核源码树

本地验证通过后,将转换产物导入真实内核:

make import LINUX=/path/to/linux/repo

import 目标(Makefile)依赖 libzstd 先完成转换,然后:

import: libzstd
	rm -f $(LINUX)/include/linux/zstd.h
	rm -f $(LINUX)/include/linux/zstd_errors.h
	rm -rf $(LINUX)/lib/zstd
	cp linux/include/linux/zstd.h       $(LINUX)/include/linux
	cp linux/include/linux/zstd_lib.h   $(LINUX)/include/linux
	cp linux/include/linux/zstd_errors.h $(LINUX)/include/linux
	cp -r linux/lib/zstd $(LINUX)/lib

LINUX 默认为 $(HOME)/repos/linuxMakefile),可通过命令行覆盖。导入后需要人工审查 diff(README 第 5 步),确认头文件、Kbuild 文件与源码位置均符合内核预期。

7.1 备选目标 import-upstream

Makefile 还提供了 import-upstream 目标——不做任何改写,直接把上游 lib/ 目录复制进内核,仅移除 threading.*pool.*xxhash.*zstdmt_* 多线程相关文件。该目标适用于不依赖内核化改造的快速同步场景,但产物不经过 zstd_deps.hlinux_zstd.h 的适配,一般仅作参考。

八、升级 zstd 到内核的八步操作清单

结合 README.md 的流程说明与上文原理,完整的升级操作如下:

  1. 进入目录cd third-party/zstd/contrib/linux-kernel
  2. 生成并审查转换:运行 make libzstd,仔细阅读输出,核对脚本打印的每个 diff 与文件变更是否符合预期;
  3. 本地测试:运行 make test 并确保全部通过(含 teststatic_testmacro-test.sh,且以 -Werror 严格告警编译);
  4. 导入内核make import LINUX=/path/to/linux/repo
  5. 人工审查:检查导入后的 diff,确认无意外改动;
  6. 回传补丁:查阅内核树中 zstd 的历史提交;如果存在只打了内核侧、未回传上游 zstd 的补丁,需要视情况移植回上游(README 第 6 步),避免两边漂移;
  7. 多架构测试与基准:至少在 x86、i386、arm 三个架构上验证,必要时运行基准对比压缩率与吞吐;
  8. 提交内核补丁:将补丁提交到 LKML(Linux Kernel Mailing List)。

九、回归验证与性能基准脚本

目录内提供了三套基准脚本,用于在真实文件系统上验证内核 zstd 的表现:

  • btrfs-benchmark.sh:挂载 btrfs 文件系统,批量拷贝 Silesia 语料(约 203 MiB)测量压缩耗时、用 du/df 估算压缩率、卸载重挂后 tar 输出测量解压吞吐;
  • btrfs-extract-benchmark.sh:聚焦解压/提取路径的基准;
  • squashfs-benchmark.sh:面向 squashfs 只读文件系统场景的基准。

需要说明的是,btrfs-benchmark.sh 注释中记录了一组历史基准数据(Ubuntu 14.04 虚拟机、2 核 4 GiB、宿主为 3.1 GHz i7 的 MacBook Pro),例如在 Silesia 语料上 zstd level 1 压缩耗时 8.147 s、压缩率 2.57,zstd level 15 压缩率 3.01;这些数据仅代表该特定环境下的测量结果,脚本注释同时强调"Ran for each of -o compress-force={none, lzo, zlib, zstd} 5 times and take the min time and ratio"。实际使用时应在目标硬件与内核版本上重新测量,切勿直接引用为通用性能结论。

十、实践要点与常见陷阱

  1. 必须先跑 make testmake import:转换脚本可能因上游源码结构调整而失效(头文件改名、新增依赖、宏变更),测试是唯一自动防线;
  2. 窗口参数与内存上限:内核调用方务必用 zstd_cstream_workspace_bound / zstd_dctx_workspace_bound 申请工作区,解压侧还要显式传入 max_window_size 限制恶意帧的窗口,防止内存放大(参见 linux_zstd.hzstd_dstream_workspace_bound 文档);
  3. 不要破坏无堆分配约束:内核版 ZSTD_malloc 恒返回 NULL,任何依赖隐式分配的新增上游代码都会在测试中暴露为解压/压缩失败,应改用 ZSTD_customMem 或静态工作区;
  4. 符号导出走 EXPORT_SYMBOL_GPL:新增的共用符号需追加到 zstd_common_module.c 的导出列表,否则模块加载时会报未定义符号;
  5. 多架构验证不可省略:不同架构对整数宽度、对齐、unaligned 访问的假设不同,README 明确要求至少覆盖 x86、i386、arm。

结语

zstd 的 contrib/linux-kernel 目录把"上游库 → 内核代码"这一繁琐且易错的过程收敛为三个自动化目标:libzstd(freestanding 改写与文件布局)、test(用户态桩环境下的严格验证)、import(写入内核树)。其背后的设计——zstd_deps.h 无 libc 依赖层、linux_zstd.h 内核风格 API、三模块 Kbuild 拆分——正是所有"将用户态库移植进内核"场景可复用的范本。无论是升级内核 zstd、向内核新增压缩后端,还是为自有库设计 freestanding 移植流程,本目录都值得作为首选参考。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
34
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.21 K
2.81 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
945
1.86 K
docsdocs
暂无描述
Markdown
906
5.84 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
537
607
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
864
1.36 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
4.28 K
1.03 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.39 K
1.48 K
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
550
401
flutter_flutterflutter_flutter
本仓库是 Flutter SDK 与 Flutter Engine 的 OpenHarmony 适配版本,由 CPF-Flutter 团队维护。开发者可使用熟悉的 Flutter 技术栈开发 OpenHarmony 应用,3.35.7 及以后的适配版本可基于本仓库源码构建支持 OpenHarmony 的 Flutter Engine。
Dart
1.19 K
347