Add zlib support

* New args to cbd format.
 * HAVE_* -> COMPRESS_HAVE_*.
 * Verify header params algorithm and compression.
 * Move percpu alloc to lbdcache.
 * Alloc percpu data in lbdcache_ctr, free in lbdcache_dtr.
This commit is contained in:
Tom Marshall 2019-10-31 10:24:20 -07:00
parent eeafc209a5
commit 164a09b9aa
7 changed files with 325 additions and 101 deletions

18
TODO
View File

@ -1,22 +1,6 @@
In lbd, atomic writes will require a pblk array in the lbd object. Not sure
how to roll back partial allocations yet but it should be doable.
For async reads:
- lbd_read() is called by compress_read() and compress_write().
lbd may have multiple simultaneous callers.
lbd calls lbatview_read() and reads its own data.
- lbatview_read() is called by lbd.
lbatview may have multiple simultaneous callers.
lbatview calls pbat_read() and reads its own data.
- pbat_read() is called by lbatview_alloc_pblk() and lbatview_free_pblk().
pbat may have multiple simultaneous callers.
pbat calls pbat_read().
Rework cache ownership:
- compress_open() should alloc only lbdcache.
- lbdcache should alloc only lbatviewcache.
- lbatviewcache should alloc lbatpagecache and pbatcache.
Cache object sizing:
- lbdcache size: multiple of num_online_cpus().
- lbatviewcache:
@ -37,8 +21,6 @@ Cache object sizing:
1/2 lbatviewcache size is way too large.
Ratio of lbatview to pbat is 1:lbat_per_pbat.
Cache objects should dynamically expand.
TODO:
- Move back to module based build system.

View File

@ -23,7 +23,7 @@
* If no suffix is given, bytes are assumed.
*/
static bool
parse_arg(const char* arg, uint64_t* val)
parse_numeric_arg(const char* arg, uint64_t* val)
{
unsigned long ulval;
const char* end;
@ -88,6 +88,8 @@ usage(void)
" -c --compress-factor Compression factor [2.0]\n"
" -l --logical-shift Logical block shift [4]\n"
" -s --size Logical size\n"
" -z --compress-alg Compression algorithm [lz4]\n"
" -Z --compress-level Compression level [?]\n"
" Note:\n"
" -c and -s are different ways of specifying the compressed device size.\n"
" Only one may be used, not both.\n"
@ -110,13 +112,15 @@ usage(void)
static int
do_format(int argc, char** argv)
{
static const char short_opts[] = "S:L:c:l:s:";
static const char short_opts[] = "S:L:c:l:s:z:Z:";
static const struct option long_opts[] = {
{ "physical-size", required_argument, NULL, 'S' },
{ "logical-blksize", required_argument, NULL, 'L' },
{ "compress-factor", required_argument, NULL, 'c' },
{ "logical-shift", required_argument, NULL, 'l' },
{ "size", required_argument, NULL, 's' },
{ "compress-alg", required_argument, NULL, 'z' },
{ "compress-level", required_argument, NULL, 'Z' },
{ NULL, no_argument, NULL, 0 }
};
char opt;
@ -124,19 +128,21 @@ do_format(int argc, char** argv)
uint64_t psize = 0;
uint16_t lshift = 0;
uint64_t lsize = 0;
enum cbd_alg alg = CBD_ALG_LZ4;
uint level = 1;
const char* dev;
while ((opt = getopt_long(argc, argv, short_opts, long_opts, NULL)) != -1) {
switch (opt) {
case 'S':
if (!parse_arg(optarg, &optval)) {
if (!parse_numeric_arg(optarg, &optval)) {
error("Failed to parse \"%s\"\n", optarg);
}
psize = optval;
break;
case 'L':
if (!parse_arg(optarg, &optval)) {
if (!parse_numeric_arg(optarg, &optval)) {
error("Failed to parse \"%s\"\n", optarg);
}
if ((optval & (optval-1)) || optval < PBLK_SIZE) {
@ -148,17 +154,38 @@ do_format(int argc, char** argv)
error("Implement me!\n");
break;
case 'l':
if (!parse_arg(optarg, &optval)) {
if (!parse_numeric_arg(optarg, &optval)) {
error("Failed to parse \"%s\"\n", optarg);
}
lshift = optval;
break;
case 's':
if (!parse_arg(optarg, &optval)) {
if (!parse_numeric_arg(optarg, &optval)) {
error("Failed to parse \"%s\"\n", optarg);
}
lsize = optval;
break;
case 'z':
alg = CBD_ALG_NONE;
if (!strcmp(optarg, "lz4")) {
alg = CBD_ALG_LZ4;
}
if (!strcmp(optarg, "zlib")) {
alg = CBD_ALG_ZLIB;
}
if (alg == CBD_ALG_NONE) {
error("Invalid compression algorithm \"%s\"\n", optarg);
}
break;
case 'Z':
if (!parse_numeric_arg(optarg, &optval)) {
error("Failed to parse \"%s\"\n", optarg);
}
if (optval < 1 || optval > 9) {
error("Compression level \"%s\" out of bounds\n", optarg);
}
level = optval;
break;
default:
usage();
}
@ -168,7 +195,7 @@ do_format(int argc, char** argv)
}
dev = argv[optind++];
cbd_format(dev, psize, lshift, lsize);
cbd_format(dev, psize, lshift, lsize, alg, level);
return 0;
}
@ -284,7 +311,7 @@ do_resize(int argc, char** argv)
while ((opt = getopt_long(argc, argv, short_opts, long_opts, NULL)) != -1) {
switch (opt) {
case 's':
if (!parse_arg(optarg, &optval)) {
if (!parse_numeric_arg(optarg, &optval)) {
fprintf(stderr, "Failed to parse \"%s\"\n", optarg);
usage();
}

View File

@ -53,7 +53,6 @@ struct compress
struct dm_dev* dev;
struct cbd_params params;
void* percpu;
struct lbdcache* lc;
struct workqueue_struct* io_workq;
@ -119,7 +118,31 @@ compress_open(struct compress* c, u64 dev_nr_pblks)
err = -EINVAL;
goto out;
}
if (header.params.algorithm == CBD_ALG_NONE ||
header.params.algorithm >= CBD_ALG_MAX) {
printk(KERN_ERR "%s: bad algorithm\n", __func__);
err = -EINVAL;
goto out;
}
#ifndef COMPRESS_HAVE_LZ4
if (header.params.algorithm == CBD_ALG_LZ4) {
printk(KERN_ERR "%s: algorithm lz4 is not built into kernel\n", __func__);
err = -EINVAL;
goto out;
}
#endif
#ifndef COMPRESS_HAVE_ZLIB
if (header.params.algorithm == CBD_ALG_ZLIB) {
printk(KERN_ERR "%s: algorithm zlib is not built into kernel\n", __func__);
err = -EINVAL;
goto out;
}
#endif
if (header.params.compression < 1 || header.params.compression > 9) {
printk(KERN_ERR "%s: bad compression\n", __func__);
err = -EINVAL;
goto out;
}
if (header.params.lblk_shift < LBLK_SHIFT_MIN ||
header.params.lblk_shift > LBLK_SHIFT_MAX) {
printk(KERN_ERR "%s: bad lblk_shift\n", __func__);
@ -151,7 +174,6 @@ compress_open(struct compress* c, u64 dev_nr_pblks)
printk(KERN_INFO " lblk_per_zone=%u\n", (unsigned int)header.params.lblk_per_zone);
memcpy(&c->params, &header.params, sizeof(header.params));
c->percpu = alloc_percpu(void*);
c->lc = kmalloc(lbdcache_size(), GFP_KERNEL);
if (!c->lc) {
@ -159,7 +181,7 @@ compress_open(struct compress* c, u64 dev_nr_pblks)
printk(KERN_ERR "Failed to alloc lbdcache\n");
goto out;
}
if (!lbdcache_ctr(c->lc, &c->params, c->percpu)) {
if (!lbdcache_ctr(c->lc, &c->params)) {
err = -ENOMEM;
printk(KERN_ERR "Failed to init logical block cache\n");
goto out;
@ -406,7 +428,6 @@ compress_dtr(struct dm_target *ti)
c = ti->private;
lbdcache_dtr(c->lc);
kfree(c->lc);
free_percpu(c->percpu);
if (c->io_workq) {
destroy_workqueue(c->io_workq);
}

View File

@ -26,6 +26,7 @@
#include <linux/mutex.h>
#include <linux/lz4.h>
#include <linux/zlib.h>
#include <linux/dm-compress.h>
@ -91,65 +92,45 @@ lblk_is_zeros(struct cbd_params* params, struct lbd* lbd)
#endif
}
struct lz4_state {
u8* wrkmem;
struct lblk_compress_state {
struct page* pages;
u8* buf;
#ifdef COMPRESS_HAVE_LZ4
u8* lz4_workmem;
#endif
#ifdef COMPRESS_HAVE_ZLIB
z_stream zlib_cstream;
z_stream zlib_dstream;
#endif
};
static struct lz4_state*
lblk_get_lz4_state(struct lbd* lbd, int cpu)
static struct lblk_compress_state*
lblk_get_compress_state(void* percpu, const struct cbd_params* params, int cpu)
{
struct lz4_state** statep;
struct lz4_state* state;
struct lblk_compress_state** statep;
statep = per_cpu_ptr(lbd->percpu, cpu);
if (*statep) {
return *statep;
}
state = kmalloc(sizeof(struct lz4_state), GFP_NOWAIT);
if (!state) {
printk(KERN_ERR "%s: failed to alloc state\n", __func__);
return NULL;
}
state->wrkmem = kmalloc(LZ4_compressBound(PBLK_SIZE * lblk_per_pblk(lbd->params)), GFP_NOWAIT);
state->pages = cbd_alloc_pages_nowait(lblk_per_pblk(lbd->params));
if (!state->wrkmem || !state->pages) {
kfree(state->wrkmem);
cbd_free_pages(state->pages, lblk_per_pblk(lbd->params));
kfree(state);
printk(KERN_ERR "%s: failed to alloc buffers\n", __func__);
return NULL;
}
state->buf = page_address(state->pages);
*statep = state;
return state;
statep = per_cpu_ptr(percpu, cpu);
return *statep;
}
/*
* Compress dc->lblk into dc->lz4_cbuf
*
* Returns number of bytes in cbuf or 0 for failure.
*/
#ifdef COMPRESS_HAVE_LZ4
static size_t
lblk_compress(struct lbd* lbd)
lblk_compress_lz4(struct lbd* lbd)
{
int clen;
int cpu;
struct lz4_state* state;
struct lblk_compress_state* state;
cpu = get_cpu();
state = lblk_get_lz4_state(lbd, cpu);
state = lblk_get_compress_state(lbd->percpu, lbd->params, cpu);
if (!state) {
put_cpu();
return 0;
}
clen = LZ4_compress_default(lbd->buf,
state->buf,
clen = LZ4_compress_fast(lbd->buf, state->buf,
PBLK_SIZE * lblk_per_pblk(lbd->params),
PBLK_SIZE * (lblk_per_pblk(lbd->params) - 1),
state->wrkmem);
lbd->params->compression, state->lz4_workmem);
if (clen <= 0) {
put_cpu();
return 0;
@ -160,6 +141,130 @@ lblk_compress(struct lbd* lbd)
return (size_t)clen;
}
static bool
lblk_decompress_lz4(struct lbd* lbd, u32 clen)
{
int ret;
int cpu;
struct lblk_compress_state* state;
u32 dlen = PBLK_SIZE * lblk_per_pblk(lbd->params);
cpu = get_cpu();
state = lblk_get_compress_state(lbd->percpu, lbd->params, cpu);
if (!state) {
put_cpu();
return false;
}
ret = LZ4_decompress_safe(lbd->buf,
state->buf,
clen,
dlen);
if (ret != dlen) {
put_cpu();
return false;
}
memcpy(lbd->buf, state->buf, dlen);
put_cpu();
return true;
}
#endif
#ifdef COMPRESS_HAVE_ZLIB
static size_t
lblk_compress_zlib(struct lbd* lbd)
{
int ret;
int cpu;
struct lblk_compress_state* state;
z_stream* stream;
cpu = get_cpu();
state = lblk_get_compress_state(lbd->percpu, lbd->params, cpu);
if (!state) {
put_cpu();
return 0;
}
stream = &state->zlib_cstream;
ret = zlib_deflateReset(stream);
BUG_ON(ret != Z_OK);
stream->next_in = lbd->buf;
stream->avail_in = PBLK_SIZE * lblk_per_pblk(lbd->params);
stream->next_out = state->buf;
stream->avail_out = PBLK_SIZE * (lblk_per_pblk(lbd->params) - 1);
ret = zlib_deflate(stream, Z_FINISH);
if (ret != Z_STREAM_END) {
put_cpu();
return 0;
}
memcpy(lbd->buf, state->buf, stream->total_out);
put_cpu();
return stream->total_out;
}
static bool
lblk_decompress_zlib(struct lbd* lbd, u32 clen)
{
int ret;
int cpu;
struct lblk_compress_state* state;
z_stream* stream;
u32 dlen = PBLK_SIZE * lblk_per_pblk(lbd->params);
cpu = get_cpu();
state = lblk_get_compress_state(lbd->percpu, lbd->params, cpu);
if (!state) {
put_cpu();
return false;
}
stream = &state->zlib_dstream;
ret = zlib_inflateReset(stream);
BUG_ON(ret != Z_OK);
stream->next_in = lbd->buf;
stream->avail_in = clen;
stream->next_out = state->buf;
stream->avail_out = dlen;
ret = zlib_inflate(stream, Z_SYNC_FLUSH);
/* See xxx */
if (ret == Z_OK && !stream->avail_in && stream->avail_out) {
u8 zerostuff = 0;
stream->next_in = &zerostuff;
stream->avail_in = 1;
ret = zlib_inflate(stream, Z_FINISH);
}
if (ret != Z_STREAM_END || stream->total_out != dlen) {
put_cpu();
return false;
}
memcpy(lbd->buf, state->buf, dlen);
put_cpu();
return true;
}
#endif
/*
* Compress dc->lblk into dc->lz4_cbuf
*
* Returns number of bytes in cbuf or 0 for failure.
*/
static size_t
lblk_compress(struct lbd* lbd)
{
#ifdef COMPRESS_HAVE_LZ4
if (lbd->params->algorithm == CBD_ALG_LZ4) {
return lblk_compress_lz4(lbd);
}
#endif
#ifdef COMPRESS_HAVE_ZLIB
if (lbd->params->algorithm == CBD_ALG_ZLIB) {
return lblk_compress_zlib(lbd);
}
#endif
return 0;
}
/*
* Decompress dc->lz4_cbuf of size clen into dc->lblk
*
@ -168,29 +273,17 @@ lblk_compress(struct lbd* lbd)
static int
lblk_decompress(struct lbd* lbd, u32 clen)
{
int ret;
int cpu;
struct lz4_state* state;
u32 dlen = PBLK_SIZE * lblk_per_pblk(lbd->params);
cpu = get_cpu();
state = lblk_get_lz4_state(lbd, cpu);
if (!state) {
put_cpu();
return -1;
#ifdef COMPRESS_HAVE_LZ4
if (lbd->params->algorithm == CBD_ALG_LZ4) {
return lblk_decompress_lz4(lbd, clen);
}
ret = LZ4_decompress_safe(lbd->buf,
state->buf,
clen,
dlen);
if (ret != dlen) {
put_cpu();
return -1;
#endif
#ifdef COMPRESS_HAVE_ZLIB
if (lbd->params->algorithm == CBD_ALG_ZLIB) {
return lblk_decompress_zlib(lbd, clen);
}
memcpy(lbd->buf, state->buf, dlen);
put_cpu();
return 0;
#endif
return false;
}
static bool
@ -381,7 +474,7 @@ lbd_read(struct lbd* lbd)
}
}
if (is_compressed) {
if (lblk_decompress(lbd, c_len) != 0) {
if (!lblk_decompress(lbd, c_len)) {
printk(KERN_ERR " decompress failed\n");
ret = -EIO;
goto out;
@ -473,15 +566,94 @@ lbdcache_realloc(struct lbdcache* lc, unsigned int len)
return true;
}
static bool
lbdcache_alloc_compress_state(void* percpu, const struct cbd_params* params, int cpu)
{
struct lblk_compress_state* state;
struct lblk_compress_state** statep;
size_t workmem_len;
#ifdef COMPRESS_HAVE_ZLIB
int ret;
#endif
state = kzalloc(sizeof(struct lblk_compress_state), GFP_NOWAIT);
if (!state) {
printk(KERN_ERR "%s: failed to alloc state\n", __func__);
return false;
}
statep = per_cpu_ptr(percpu, cpu);
*statep = state;
state->pages = cbd_alloc_pages_nowait(lblk_per_pblk(params));
if (!state->pages) {
return false;
}
state->buf = page_address(state->pages);
#ifdef COMPRESS_HAVE_LZ4
workmem_len = LZ4_compressBound(PBLK_SIZE * lblk_per_pblk(params));
state->lz4_workmem = kzalloc(workmem_len, GFP_NOWAIT);
if (!state->lz4_workmem) {
return false;
}
#endif
#ifdef COMPRESS_HAVE_ZLIB
workmem_len = zlib_deflate_workspacesize(MAX_WBITS, DEF_MEM_LEVEL);
state->zlib_cstream.workspace = kzalloc(workmem_len, GFP_NOWAIT);
if (!state->zlib_cstream.workspace) {
return false;
}
ret = zlib_deflateInit2(&state->zlib_cstream, params->compression,
Z_DEFLATED, MAX_WBITS, DEF_MEM_LEVEL,
Z_DEFAULT_STRATEGY);
BUG_ON(ret != Z_OK);
workmem_len = zlib_inflate_workspacesize();
state->zlib_dstream.workspace = kzalloc(workmem_len, GFP_NOWAIT);
if (!state->zlib_dstream.workspace) {
return false;
}
ret = zlib_inflateInit2(&state->zlib_dstream, DEF_WBITS);
BUG_ON(ret != Z_OK);
#endif
return true;
}
static void
lbdcache_free_compress_state(void* percpu, const struct cbd_params* params, int cpu)
{
struct lblk_compress_state** statep;
struct lblk_compress_state* state;
statep = per_cpu_ptr(percpu, cpu);
state = *statep;
if (!state) {
return;
}
#ifdef COMPRESS_HAVE_ZLIB
kfree(state->zlib_dstream.workspace);
kfree(state->zlib_cstream.workspace);
#endif
#ifdef COMPRESS_HAVE_LZ4
kfree(state->lz4_workmem);
#endif
cbd_free_pages(state->pages, lblk_per_pblk(params));
kfree(state);
}
bool
lbdcache_ctr(struct lbdcache* lc,
struct cbd_params* params,
void* percpu)
struct cbd_params* params)
{
int cpu;
memset(lc, 0, sizeof(struct lbdcache));
mutex_init(&lc->lock);
lc->params = params;
lc->percpu = percpu;
lc->percpu = alloc_percpu(void*);
for (cpu = 0; cpu < num_online_cpus(); ++cpu) {
if (!lbdcache_alloc_compress_state(lc->percpu, params, cpu)) {
return false;
}
}
lc->lvc = kzalloc(lbatviewcache_size(), GFP_KERNEL);
if (!lc->lvc) {
return false;
@ -498,6 +670,7 @@ lbdcache_dtr(struct lbdcache* lc)
{
unsigned int n;
struct lbd* lbd;
int cpu;
for (n = 0; n < lc->len; ++n) {
lbd = lc->cache[n];
@ -516,6 +689,11 @@ lbdcache_dtr(struct lbdcache* lc)
lbatviewcache_dtr(lc->lvc);
kfree(lc->lvc);
lc->lvc = NULL;
for (cpu = 0; cpu < num_online_cpus(); ++cpu) {
lbdcache_free_compress_state(lc->percpu, lc->params, cpu);
}
free_percpu(lc->percpu);
lc->percpu = NULL;
lc->params = NULL;
}

View File

@ -50,7 +50,8 @@ typedef enum {
} tristate_t;
int cbd_format(const char* dev,
uint64_t psize, uint16_t lshift, uint64_t lsize);
uint64_t psize, uint16_t lshift, uint64_t lsize,
enum cbd_alg alg, uint level);
int cbd_open(const char* dev,
const char* name);
int cbd_close(const char* name);

View File

@ -444,6 +444,13 @@ lba_put(const struct cbd_params* params,
#ifdef __KERNEL__
#if defined(CONFIG_LZ4_COMPRESS) || defined(CONFIG_LZ4_COMPRESS_MODULE)
#define COMPRESS_HAVE_LZ4 1
#endif
#if defined(CONFIG_ZLIB_INFLATE) || defined(CONFIG_ZLIB_INFLATE_MODULE)
#define COMPRESS_HAVE_ZLIB 1
#endif
enum cache_state {
CACHE_STATE_UNCACHED,
CACHE_STATE_CLEAN,
@ -548,8 +555,7 @@ void lbd_data_write(struct lbd* lbd, u32 off, u32 len, const u8* buf);
struct lbdcache;
size_t lbdcache_size(void);
bool lbdcache_ctr(struct lbdcache* lc,
struct cbd_params* params,
void* percpu);
struct cbd_params* params);
void lbdcache_dtr(struct lbdcache* lc);
struct lbd*
lbdcache_get(struct lbdcache* lc, u64 lblk);

View File

@ -27,7 +27,8 @@ pblk_write(int fd, u64 pblk, u32 count, const u8* data)
int
cbd_format(const char* dev,
uint64_t psize, uint16_t lshift, uint64_t lsize)
uint64_t psize, uint16_t lshift, uint64_t lsize,
enum cbd_alg alg, uint level)
{
int devfd;
uint32_t lblk_size;
@ -67,6 +68,12 @@ cbd_format(const char* dev,
if (lsize % (1 << (lshift + PBLK_SHIFT))) {
error("Logical size %lu is not a multiple of %u bytes\n", (ulong)psize, (uint)lblk_size);
}
if (alg <= CBD_ALG_NONE || alg >= CBD_ALG_MAX) {
error("Compression algorithm %d unknown\n", (int)alg);
}
if (level < 1 || level > 9) {
error("Compression level %u out of bounds\n", level);
}
printf("%s: paramaters...\n", __func__);
printf(" psize=%lu\n", (unsigned long)psize);
@ -76,8 +83,8 @@ cbd_format(const char* dev,
memcpy(header.magic, CBD_MAGIC, sizeof(header.magic));
header.version_major = CBD_VERSION_MAJOR;
header.version_minor = CBD_VERSION_MINOR;
header.params.algorithm = CBD_ALG_LZ4;
header.params.compression = 1;
header.params.algorithm = alg;
header.params.compression = level;
header.params.lblk_shift = lshift;
header.params.nr_pblk = psize / PBLK_SIZE;
/* XXX: Initial estimate */
@ -86,6 +93,8 @@ cbd_format(const char* dev,
header.params.nr_zones = ((psize >> PBLK_SHIFT) - CBD_HEADER_BLOCKS) / zone_len(&header.params);
header.params.lblk_per_zone = DIV_ROUND_UP(lsize / lblk_size, header.params.nr_zones);
printf("%s: parameters...\n", __func__);
printf(" algorithm=%d\n", (int)header.params.algorithm);
printf(" compression=%u\n", (unsigned int)header.params.compression);
printf(" lblk_shift=%hu\n", (unsigned short)header.params.lblk_shift);
printf(" nr_pblk=%lu\n", (unsigned long)header.params.nr_pblk);
printf(" nr_zones=%lu\n", (unsigned long)header.params.nr_zones);