LinuxカーネルHack: LZOリアルタイムデータ圧縮ライブラリをカーネルモジュールから使う実験

前回、カーネルモジュールをUMLでいじれるようになったので、次は何をしようかと思って、今回はLZOで遊んでみることにした。最初は、zlibで遊ぼうかと思ったけれども、ちょっと違う方が面白そうだったので。それに、BtrfsのWikiの「Project ideas」(https://btrfs.wiki.kernel.org/index.php/Project_ideas)にLZO圧縮について書いてあって、ちょっと興味が出たから。うまくいけば、BtrfsのLZO組対応も今後検討してみたいから。

LZOとは

ロスレスの圧縮/展開ライブラリ。高速にリアルタイムで圧縮/展開ができるのが特徴。
http://www.oberhumer.com/opensource/lzo/

ちなみに、Btrfsではzlibを使っていて、LZOよりも遅い。BtrfsのWikiの「Project ideas」にも書いてあるけれども、BtrfsがLZOを対応できれば、より多くのユーザーが圧縮機能を有効にする可能性が高まりそう。

準備

LZOのソースを落とす。lzoのexamples/simple.cを見たら大体使い方はわかった。zlibと大してかわらない。むしろ、zlibよりもシンプル。

ビルドして、example/simple.cを動かしてみる。simple.cは、131072(128*1024)バイトの0でうまったバイト列を単に圧縮し、圧縮した結果を、逆に展開しているだけ。

% wget http://www.oberhumer.com/opensource/lzo/download/lzo-2.03.tar.gz
% tar xvf lzo-2.03.tar.gz
% lzo-2.03
% make
% cd examples
% ./simple

LZO real-time data compression library (v2.03, Apr 30 2008).
Copyright (C) 1996-2008 Markus Franz Xaver Johannes Oberhumer
All Rights Reserved.

compressed 131072 bytes into 526 bytes
decompressed 526 bytes back into 131072 bytes
Simple compression test passed.

カーネルからLZOを使うには、まずはLZOをカーネルに組み込む必要があるから、面倒そう。まさか無いだろうなと思いつつ、カーネルのlib以下を見てたら、ラッキーにもlzoを発見。すでにカーネルに取り込まれている。手間が省けた。以下のソースを読んで、ざっくり使い方をつかんだ。

  • include/linux/lzo.h
  • lib/lzo/*
  • crypto/lzo.c
  • drivers/staging/ramzswap/ramzswap_drv.c
  • fs/jffs2/compr_lzo.c

LZOはデフォルトではUMLカーネルに組み込まれてない様子。menuconfigでそれらしきものを探す。Cryptographic API -> LZO compression algorithmをカーネルに組み込んでみた。

カーネルをビルドしてみる。途中経過を見ると、lib/lzo以下がビルドされてる。いけてそう。

% time make -j 3 ARCH=um
scripts/kconfig/conf --silentoldconfig arch/um/Kconfig.x86
  CHK     include/linux/version.h
make[1]: `arch/um/sys-i386/user-offsets.s' は更新済みです
  CHK     arch/um/include/shared/user_constants.h
  CHK     include/generated/utsrelease.h
  CALL    scripts/checksyscalls.sh
  CHK     include/generated/compile.h
  QUOTE   arch/um/kernel/config.tmp
  QUOTE   arch/um/kernel/config.c
  CC      arch/um/kernel/config.o
  LD      arch/um/kernel/built-in.o
  GZIP    kernel/config_data.gz
  IKCFG   kernel/config_data.h
  CC      kernel/configs.o
  LD      kernel/built-in.o
  CC      crypto/lzo.o
  LD      crypto/built-in.o
  CC      lib/lzo/lzo1x_compress.o
  CC      lib/lzo/lzo1x_decompress.o
  LD      lib/lzo/lzo_compress.o
  LD      lib/lzo/lzo_decompress.o
  LD      lib/lzo/built-in.o
  LD      lib/built-in.o
  LD      vmlinux.o
  MODPOST vmlinux.o
  GEN     .version
  CHK     include/generated/compile.h
  UPD     include/generated/compile.h
  CC      init/version.o
  LD      init/built-in.o
  LD      .tmp_vmlinux1
  KSYM    .tmp_kallsyms1.S
  AS      .tmp_kallsyms1.o
  LD      .tmp_vmlinux2
  KSYM    .tmp_kallsyms2.S
  AS      .tmp_kallsyms2.o
  LD      .tmp_vmlinux3
  KSYM    .tmp_kallsyms3.S
  AS      .tmp_kallsyms3.o
  LD      vmlinux
  SYSMAP  System.map
  SYSMAP  .tmp_System.map
  LINK linux
  Building modules, stage 2.
  MODPOST 16 modules

real	1m22.406s
user	0m7.064s
sys	0m27.190s

LZOが使えるようになったことをGDBで確認。LZOが組み込まれた!

% gdb ./linux
(gdb) b lzo (タブを入力)
lzo.c                  lzo1x_decompress_safe  lzo_exit
lzo1x_1_compress       lzo_compress           lzo_init
lzo1x_compress.c       lzo_ctx                lzo_mod_fini
lzo1x_decompress.c     lzo_decompress         lzo_mod_init

コード

lzoのexamples/simple.cとfs/jffs2/compr_lzo.cを参考に作ってみた。example/simple.cと同じく、0バイトのバイト列を圧縮するという無意味な。。

% cat lzo/lzo.c
#include <linux/module.h>
#include <linux/init.h>
#include <linux/vmalloc.h>
#include <linux/lzo.h>

MODULE_LICENSE("GPL");

#define IN_LEN (128*1024L)

static void *in_buf;
static void *lzo_mem;
static void *lzo_compress_buf;
static void *lzo_decompress_buf;

static int lzo_init(void)
{
    int ret;
    size_t compress_len;
    size_t in_len;
    size_t out_len;

    printk(KERN_ALERT "enter lzo_init\n");

    in_len = IN_LEN;
    in_buf = vmalloc(in_len);
    lzo_mem = vmalloc(LZO1X_MEM_COMPRESS);
    lzo_compress_buf = vmalloc(lzo1x_worst_compress(in_len));
    lzo_decompress_buf = vmalloc(in_len);
    if (!in_buf || !lzo_mem || !lzo_compress_buf || !lzo_decompress_buf)
    {
        printk(KERN_ALERT "failed to allocate memory");
        return -ENOMEM;
    }

    /* Fill with zero */
    memset(in_buf, 0, in_len);

    /* Complession */
    ret = lzo1x_1_compress(in_buf, in_len, lzo_compress_buf, &compress_len, lzo_mem);
    if (ret != LZO_E_OK)
    {
        printk(KERN_ALERT "failed to compress memory");
        return -1;
    }
    if (compress_len >= in_len)
    {
        printk(KERN_ALERT "this block comtains incompressible data");
        return -1;
    }

    printk(KERN_ALERT "in_len = %d, compress_len = %d", in_len, compress_len);

    /* Decompression */
    out_len = in_len;
    ret = lzo1x_decompress_safe(lzo_compress_buf, compress_len, lzo_decompress_buf, &out_len);
    if (ret != LZO_E_OK || out_len != in_len)
    {
        printk(KERN_ALERT "failed to decompress memory ret=%d, out_len=%d, in_len=%d", ret, out_len, in_len);
        return -1;
    }
    return 0;
}

static void lzo_exit(void)
{
    printk(KERN_ALERT "enter lzo_exit\n");

    vfree(in_buf);
    vfree(lzo_mem);
    vfree(lzo_compress_buf);
    vfree(lzo_decompress_buf);
}

module_init(lzo_init);
module_exit(lzo_exit);

ビルド&実行

前回と同じ方法でビルド。実行すると、131072(128*1024)バイトから526に圧縮されたと。これはexamples/simple.cを実行した時と同じ結果。

% make ARCH=um -C /mnt/home/xxx/linux-2.6/ M=`pwd` modules
# dmesg -c
# insmod lzo.ko
# rmsmod lzo.ko
# dmesg -c
enter lzo_init
in_len = 131072, compress_len = 526
enter lzo_exit

さいごに

BtrfsのLZOへの対応はそれほど手間をかけずにできそうな感触。ただ、fs/btrfs/zlib.cはそれなりに複雑なので、もう少し理解を深めてからでないと難しそう。