以前Linuxのカーネルスレッドがループして暴走したときに、カーネルスレッドの扱いを調べていた時期があって、それの簡単な動きを再現するべくカーネルモジュールを作りました。まずは、自力で試したい人は以下を見ずに試すと良いでしょう。
まずはカーネルスレッドをループさせる
カーネルスレッドは、所謂ps上で[kthread_dayo]
みたいな感じで見えるプロセスの事です。厳密にはユーザランドのプロセスも定期的にカーネル側の処理が走るときは[]
付けになったりするのですが、簡単には上記のような状態とします。これをユーザランドのプログラミングで作ることは難しいのですが、カーネルモジュールを使えばすごく簡単につくれます。
まずは以下のようにカーネルスレッドをループさせるコードを書きます。
#include <linux/module.h> #include <linux/sched.h> #include <linux/kthread.h> MODULE_AUTHOR("matsumotory"); MODULE_DESCRIPTION("run kthread"); MODULE_LICENSE("GPL"); struct task_struct *k; static int kthread_cb(void *arg) { printk("[%s] running as kthread\n", k->comm); while(!kthread_should_stop()) { schedule(); } return 0; } static int __init run_kthread_init(void) { k = kthread_create(kthread_cb, NULL, "matsumotory"); printk(KERN_INFO "[%s] wake up as kthread\n", k->comm); wake_up_process(k); return 0; } static void __exit run_kthread_exit(void) { printk(KERN_INFO "[%s] stop kthread\n", k->comm); kthread_stop(k); } module_init(run_kthread_init); module_exit(run_kthread_exit); API Training Shop Blog About
そして、これを動作しているkernelとそれに該当するkernelのコードを持ってきた上で以下のようなMakefile
を作ります。
obj-m := run_kthread.o # ここにカーネルソースがある ROOTDIR := /home/matsumotory/linux-4.2.3/ PWD := $(shell pwd) default: $(MAKE) -C $(ROOTDIR) M=$(PWD) modules clean: rm -f *.o *.ko
これで、以下のようにビルドしてからできたkernelモジュールを読み込みましょう。
make insmod run_kthread.ko lsmod | grep run_kthread
すると、以下のようにループするkernelスレッドが動きますわーい。
$ ps auwx | grep "\[matsumotory\]" root 21747 102 0.0 0 0 ? R 13:33 0:13 [matsumotory]
また、dmesgには以下のようなログが吐かれます。
[ 1139.247589] [matsumotory] wake up as kthread [ 1139.247603] [matsumotory] running as kthread
カーネルスレッドを殺す
このループしたカーネルスレッドをユーザランド側から殺そうとしても死にません。なんせカーネルスレッドだし実装的にもシグナルとかガン無視していますし、あれこれ殺せない!!となるかもしれません。
でも安心して下さい。次はこのカーネルスレッドをカーネル側から殺すカーネルモジュールを作ってみましょう。まずは以下のようにカーネルモジュールのコードを書きます。
#include <linux/module.h> #include <linux/debugfs.h> #include <linux/sched.h> #include <linux/pid.h> #include <linux/kthread.h> #include <asm-generic/uaccess.h> MODULE_AUTHOR("matsumotory"); MODULE_DESCRIPTION("kill kthread"); MODULE_LICENSE("GPL"); static int file_value; static struct dentry *dentry_file; struct task_struct *k; static ssize_t kill_kthread_by_pid(struct file *file, const char __user *ubuf, size_t len, loff_t *ppos) { pid_t pid; char kbuf[64]; struct pid *pid_s; if (len > sizeof(kbuf) -1) { printk(KERN_INFO "kill_kthread_by_pid: invalid pid\n"); return -EINVAL; } if (copy_from_user(kbuf, ubuf, len)) { printk(KERN_INFO "kill_kthread_by_pid: copy from user land to kernel failed\n"); return -EFAULT; } kbuf[len] = '\0'; if (!(pid = simple_strtoul(kbuf, NULL, 10))) { printk(KERN_INFO "kill_kthread_by_pid: convert pid failed, invalid pid\n"); return -EINVAL; } if (!(pid_s = find_get_pid(pid))) { printk(KERN_INFO "kill_kthread_by_pid: not found pid struct\n"); return -ESRCH; } if (!(k = get_pid_task(pid_s, PIDTYPE_PID))) { printk(KERN_INFO "kill_kthread_by_pid: not found task struct\n"); put_pid(pid_s); return -ESRCH; } printk(KERN_INFO "[%s=%d] kill kthread by kthread_stop\n", k->comm, pid); kthread_stop(k); return len; } static struct file_operations fops = { .write = kill_kthread_by_pid, }; static int __init kill_kthread_module_init(void) { dentry_file = debugfs_create_file("kill_kthread", 0644, NULL, &file_value, &fops); if (!dentry_file) { printk(KERN_ERR "kill_kthread_module_init failed"); return -ENODEV; } return 0; } static void __exit kill_kthread_module_exit(void) { debugfs_remove(dentry_file); } module_init(kill_kthread_module_init); module_exit(kill_kthread_module_exit);
そして、以下の様なMakefile
を作ります。
obj-m := kill_kthread.o ROOTDIR := /home/matsumotory/linux-4.2.3/ PWD := $(shell pwd) default: $(MAKE) -C $(ROOTDIR) M=$(PWD) modules clean: rm -f *.o *.ko
そしてカーネルモジュールをビルドしてロードしましょう。
make insmoda kill_kthread.ko lsmod | grep kill_kthread
そして、このカーネルモジュールの使い方は、カーネルスレッドのPIDを指定してそれをdebugfs上のファイルに渡す事で内部的にカーネルを停止させるフラグをたてるような実装にしています。
なので、debugfsをマウントしていない方は以下のようにマウントして、
mount -t debugfs nodev /sys/kernel/debug/
上記でループさせているカーネルスレッドのPIDを取得してから以下のようにファイルに書き込みましょう。
echo $pid > /sys/kernel/debug/kill_kthread
するとわーい、ループしていたカーネルスレッドが消えてなくなったと思います。やった、カーネルスレッドをコントロールした気分になったぞ!!
コードとしてはようするに、ループさせるコードの中で以下のようにカーネルスレッドがループする条件を以下のように実装しています。
while(!kthread_should_stop()) {
schedule();
}
そして、停止させるカーネルモジュールのコードは渡されたPIDから対応するカーネルスレッドを特定し、
kthread_stop(k);
によってkthread_should_stop
でチェックされるフラグをカーネル側から立てます。これによって、ループの条件が偽となりループが止まるわけです。実際にカーネルのコードを読んでもらうと、kthread_should_stop
とkthread_stop
がPIDに紐づく構造体から辿れるフラグを操作している事がわかるでしょう。
ちなみに、どちらのカーネルモジュールも以下のコマンドでリムーブすることももちろん可能です。
rmmod run_kthread lsmod | grep run_kthread rmmod kill_kthread lsmod | grep kill_kthread
dmesgでカーネルスレッドがストップされた旨が出力されます。
[ 1148.646109] [matsumotory] stop kthread
ps上もいませんね。
$ ps auwx | grep "\[matsumotory\]"
まとめ
ということで、カーネルスレッドってなんかあったなぁという所から、それを実際にカーネルモジュールで操作してみると意外と簡単にできることがわかりますね。
こうやって最初は簡単な実装からはじめて、徐々に深い所へとコード量を増やしながら理解を深めていくと、カーネルと親しみを持ちつつ勉強できるかもしれません。