Re: [PATCH 1/2] lib: add support for ZSTD-compressed kernel

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



> On Mar 16, 2020, at 6:50 AM, Petr Malat <oss@xxxxxxxxx> wrote:
> 
> Add support for extracting ZSTD-compressed kernel images, as well as
> ZSTD-compressed initramfs.

Hi Petr,

Thanks for putting up this patch!

In the last month I rebased, retested, and refactored my patches [0][1], because
we had a new use case for a zstd compressed initramfs. We have been using
the patches I put up for quite some time internally, so we know they work well.

I will re-submit my patches today, once I write all the commit summaries and a
new cover letter. Thanks for the kick in the butt to resubmit it.

Your patches are missing several things that I have included in my patches [0][1]:
* Invocations of EXPORT_SYMBOL() and MODULE_LICENSE() in the pre-boot
  either emitted warnings or didn’t work in the pre-boot environment, unless that
  has changed.
* The memcpy() inside of ZSTD_copy8() which is the core of zstd’s hot loop gets
  outlined in the x86 preboot environment unless you use __builtin_memcpy().
  This destroys decompression speed.
* unzstd() can use less memory when fill & flush are both NULL by calling
  ZSTD_decompressDCtx().
* ZO_z_extra_bytes needs to be bumped because zstd can overlap more than
  Other compressors. If it isn’t you could corrupt the kernel.

I think it would be better to go with my patches. They have already been tested
in production for both correctness and performance, and I have boot tested on
x86, arm, and aarch64. I will make my case fully when I resubmit my patches.

Thanks again for submitting this, because I have been dawdling!
Nick

[0] https://lore.kernel.org/patchwork/patch/839674/
[1] https://lore.kernel.org/patchwork/patch/839675/

> Signed-off-by: Petr Malat <oss@xxxxxxxxx>
> ---
> include/linux/decompress/unzstd.h |  12 +++
> init/Kconfig                      |  15 ++-
> lib/Kconfig                       |   4 +
> lib/Makefile                      |   1 +
> lib/decompress.c                  |   5 +
> lib/decompress_unzstd.c           | 159 ++++++++++++++++++++++++++++++
> lib/zstd/decompress.c             |   2 +
> lib/zstd/fse_decompress.c         |   4 +-
> scripts/Makefile.lib              |   3 +
> usr/Kconfig                       |  24 +++++
> 10 files changed, 227 insertions(+), 2 deletions(-)
> create mode 100644 include/linux/decompress/unzstd.h
> create mode 100644 lib/decompress_unzstd.c
> 
> diff --git a/include/linux/decompress/unzstd.h b/include/linux/decompress/unzstd.h
> new file mode 100644
> index 000000000000..dd2c49d47456
> --- /dev/null
> +++ b/include/linux/decompress/unzstd.h
> @@ -0,0 +1,12 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +#ifndef DECOMPRESS_UNZSTD_H
> +#define DECOMPRESS_UNZSTD_H
> +
> +int unzstd(unsigned char *inbuf, long len,
> +	long (*fill)(void*, unsigned long),
> +	long (*flush)(void*, unsigned long),
> +	unsigned char *output,
> +	long *pos,
> +	void (*error)(char *x));
> +#endif
> +
> diff --git a/init/Kconfig b/init/Kconfig
> index a34064a031a5..628eb3c290a2 100644
> --- a/init/Kconfig
> +++ b/init/Kconfig
> @@ -172,13 +172,16 @@ config HAVE_KERNEL_LZO
> config HAVE_KERNEL_LZ4
> 	bool
> 
> +config HAVE_KERNEL_ZSTD
> +	bool
> +
> config HAVE_KERNEL_UNCOMPRESSED
> 	bool
> 
> choice
> 	prompt "Kernel compression mode"
> 	default KERNEL_GZIP
> -	depends on HAVE_KERNEL_GZIP || HAVE_KERNEL_BZIP2 || HAVE_KERNEL_LZMA || HAVE_KERNEL_XZ || HAVE_KERNEL_LZO || HAVE_KERNEL_LZ4 || HAVE_KERNEL_UNCOMPRESSED
> +	depends on HAVE_KERNEL_GZIP || HAVE_KERNEL_BZIP2 || HAVE_KERNEL_LZMA || HAVE_KERNEL_XZ || HAVE_KERNEL_LZO || HAVE_KERNEL_LZ4 || HAVE_KERNEL_ZSTD || HAVE_KERNEL_UNCOMPRESSED
> 	help
> 	  The linux kernel is a kind of self-extracting executable.
> 	  Several compression algorithms are available, which differ
> @@ -257,6 +260,16 @@ config KERNEL_LZ4
> 	  is about 8% bigger than LZO. But the decompression speed is
> 	  faster than LZO.
> 
> +config KERNEL_ZSTD
> +	bool "ZSTD"
> +	depends on HAVE_KERNEL_ZSTD
> +	help
> +	  Its compression ratio is roughly 10% worst than xz, but the
> +	  decompression is 10x faster. Currently, this is one of the optimal
> +	  algorithms available in the kernel, as there isn't an algorithm,
> +	  which would provide a better compression ratio and a shorter
> +	  decompression time.
> +
> config KERNEL_UNCOMPRESSED
> 	bool "None"
> 	depends on HAVE_KERNEL_UNCOMPRESSED
> diff --git a/lib/Kconfig b/lib/Kconfig
> index 6e790dc55c5b..df301bd888d7 100644
> --- a/lib/Kconfig
> +++ b/lib/Kconfig
> @@ -329,6 +329,10 @@ config DECOMPRESS_LZ4
> 	select LZ4_DECOMPRESS
> 	tristate
> 
> +config DECOMPRESS_ZSTD
> +	select ZSTD_DECOMPRESS
> +	tristate
> +
> #
> # Generic allocator support is selected if needed
> #
> diff --git a/lib/Makefile b/lib/Makefile
> index 93217d44237f..3ab9f4c31f8b 100644
> --- a/lib/Makefile
> +++ b/lib/Makefile
> @@ -158,6 +158,7 @@ lib-$(CONFIG_DECOMPRESS_LZMA) += decompress_unlzma.o
> lib-$(CONFIG_DECOMPRESS_XZ) += decompress_unxz.o
> lib-$(CONFIG_DECOMPRESS_LZO) += decompress_unlzo.o
> lib-$(CONFIG_DECOMPRESS_LZ4) += decompress_unlz4.o
> +lib-$(CONFIG_DECOMPRESS_ZSTD) += decompress_unzstd.o
> 
> obj-$(CONFIG_TEXTSEARCH) += textsearch.o
> obj-$(CONFIG_TEXTSEARCH_KMP) += ts_kmp.o
> diff --git a/lib/decompress.c b/lib/decompress.c
> index 857ab1af1ef3..ab3fc90ffc64 100644
> --- a/lib/decompress.c
> +++ b/lib/decompress.c
> @@ -13,6 +13,7 @@
> #include <linux/decompress/inflate.h>
> #include <linux/decompress/unlzo.h>
> #include <linux/decompress/unlz4.h>
> +#include <linux/decompress/unzstd.h>
> 
> #include <linux/types.h>
> #include <linux/string.h>
> @@ -37,6 +38,9 @@
> #ifndef CONFIG_DECOMPRESS_LZ4
> # define unlz4 NULL
> #endif
> +#ifndef CONFIG_DECOMPRESS_ZSTD
> +# define unzstd NULL
> +#endif
> 
> struct compress_format {
> 	unsigned char magic[2];
> @@ -52,6 +56,7 @@ static const struct compress_format compressed_formats[] __initconst = {
> 	{ {0xfd, 0x37}, "xz", unxz },
> 	{ {0x89, 0x4c}, "lzo", unlzo },
> 	{ {0x02, 0x21}, "lz4", unlz4 },
> +	{ {0x28, 0xb5}, "zstd", unzstd },
> 	{ {0, 0}, NULL, NULL }
> };
> 
> diff --git a/lib/decompress_unzstd.c b/lib/decompress_unzstd.c
> new file mode 100644
> index 000000000000..5af647a49885
> --- /dev/null
> +++ b/lib/decompress_unzstd.c
> @@ -0,0 +1,159 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Wrapper for decompressing ZSTD-compressed kernel, initramfs, and initrd
> + * Based on decompress_unlz4.c
> + *
> + * Copyright (C) 2020, Petr Malat <oss@xxxxxxxxx>
> + */
> +
> +#ifdef STATIC
> +#define PREBOOT
> +#include "zstd/zstd_internal.h"
> +#include "zstd/huf_decompress.c"
> +#include "zstd/entropy_common.c"
> +#include "zstd/fse_decompress.c"
> +#include "zstd/zstd_common.c"
> +#include "zstd/decompress.c"
> +#include "xxhash.c"
> +#else
> +#include <linux/decompress/unzstd.h>
> +#include <linux/zstd.h>
> +#endif
> +#include <linux/types.h>
> +#include <linux/decompress/mm.h>
> +#include <linux/compiler.h>
> +
> +STATIC inline int INIT unzstd(u8 *input, long in_len,
> +				long (*fill)(void *, unsigned long),
> +				long (*flush)(void *, unsigned long),
> +				u8 *output, long *posp,
> +				void (*error)(char *x))
> +{
> +	int ret = -1, ws = 1 << ZSTD_WINDOWLOG_MAX;
> +	u8 *inp, *outp;
> +	ZSTD_DStream *zstd;
> +	void *workspace;
> +	size_t workspace_size;
> +	ZSTD_outBuffer out;
> +	ZSTD_inBuffer in;
> +	unsigned long out_len;
> +	unsigned long pos;
> +
> +	if (output) {
> +		out_len = ULONG_MAX; // Caller knows data will fit
> +		outp = output;
> +	} else if (!flush) {
> +		error("NULL output pointer and no flush function provided");
> +		goto exit_0;
> +	} else {
> +		out_len = ZSTD_DStreamOutSize();
> +		outp = large_malloc(out_len);
> +		if (!outp) {
> +			error("Could not allocate output buffer");
> +			goto exit_0;
> +		}
> +	}
> +
> +	if (input && fill) {
> +		error("Both input pointer and fill function provided,");
> +		goto exit_1;
> +	} else if (input) {
> +		ZSTD_frameParams p;
> +
> +		inp = input;
> +		if (!ZSTD_getFrameParams(&p, input, in_len))
> +			ws = p.windowSize;
> +	} else if (!fill) {
> +		error("NULL input pointer and missing fill function");
> +		goto exit_1;
> +	} else {
> +		in_len = ZSTD_DStreamInSize();
> +		inp = large_malloc(in_len);
> +		if (!inp) {
> +			error("Could not allocate input buffer");
> +			goto exit_1;
> +		}
> +	}
> +
> +	workspace_size = ZSTD_DStreamWorkspaceBound(ws);
> +	workspace = large_malloc(workspace_size);
> +	if (!workspace) {
> +		error("Could not allocate workspace");
> +		goto exit_2;
> +	}
> +
> +	zstd = ZSTD_initDStream(ws, workspace, workspace_size);
> +	if (!zstd) {
> +		error("Could not initialize ZSTD");
> +		goto exit_3;
> +	}
> +
> +	in.src = inp;
> +	in.size = in_len;
> +	in.pos = 0;
> +	if (posp)
> +		*posp = 0;
> +
> +	for (;;) {
> +		if (fill) {
> +			in.size = fill(inp, in_len);
> +			if (in.size == 0)
> +				break;
> +		} else if (in.size == in.pos) {
> +			break;
> +		}
> +init:		out.dst = outp;
> +		out.size = out_len;
> +		out.pos = 0;
> +		pos = in.pos;
> +
> +		ret = ZSTD_decompressStream(zstd, &out, &in);
> +		if (posp)
> +			*posp += in.pos - pos;
> +		if (ZSTD_isError(ret)) {
> +			error("Decompression failed");
> +			ret = -EIO;
> +			goto exit_3;
> +		}
> +
> +		if (flush && out.pos) {
> +			if (flush(out.dst, out.pos) != out.pos) {
> +				ret = -EIO;
> +				goto exit_3;
> +			}
> +			goto init;
> +		}
> +
> +		if (ret == 0) {
> +			ret = ZSTD_resetDStream(zstd);
> +			if (ZSTD_isError(ret)) {
> +				ret = -EIO;
> +				goto exit_3;
> +			}
> +		}
> +		if (in.pos < in.size)
> +			goto init_out;
> +	}
> +
> +	ret = 0;
> +
> +exit_3:	large_free(workspace);
> +exit_2:	if (!input)
> +		large_free(inp);
> +exit_1:	if (!output)
> +		large_free(outp);
> +exit_0:	return ret;
> +}
> +
> +#ifdef PREBOOT
> +STATIC int INIT __decompress(unsigned char *buf, long in_len,
> +			      long (*fill)(void*, unsigned long),
> +			      long (*flush)(void*, unsigned long),
> +			      unsigned char *output, long out_len,
> +			      long *posp,
> +			      void (*error)(char *x)
> +	)
> +{
> +	return unzstd(buf, in_len, fill, flush, output, posp, error);
> +}
> +#endif
> diff --git a/lib/zstd/decompress.c b/lib/zstd/decompress.c
> index 269ee9a796c1..6a5e1ce22719 100644
> --- a/lib/zstd/decompress.c
> +++ b/lib/zstd/decompress.c
> @@ -42,9 +42,11 @@
> /*-*************************************
> *  Macros
> ***************************************/
> +#ifndef PREBOOT
> #define ZSTD_isError ERR_isError /* for inlining */
> #define FSE_isError ERR_isError
> #define HUF_isError ERR_isError
> +#endif
> 
> /*_*******************************************************
> *  Memory operations
> diff --git a/lib/zstd/fse_decompress.c b/lib/zstd/fse_decompress.c
> index a84300e5a013..bd4e9c891d96 100644
> --- a/lib/zstd/fse_decompress.c
> +++ b/lib/zstd/fse_decompress.c
> @@ -54,12 +54,13 @@
> /* **************************************************************
> *  Error Management
> ****************************************************************/
> -#define FSE_isError ERR_isError
> #define FSE_STATIC_ASSERT(c)                                   \
> 	{                                                      \
> 		enum { FSE_static_assert = 1 / (int)(!!(c)) }; \
> 	} /* use only *after* variable declarations */
> 
> +#ifndef PREBOOT
> +#define FSE_isError ERR_isError
> /* check and forward error code */
> #define CHECK_F(f)                  \
> 	{                           \
> @@ -67,6 +68,7 @@
> 		if (FSE_isError(e)) \
> 			return e;   \
> 	}
> +#endif
> 
> /* **************************************************************
> *  Templates
> diff --git a/scripts/Makefile.lib b/scripts/Makefile.lib
> index 3fa32f83b2d7..1c2f2dc528dc 100644
> --- a/scripts/Makefile.lib
> +++ b/scripts/Makefile.lib
> @@ -337,6 +337,9 @@ quiet_cmd_lz4 = LZ4     $@
>       cmd_lz4 = { cat $(real-prereqs) | lz4c -l -c1 stdin stdout; \
>                   $(size_append); } > $@
> 
> +quiet_cmd_zstd = ZSTD    $@
> +      cmd_zstd = { cat $(real-prereqs) | zstd -19 --zstd=wlog=21; $(size_append); } > $@
> +
> # U-Boot mkimage
> # ---------------------------------------------------------------------------
> 
> diff --git a/usr/Kconfig b/usr/Kconfig
> index a6b68503d177..892eb15957db 100644
> --- a/usr/Kconfig
> +++ b/usr/Kconfig
> @@ -106,6 +106,15 @@ config RD_LZ4
> 	  Support loading of a LZ4 encoded initial ramdisk or cpio buffer
> 	  If unsure, say N.
> 
> +config RD_ZSTD
> +	bool "Support initial ramdisk/ramfs compressed using ZSTD"
> +	default y
> +	depends on BLK_DEV_INITRD
> +	select DECOMPRESS_ZSTD
> +	help
> +	  Support loading of a ZSTD encoded initial ramdisk or cpio buffer
> +	  If unsure, say N.
> +
> choice
> 	prompt "Built-in initramfs compression mode"
> 	depends on INITRAMFS_SOURCE!=""
> @@ -214,6 +223,19 @@ config INITRAMFS_COMPRESSION_LZ4
> 	  If you choose this, keep in mind that most distros don't provide lz4
> 	  by default which could cause a build failure.
> 
> +config INITRAMFS_COMPRESSION_ZSTD
> +	bool "ZSTD"
> +	depends on RD_ZSTD
> +	help
> +	  Its compression ratio is roughly 10% worst than xz, but the
> +	  decompression is 10x faster. Currently, this is one of the optimal
> +	  algorithms available in the kernel, as there isn't an algorithm,
> +	  which would provide a better compression ratio and a shorter
> +	  decompression time.
> +
> +	  If you choose this, keep in mind that you may need to install the zstd
> +	  tool to be able to compress the initram.
> +
> endchoice
> 
> config INITRAMFS_COMPRESSION
> @@ -226,10 +248,12 @@ config INITRAMFS_COMPRESSION
> 	default ".xz"   if INITRAMFS_COMPRESSION_XZ
> 	default ".lzo"  if INITRAMFS_COMPRESSION_LZO
> 	default ".lz4"  if INITRAMFS_COMPRESSION_LZ4
> +	default ".zst"  if INITRAMFS_COMPRESSION_ZSTD
> 	default ".gz"   if RD_GZIP
> 	default ".lz4"  if RD_LZ4
> 	default ".lzo"  if RD_LZO
> 	default ".xz"   if RD_XZ
> 	default ".lzma" if RD_LZMA
> 	default ".bz2"  if RD_BZIP2
> +	default ".zst"  if RD_ZSTD
> 	default ""
> -- 
> 2.20.1
> 





[Index of Archives]     [Linux&nblp;USB Development]     [Linux Media]     [Video for Linux]     [Linux Audio Users]     [Yosemite Secrets]     [Linux Kernel]     [Linux SCSI]

  Powered by Linux