- 1 はじめに
- 2 検証環境
- 3 Makefile
- 4 Hellow World
- 5 カーネルモジュールに引数を渡す方法
- 6 割り込み
- 7 スラブオブジェクト
- 8 カーネルスレッド
- 9 スピンロック
- 10 タイマ
- 11 sysfs
- 12 デバイスの登録、削除
- Z 参考情報
1 はじめに
様々な種類のカーネルモジュールを作って、その実行結果を確認してみます。なお、サンプルプログラムを見やすくするため、エラー処理は意図的に省略しています。
2 検証環境
CentOS版数は以下のとおりです。
[root@server ~]# cat /etc/redhat-release CentOS Linux release 7.9.2009 (Core)
カーネル版数は以下のとおりです。
[root@server ~]# uname -r 3.10.0-1160.el7.x86_64
3 Makefile
全てのサンプルプログラム共通のMakefileです。なお、$(MAKE)とrmから行頭までの間は、空白ではなくTABです。
[root@server modules]# cat Makefile obj-m := test.o KDIR := /lib/modules/$(shell uname -r)/build VERBOSE = 0 all: $(MAKE) -C $(KDIR) M=$(PWD) KBUILD_VERBOSE=$(VERBOSE) CONFIG_DEBUG_INFO=y modules clean: rm -f *.o *.ko *.mod.c Module.symvers modules.order
4 Hellow World
Hellow Worldをログに出力するサンプルプログラムを作成します。
[root@server modules]# cat test.c #include <linux/module.h> static int __init test_init(void) { printk(KERN_INFO "Hello World\n"); return 0; } static void __exit test_exit(void) { printk(KERN_INFO "Bye!\n"); } module_init(test_init); module_exit(test_exit); MODULE_AUTHOR("hana_shin"); MODULE_LICENSE("GPL v2");
makeコマンドを実行します。
[root@server modules]# make make -C /lib/modules/3.10.0-1160.el7.x86_64/build M=/root/modules KBUILD_VERBOSE=0 CONFIG_DEBUG_INFO=y modules make[1]: ディレクトリ `/usr/src/kernels/3.10.0-1160.el7.x86_64' に入ります CC [M] /root/modules/test.o Building modules, stage 2. MODPOST 1 modules CC /root/modules/test.mod.o LD [M] /root/modules/test.ko make[1]: ディレクトリ `/usr/src/kernels/3.10.0-1160.el7.x86_64' から出ます
makeコマンドを実行すると、以下のファイルが作成されます。
[root@server modules]# ls -l 合計 220 -rw-r--r--. 1 root root 225 2月 16 17:00 Makefile -rw-r--r--. 1 root root 0 2月 16 17:00 Module.symvers -rw-r--r--. 1 root root 29 2月 16 17:00 modules.order -rw-r--r--. 1 root root 283 2月 16 16:55 test.c -rw-r--r--. 1 root root 101864 2月 16 17:00 test.ko -rw-r--r--. 1 root root 856 2月 16 17:00 test.mod.c -rw-r--r--. 1 root root 58792 2月 16 17:00 test.mod.o -rw-r--r--. 1 root root 44616 2月 16 17:00 test.o
tailコマンドを実行して、カーネルモジュールが出力するログを確認します。
[root@server ~]# tail -f /var/log/messages
作成したカーネルモジュールをロードしてみます。
[root@server modules]# insmod test.ko
lsmodコマンドを実行します。作成したカーネルモジュールがロードされていることがわかります。
[root@server modules]# lsmod |grep test test 12424 0
ログを確認すると、 Hello Worldが出力されていることがわかります。
[root@server ~]# tail -f /var/log/messages Feb 16 17:05:38 server kernel: Hello World
テストが終了したので、カーネルモジュールをアンロードします。
[root@server modules]# rmmod test.ko
lsmodコマンドを実行します。作成したカーネルモジュールがアンロードできたことがわかります。
[root@server modules]# lsmod |grep test [root@server modules]#
5 カーネルモジュールに引数を渡す方法
カーネルモジュールに引数を渡すことができます。ここでは、数値、文字列を引数にとるサンプルプログラムを作成します。
5.1 module_paramの書式
カーネルモジュールの引数は以下のように指定します。
- 第1引数:変数名
- 第2引数:型名(int,long,short,charp等がある。詳細はmanを参照)
- 第3引数:読み込み専用属性
5.2 数値を渡す方法
サンプルプログラムは以下のとおりです。
[root@server modules]# cat test.c #include <linux/module.h> static int cnt = 1; module_param(cnt, int, S_IRUGO); static int __init test_init(void) { printk(KERN_INFO "Hello World,cnt=%d\n",cnt); return 0; } static void __exit test_exit(void) { printk(KERN_INFO "Bye!\n"); } module_init(test_init); module_exit(test_exit); MODULE_AUTHOR("hana_shin"); MODULE_LICENSE("GPL v2");
作成したカーネルモジュールをロードしてみます。
[root@server modules]# insmod test.ko cnt=5
tailコマンドを実行して、カーネルモジュールが出力するログを確認します。カーネルモジュールのロード時の引数(5)がログに出力されていることがわかります。
[root@server ~]# tail -f /var/log/messages Feb 16 17:22:08 server kernel: Hello World,cnt=5
テストが終了したので、カーネルモジュールをアンロードします。
[root@server modules]# rmmod test.ko
5.3 文字列を渡す方法
ここではカーネルモジュールへの引数として、文字列を2つ渡してみます。
サンプルプログラムは以下のとおりです。
[root@server modules]# cat test.c #include <linux/module.h> static char *str1, *str2; module_param(str1, charp, S_IRUGO); module_param(str2, charp, S_IRUGO); static int __init test_init(void) { printk(KERN_INFO "Hello World, Input String=%s:%s\n", str1, str2); return 0; } static void __exit test_exit(void) { printk(KERN_INFO "Bye!\n"); } module_init(test_init); module_exit(test_exit); MODULE_AUTHOR("hana_shin"); MODULE_LICENSE("GPL v2");
作成したカーネルモジュールをロードします。このとき、モジュールへの引数として、文字列を2つ(aaとbb)渡してみます。
[root@server modules]# insmod test.ko str1=aa str2=bb
tailコマンドを実行して、カーネルモジュールが出力するログを確認します。カーネルモジュールのロード時に指定した文字列がログに出力されていることがわかります。
[root@server ~]# tail -f /var/log/messages Feb 16 17:29:49 server kernel: Hello World, Input String=aa:bb
テストが終了したので、カーネルモジュールをアンロードします。
[root@server modules]# rmmod test.ko
6 割り込み
割り込み(IRQ=17)が発生すると、割り込みが発生した回数をログに出力するサンプルプログラムを作成してみます。
[root@server modules]# cat test.c #include <linux/module.h> #include <linux/interrupt.h> #define TEST_IRQ 17 static int irq_counter=0; static int test_dev_id; static irqreturn_t test_interrupt(int irq, void *dev_id) { printk(KERN_INFO "Interrupt Counter=%d\n",++irq_counter); return IRQ_HANDLED; } static int __init test_init(void) { if(request_irq(TEST_IRQ, test_interrupt, IRQF_SHARED, "test_interrupt", &test_dev_id)) return -1; printk(KERN_INFO "Loading Interrupt Handler\n"); return 0; } static void __exit test_exit(void) { synchronize_irq(TEST_IRQ); free_irq(TEST_IRQ, &test_dev_id); printk(KERN_INFO "Unloading Interrupt handler\n"); } module_init(test_init); module_exit(test_exit); MODULE_AUTHOR("hana_shin"); MODULE_LICENSE("GPL v2");
/proc/interruptsを確認すると、CPU3に対してIRQ17が12812回発生していることがわかります。
[root@server ~]# cat /proc/interrupts |grep "17:" CPU0 CPU1 CPU2 CPU3 17: 10149 0 0 12812 IO-APIC-fasteoi ehci_hcd:usb1, ioc0
作成したカーネルモジュールをロードします。
[root@server modules]# insmod test.ko
IRQ17の割り込みが2回発生したことが分かります。
[root@server ~]# tail -f /var/log/messages Feb 16 18:46:42 server kernel: Interrupt Counter=1 Feb 16 18:46:42 server kernel: Interrupt Counter=2
/proc/interruptsを確認すると、CPU3への割り込み回数が2回増えていることがわかります。
[root@server ~]# cat /proc/interrupts |grep "17:" CPU0 CPU1 CPU2 CPU3 17: 10149 0 0 12814 IO-APIC-fasteoi ehci_hcd:usb1, ioc0, test_interrupt
テストが終了したので、カーネルモジュールをアンロードします。
[root@server modules]# rmmod test.ko
7 スラブオブジェクト
スラブとは特定の大きさのメモリが集まった領域のことです。たとえば、128バイトのスラブとは、128バイトの大きさのメモリが集まった領域のことです。スラブの利用者は、サイズを指定して、スラブの中からメモリを獲得します。スラブの中の個々のメモリ領域はスラブオブジェクトと呼びます。
128byte slab 64byte slab +-----------+ +-----------+ | 128(byte) | <-- slabオブジェクト | 64(byte) | <-- slabオブジェクト +-----------+ +-----------+ | : | | : | +-----------+ +-----------+ | 128(byte) | <-- slabオブジェクト | 64(byte) | <-- slabオブジェクト +-----------+ +-----------+
128バイトのスラブオブジェクトを獲得するサンプルプログラムを作成します。
[root@server modules]# cat test.c #include <linux/module.h> #include <linux/slab.h> #define OBJECT_NUM_MAX 100 #define OBJECT_SIZE 128 static int num = 1; static void *test_ptr[OBJECT_NUM_MAX]; module_param(num, int, S_IRUGO); static int __init test_init(void) { int i; printk(KERN_INFO "Hello!,The number of slab object is %d\n", num); for(i=0; i<num; i++) { test_ptr[i] = kmalloc(OBJECT_SIZE, GFP_ATOMIC | __GFP_ZERO); printk(KERN_INFO "start=0x%p, end=0x%p\n",test_ptr[i], test_ptr[i] + OBJECT_SIZE); } return 0; } static void __exit test_exit(void) { int i; printk(KERN_INFO "Bye!\n"); for(i=0; i<num; i++) { kfree(test_ptr[i]); } } module_init(test_init); module_exit(test_exit); MODULE_AUTHOR("hana_shin"); MODULE_LICENSE("GPL v2");
作成したカーネルモジュールをロードします。128バイトのスラブオブジェクトを3個獲得します。
[root@server modules]# insmod test.ko num=3
128バイトのスラブオブジェクトが3個獲得されていることがわかります。
Feb 16 19:04:24 server kernel: Hello!,The number of slab object is 3 Feb 16 19:04:24 server kernel: start=0xffff974e224cee00, end=0xffff974e224cee80 Feb 16 19:04:24 server kernel: start=0xffff974e224ce080, end=0xffff974e224ce100 Feb 16 19:04:24 server kernel: start=0xffff974e224cf480, end=0xffff974e224cf500
テストが終了したので、カーネルモジュールをアンロードします。
[root@server modules]# rmmod test.ko
8 カーネルスレッド
ユーザ空間のプロセスはシステムコールを実行すると、ユーザモードからカーネルモードに移行します。そして、システムコールの実行が完了すると、カーネルモードからユーザモードに復帰します。しかし、カーネルスレッドは、カーネルモードのみで動作するプロセスです。
ここでは、カーネルスレッドを生成するサンプルプログラムを作成します。
[root@server modules]# cat test.c #include <linux/module.h> #include <linux/kthread.h> static char *name; struct task_struct *t; module_param(name, charp, S_IRUGO); static int test_func(void *arg) { printk(KERN_INFO "%s kthread started.PID is %d\n", t->comm, t->pid); while(!kthread_should_stop()) schedule(); return 0; } static int __init test_init(void) { t = kthread_run(test_func, NULL, name); return 0; } static void __exit test_exit(void) { kthread_stop(t); printk(KERN_INFO "%s kthread ended.\n", t->comm); } module_init(test_init); module_exit(test_exit); MODULE_AUTHOR("hana_shin"); MODULE_LICENSE("GPL v2");
作成したカーネルモジュールをロードします。カーネルモジュールをロードすると、test1という名前のカーネルスレッドを生成します。
[root@server modules]# insmod test.ko name=test1
ログを確認すると、PID=2380のカーネルスレッドが生成されたことがわかります。
[root@server ~]# tail -f /var/log/messages Feb 16 19:17:43 server kernel: test1 kthread started.PID is 2380
psコマンドを実行します。PID=2380のプロセスはtest1であることがわかります。
[root@server ~]# ps -C test1 -o comm,pid,ppid,%cpu COMMAND PID PPID %CPU test1 2380 2 100
また、次のようにpsコマンドを実行すると、test1の名前の前後が[ ]で囲まれてます。カーネルスレッドの場合、プロセス名の前後が[ ]で囲まれます。
[root@server ~]# ps aux|grep test1 root 2380 100 0.0 0 0 ? R 19:17 12:43 [test1] root 2562 0.0 0.0 112824 972 pts/1 S+ 19:30 0:00 grep --color=auto test1
テストが終了したので、カーネルモジュールをアンロードします。
[root@server modules]# rmmod test.ko
9 スピンロック
スピンロックとは、スレッドがロックを獲得できるまで単純にビジーループする方式です。ここでは、カーネルモジュールをロードするとき、スピンロックを獲得し、カーネルモジュールをアンロードするときスピンロックを解放するサンプルプログラムを作成します。
[root@server modules]# cat test.c #include <linux/module.h> spinlock_t test_lock; static int __init test_init(void) { spin_lock_init(&test_lock); printk(KERN_INFO "spin lock initialization\n"); spin_lock(&test_lock); printk(KERN_INFO "lock spin lock\n"); return 0; } static void __exit test_exit(void) { spin_unlock(&test_lock); printk(KERN_INFO "unlock spin lock\n"); } module_init(test_init); module_exit(test_exit); MODULE_AUTHOR("hana_shin"); MODULE_LICENSE("GPL v2");
作成したカーネルモジュールをロードします。
[root@server modules]# insmod test.ko
カーネルモジュールをロードすると、ログに”lock spin lock”というメッセージが出力されることがわかります。
[root@server ~]# tail -f /var/log/messages Feb 16 19:56:08 server kernel: lock spin lock
作成したカーネルモジュールをアンロードします。
[root@server modules]# rmmod test.ko
カーネルモジュールをアンロードすると、ログに”unlock spin lock”というメッセージが出力されることがわかります。
[root@server ~]# tail -f /var/log/messages Feb 16 19:56:14 server kernel: unlock spin lock
10 タイマ
定期的にタイマを起動するサンプルプログラムを作成します。
[root@server modules]# cat test.c #include <linux/module.h> static int sec = 1; module_param(sec, int, S_IRUGO); static struct timer_list test_timer; static void test_func(int sec) { printk(KERN_INFO "Timer expired\n"); mod_timer(&test_timer, jiffies + sec * HZ); } static int __init test_init(void) { init_timer(&test_timer); test_timer.data = sec; test_timer.function = (void *)&test_func; test_timer.expires = jiffies + sec * HZ; add_timer(&test_timer); printk(KERN_INFO "Hello. Time interval is %d second\n", sec); return 0; } static void __exit test_exit(void) { printk(KERN_INFO "Bye\n"); del_timer(&test_timer); } module_init(test_init); module_exit(test_exit); MODULE_AUTHOR("hana_shin"); MODULE_LICENSE("GPL v2");
カーネルモジュールをロードします。このとき、カーネルモジュールの引数にタイマの起動間隔(2秒)を指定します。
[root@server modules]# insmod test.ko sec=2
ログを確認すると、2秒おきにタイマが起動されていることがわかります。
[root@server ~]# tail -f /var/log/messages Feb 16 20:01:53 server kernel: Hello. Time interval is 2 second Feb 16 20:01:55 server kernel: Timer expired Feb 16 20:01:57 server kernel: Timer expired Feb 16 20:01:59 server kernel: Timer expired
テストが終了したので、カーネルモジュールをアンロードします。
[root@server modules]# rmmod test.ko
11 sysfs
sysfsとは、ユーザープロセスから、デバイスやドライバーなどに関するカーネル情報にアクセスするためのインターフェイスです。/sys/kernel配下にtest/statディレクトリを作成し、そのディレクトにjiffiesとtimeという名前のファイルを作成するサンプルプログラムを作成します。
[root@server modules]# cat test.c #include <linux/module.h> static struct kobject *test_kobj; static ssize_t test1_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { return sprintf(buf, "%lu\n", get_seconds()); } static ssize_t test2_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { return sprintf(buf, "%lu\n", jiffies); } static struct kobj_attribute test1_attr = __ATTR(time, 0444, test1_show, NULL); static struct kobj_attribute test2_attr = __ATTR(jiffies, 0444, test2_show, NULL); static struct attribute *test_attrs[] = { &test1_attr.attr, &test2_attr.attr, NULL, }; static struct attribute_group test_attr_group = { .name = "stat", .attrs = test_attrs, }; static int test_init(void) { int ret; printk(KERN_INFO "Hello\n"); test_kobj = kobject_create_and_add("test", kernel_kobj); ret = sysfs_create_group(test_kobj, &test_attr_group); return ret; } static void test_exit(void) { kobject_put(test_kobj); printk(KERN_INFO "Bye!\n"); } module_init(test_init); module_exit(test_exit); MODULE_AUTHOR("hana_shin"); MODULE_LICENSE("GPL v2");
作成したカーネルモジュールをロードします。
[root@server modules]# insmod test.ko
ディレクトリを/sys/kernel/test/statに移動します。
[root@server modules]# cd /sys/kernel/test/stat/ [root@server stat]# pwd /sys/kernel/test/stat
/sys/kernel/test/stat配下に作成したファイルを確認します。
[root@server stat]# ls -l 合計 0 -r--r--r--. 1 root root 4096 2月 16 20:29 jiffies -r--r--r--. 1 root root 4096 2月 16 20:29 time
jiffiesファイルを読み出します。jiffiesはカーネル起動時からの時刻です。1ミリ秒単位で+1増加します。
[root@server stat]# cat jiffies 4299535090
timeファイルを読み出します。
[root@server stat]# cat time 1645011290
12 デバイスの登録、削除
/dev配下に"test_device"という名前でのデバイスを作成するサンプルプログラムを作成します。
[root@server modules]# cat test.c #include <linux/module.h> #include <linux/fs.h> #include <linux/miscdevice.h> static struct file_operations test_dev_fops = { .owner = THIS_MODULE, }; static struct miscdevice test_dev = { .name = "test_device", .minor = MISC_DYNAMIC_MINOR, .fops = &test_dev_fops, }; static __init int test_init(void) { printk(KERN_INFO "Device registered!\n"); misc_register(&test_dev); return 0; } static __exit void test_exit(void) { printk(KERN_INFO "Device unegistered!\n"); misc_deregister(&test_dev); } module_init(test_init); module_exit(test_exit); MODULE_AUTHOR("hana_shin"); MODULE_LICENSE("GPL v2");
カーネルモジュールをロードします。
[root@server modules]# insmod test.ko
カーネルモジュールをロードすると、/dev配下に"test_device"という名前のデバイスが作成されます。
[root@server modules]# ls -l /dev/test_device crw-------. 1 root root 10, 55 2月 16 20:42 /dev/test_device
カーネルモジュールをアンロードします。
[root@server modules]# rmmod test.ko
カーネルモジュールをアンロードすると、/dev配下に"test_device"という名前のデバイスが削除されたことがわかります。
[root@server modules]# ls -l /dev/test_device ls: /dev/test_device にアクセスできません: そのようなファイルやディレクトリはありません
Z 参考情報
私が業務や記事執筆で参考にした書籍を以下のページに記載します。
Linux技術のスキルアップをしよう! - hana_shinのLinux技術ブログ