1. Qiita
  2. 投稿
  3. Linux

お手軽Linuxカーネル開発/テスト

  • 2
    いいね
  • 0
    コメント

はじめに

これはLinux Advent Calendar 2016 14日目の記事です。

本記事では、お手軽にlinuxカーネル開発/テスト環境を構築するツール、elkdt(以下、本ツール)、およびその活用方法を紹介します。本記事の対象読者はバリバリのカーネル開発者だけでなく、自分のカーネルやカーネルモジュールを一度作ってみたかった人、開発には興味がないけれどカーネルのテスト(たとえばカーネルのバグの原因となったコミットを突き止めたい)には興味のある人も含みます。

linuxに限らず、カーネルの開発/テストは通常のソフトウェアのそれに比べて遥かに面倒です。理由は

  • ビルド、インストール、およびビルドしたカーネルを使ってのブートに、独特のお作法を覚える必要がある
  • ビルド時間が長い(とくにdistroのデフォルト設定の場合、数時間を要することもありうる)
  • テスト前後にシステムのリブートが必要
  • カーネルにバグがあればそもそもブートしなかったり、ブートしても途中でシステム全体が停止したり、データ破壊が発生する恐れがある
  • VMなどの別マシンでテストしようとしても、そのための開発環境を作るのが面倒

などです。本ツールはこれらの問題を解決し、手間をほとんどかけずにカーネル開発/テスト環境を構築できます。環境を構築した後のビルド、インストール、各種テストなどの作業もほぼ自動化されており、とても便利です。

環境

筆者は debian/stretch(testing) x86_64 を使用しました。他のdistroでは未検証です。必要に応じて、適宜読み替えて下さい。

本ツールの使用前に、以下のパッケージをインストールしておく必要があります。

  • git
  • vagrant
  • kernel-package
  • vagrant-libvirt
  • qemu-kvm
  • libvirt-daemon
  • libvirt-clients

初期化

$ git clone https://github.com/satoru-takeuchi/elkdt.git
Cloning into 'elkdt'...
...
$ cd elkdt
$ ./init
...
$ 

これでカーネルソース、テスト用VMイメージのダウンロード、各種設定などの初期化がすべて完了し、開発/テストの準備が整います。./initの実行中にVMイメージやlinuxカーネルのgitリポジトリをダウンロードしますので、回線速度が遅ければ長時間を要します。

既にカーネルソースをお持ちの方は、./initの実行前にソースをelkdtのソースディレクトリの直下にlinuxという名前で配置してください。本ツールを使用する際には、カーネルソースに対してmake mrproperを実行して既存のビルド済カーネルや.configを削除してしまうのでご注意下さい。

活用方法

以下の例に記載のコマンドは本ツールのソースディレクトリの直下から実行しています。

自分でビルドしたカーネルを動かす(ソース改変なし)

本記事執筆時点でリリースされたばかりのlinux v4.9を動かしてみましょう。次のコマンドでビルド、インストール、それを使ってのブートまですべてを実行できます。

$ cd linux
$ git checkout v4.9
$ cd ../ktest
$ ./ktest.pl
...
*******************************************
*******************************************
KTEST RESULT: TEST 1 SUCCESS!!!!         **
*******************************************
*******************************************

    1 of 1 tests were successful

$ 

成功したようです。ではVMにログインして本当に成功しているか確かめてみましょう。

$ cd ../elkdt
$ vagrant ssh
...
vagrant@packer-qemu:~$ uname -r
4.9.0-ktest
vagrant@packer-qemu:~$ 

uname -rとは現在のカーネルバージョンを確かめるためのコマンドです。ちゃんと4.9が動作していることがわかります(末尾についている"-ktest"は気にしないでください)。

最後に元のカーネルでブートしなおしましょう。

vagrant@packer-qemu:~$ exit
$ vagrant halt
...
$ vagrant reload
...
$ cd ../
$ 

これでおしまいです。再度自前カーネルで起動したい場合は次のようにします。

$ cd elkdt
$ vagrant ssh 
...
vagrant@packer-qemu:~$ sudo su 
root@packer-qemu:/home/vagrant# grub-reboot ktest
root@packer-qemu:/home/vagrant# exit
exit 
vagrant@packer-qemu:~$ exit
...
$ vagrant reload 
...
$ 

上記 git checkout の v4.9 の箇所をお好きなtagやcommit IDに変更することによって、好きなカーネルを使えます。

自分でビルドしたカーネルを動かす(ソース改変あり)

何の変更もしていないものを動かすだけではあまり面白くないので、自分でソースを改変したカーネルを動かしてみましょう。ここでは例として用意したパッチをlinux v4.9に適用したカーネルを作ってみます。このパッチは、起動時に簡単なメッセージをカーネルのログに出力するだけのものです。

まずパッチの中身を見てみましょう。

$ cat dev/kernel-patch/first/0001-Print-a-message-on-boot.patch 
From 93cc6bf35ed2850634cb1bcfe621b38d81c6ab25 Mon Sep 17 00:00:00 2001
From: Satoru Takeuchi <satoru.takeuchi@gmail.com>
Date: Wed, 14 Dec 2016 20:42:17 +0900
Subject: [PATCH] Print a message on boot

Signed-off-by: Satoru Takeuchi <satoru.takeuchi@gmail.com>
---
 init/main.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/init/main.c b/init/main.c
index 2858be7..9736dac 100644
--- a/init/main.c
+++ b/init/main.c
@@ -657,6 +657,7 @@ asmlinkage __visible void __init start_kernel(void)
        }

        ftrace_init();
+       printk("my patch is applied!\n");

        /* Do the rest non-__init'ed, we're now alive */
        rest_init();
--
2.10.2

カーネルを作って起動しましょう。

$ cd linux
$ git checkout -b test v4.9
Switched to a new branch 'test'
$ git am ../dev/kernel-patch/first/0001-Print-a-message-on-boot.patch 
Applying: Print a message on boot
$ cd ../ktest
$ ./ktest.pl
...
*******************************************
*******************************************
KTEST RESULT: TEST 1 SUCCESS!!!!         **
*******************************************
*******************************************

    1 of 1 tests were successful

成功したようです。では本当に成功したか確かめてみましょう。

$ cd ../elkdt
$ vagrant ssh
...
vagrant@packer-qemu:~$ uname -r
4.9.0-ktest+
vagrant@packer-qemu:~$ 

ちゃんと自前カーネルが動いているようです。カーネルバージョンの"-ktest"の後ろの"+"は、今回のように、公式カーネル以外に独自の修正をした場合に追加されるものです。

では、カーネルのログに期待したメッセージは出ているか、確認してみましょう。

vagrant@packer-qemu:~$ dmesg | grep "my patch"
[    0.167288] my patch is applied!
$ 

カーネル起動時に "my patch is applied!" というメッセージが無事出力されていました。

このパッチを起点に、メッセージを変更したり、メッセージを挿入する場所を変えたり(そして挿入する場所を間違えてブートしなくなったり…)、別の関数を使ってみたり、と、いろんなことに挑戦してみてください。どうせ環境を復旧するのは簡単です。

この後、またカーネルをもとに戻しておいて下さい。

自作カーネルモジュールの作成

カーネルモジュールの作成も簡単です。ここでは、読み出した際に"Hello world!" とカーネルログに出力するだけの簡単なモジュールを作ってみます。

ソースを見てみましょう。

$ cat dev/module/hello/hello.c
#include <linux/module.h>

MODULE_LICENSE("GPL2");
MODULE_AUTHOR("Satoru Takeuchi <satoru.takeuchi@gmail.com>");
MODULE_DESCRIPTION("Hello world kernel module");

static int hello_init(void) {
        printk(KERN_ALERT "Hello world!\n");
        return 0;
}

static void hello_exit(void) {
        printk(KERN_ALERT "driver unloaded\n");
}

module_init(hello_init);
module_exit(hello_exit);

ビルドするには、VM上で自作カーネルがブートしている状態で次のコマンドを実行します。

$ cd dev/module/hello
$ make
...
$ 

できたモジュールをVM上にコピーします。

$ cp hello.ko ../../../elkdt
$ cd ../../../elkdt
$ vagrant rsync
...
$ 

モジュールをロードします。

$ vagrant ssh
...
vagrant@packer-qemu:~$ sudo su
root@packer-qemu:/home/vagrant# insmod /vagrant/hello.ko
root@packer-qemu:/home/vagrant# 

ロードは成功したようです。ロード時にメッセージが出力されたか確認します。

root@packer-qemu:/home/vagrant# dmesg | tail -3
[  314.198886] random: crng init done
[  516.935519] hello: loading out-of-tree module taints kernel.
[  516.936950] Hello world!
root@packer-qemu:/home/vagrant# 

ちゃんとメッセージが出たようです。

カーネルのビルドには時間がかかるので、手っ取り早くカーネル開発を体験してみたいという場合は、この簡単なモジュールをもとに少しづつ拡張して自分のモジュールを作ってみるのがよいと思います。

カーネルの設定を変更

カーネルの設定を変更するには(たとえばデフォルトではビルドされないファイルシステムを有効にするなど)、./ktest.plを実行する前に次のコマンドを実行してください。

$ cp ktest/minconfig{,.bak}
$ cp ktest/minconfig linux/.config
$ cd linux
$ make menuconfig
...                                      # ここで設定を好きに変更
$ mv .config ../ktest/minconfig
$ make mrproper
$ cd ../

変更した設定を使ってビルドしたカーネルがブートしない場合は、ktest/minconfig{.bak,}によって設定ファイルを正しく動作するものに復元してください。カーネルビルドに慣れていない人がカーネルの設定を変更する際のコツは、一気にたくさん変更しないことです。

カーネルの設定についての詳細は後述の参考資料「linux kernelのmakeターゲットについてのあれこれ」をごらんください。

さらに高度なことをする

本ツール(のバックエンドとして使っているktestというツール)は、ただ自作カーネルをブートするだけでなく、以下のようなこともすべて自動でできます。

  • ユーザ独自のテストを流す
  • パッチセット内のそれぞれのパッチが動作するかをすべてテストする
  • バグが入り込んだコミットをbisectして見つける

詳細については後述の参考資料「linux kernel auto test by using ktest」の、それぞれ"User defined test", "Test patchset", および "Bisect"の節を参照してください。

おわりに

前述の通り、カーネル開発/テストは通常のソフトウェアのそれよりも難しいです。しかし、その難しさのうち、本質的ではないものについては本ツールによって取り除いたつもりです。この記事、このツールをきっかけに、カーネルに親しみを持ってくれるかたが増えることを願っています。

Happy hacking!

参考資料

linux kernel auto test by using ktest ... ktestのチュートリアル
linux kernelのmakeターゲットについてのあれこれ

この投稿は Linux Advent Calendar 201614日目の記事です。
Comments Loading...