カーネルモジュール

January 07, 2020

カーネルモジュールとは

Linux カーネルにはモジュール機能が実装されており、デバイスドライバやファイルシステム等、独立したカーネル機能をカーネルモジュールとして作成し、必要に応じて動的にカーネル空間に組み込むまたは削除することが可能になっている。
カーネルモジュールは Linux カーネル空間に動的に組み込むことが可能なバイナリファイルとして作成され、拡張子は .ko となる。
カーネルソースファイル中に CONFIG_*=m とされた項目に対応するドライバはカーネルモジュールとして作成される。作成されたモジュールは通常 /lib/modules/kernel_version/kernel 配下にインストールされる。

$ ls -l /lib/modules/4.14.138-89.102.amzn1.x86_64/kernel/  
total 40  
drwxr-xr-x  3 root root 4096 Aug 26 18:05 arch  
drwxr-xr-x  2 root root 4096 Aug 26 18:05 block  
drwxr-xr-x  3 root root 4096 Aug 26 18:05 crypto  
drwxr-xr-x 44 root root 4096 Aug 26 18:05 drivers  
drwxr-xr-x 38 root root 4096 Aug 26 18:05 fs  
drwxr-xr-x  7 root root 4096 Aug 26 18:05 lib  
drwxr-xr-x  2 root root 4096 Aug 26 18:05 mm  
drwxr-xr-x 30 root root 4096 Aug 26 18:05 net  
drwxr-xr-x  3 root root 4096 Aug 26 18:05 security  
drwxr-xr-x  3 root root 4096 Aug 26 18:05 virt  

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

カーネルのソースツリーに含まれない、独自のカーネルモジュールを作成することも可能となる。
以下のようなコードを用意する。

mymod.c
#include <linux/module.h>  
#include <linux/timer.h>  
#include <linux/errno.h>  
  
static int sec = 5;  
module_param(sec, int, S_IRUGO|S_IWUSR);  
MODULE_PARM_DESC(sec, "Set the interval.");  
  
static void mymod_timer(unsigned long data);  
  
static DEFINE_TIMER(timer, mymod_timer, 0, 0);  
  
static void mymod_timer(unsigned long data) {  
  printk(KERN_INFO "mymod: timer\n");  
  mod_timer(&amp;timer, jiffies + sec * HZ);  
}  
  
static int mymod_init(void) {  
  printk(KERN_INFO "mymod: init\n");  
  
  if (sec <= 0) {  
    printk(KERN_INFO "Invalid interval sec=%d\n", sec);  
    return -EINVAL;  
  }  
  
  mod_timer(&amp;timer, jiffies + sec * HZ);  
  
  return 0;  
}  
  
static void mymod_exit(void) {  
  del_timer(&amp;timer);  
  printk(KERN_INFO "mymod: exit\n");  
}  
  
module_init(mymod_init);  
module_exit(mymod_exit);  
  
MODULE_AUTHOR("Yuki Yamaguchi");  
MODULE_LICENSE("GPL");  
MODULE_DESCRIPTION("My first module");  

上記コードのポイントは以下の点となる。

  • module_param でこのモジュールのパラメータ sec を設定する
  • module_init() 及び module_exit() により、このモジュールの初期化処理 (ロードされた際の処理) として mymod_init() 及び終了処理 (アンロードされた際の処理) として mymod_exit() を定義する
  • DEFINE_TIMER で mymod_timer() をこのモジュールのタイマーとして設定する
  • mymod_init() では mod_timer() によりタイマーを登録しており、sec 秒後にタイマー関数が実行される
  • sec 秒後に実行されたタイマー関数 mymod_timer() は再び sec 秒後にタイマーを設定するため、sec 秒ごとにタイマーが呼ばれる形となる

あとは以下のような Makefile を作って置く必要があります。

Makefile
obj-m := mymod.o  

あとは make するだけなんですが、ここで /lib/modules/`uname -r`/build のシンボリックリンクが生きてるか確認する必要がある。これが無いと実際に make した際に No such file or directory と言われて終了する。
/lib/modules/`uname -r`/build は対応したカーネルバージョンの kernel-header を指すことが期待されるため、もしシンボリックリンクが切れてるようなら以下のように対応したバージョンの kernel-header がインストールされているかを確認し、必要に応じてインストールする。

$ rpm -qa kernel* | grep `uname -r`  
kernel-tools-4.14.154-99.181.amzn1.x86_64  
kernel-4.14.154-99.181.amzn1.x86_64  
kernel-headers-4.14.154-99.181.amzn1.x86_64  
kernel-devel-4.14.154-99.181.amzn1.x86_64  
kernel-tools-devel-4.14.154-99.181.amzn1.x86_64  

あとは実際に make する。

$ make -C /lib/modules/`uname -r`/build M=`pwd`  
make: Entering directory `/usr/src/kernels/4.14.154-99.181.amzn1.x86_64'  
  AR      /home/ec2-user/mymod/built-in.o  
  CC [M]  /home/ec2-user/mymod/mymod.o  
  Building modules, stage 2.  
  MODPOST 1 modules  
  CC      /home/ec2-user/mymod/mymod.mod.o  
  LD [M]  /home/ec2-user/mymod/mymod.ko  
make: Leaving directory `/usr/src/kernels/4.14.154-99.181.amzn1.x86_64'  

実際に modinfo すると情報が取得できる。

$ modinfo mymod.ko  
filename:       /home/ec2-user/mymod/mymod.ko  
description:    My first module  
license:        GPL  
author:         Yuki Yamaguchi  
srcversion:     BC427786F9AA9F933ADC0A7  
depends:          
retpoline:      Y  
name:           mymod  
vermagic:       4.14.154-99.181.amzn1.x86_64 SMP mod_unload modversions   
parm:           sec:Set the interval. (int)  

自作カーネルモジュールの組み込み

実際に作成したモジュールを組み込んでみる。

$ sudo insmod mymod.ko  
$ dmesg | tail  
[  315.071059] mymod: loading out-of-tree module taints kernel.  
[  315.073950] mymod: init  
[  320.216068] mymod: timer  
[  325.336002] mymod: timer  
[  330.455939] mymod: timer  
[  335.575876] mymod: timer  
[  340.695812] mymod: timer  
[  345.815741] mymod: timer  
[  350.935680] mymod: timer  
[  356.055616] mymod: timer  

上記より実際にモジュールがロードされ、タイマーが 5 秒置きに実行されていることがわかる。
なお、sec パラメータは以下のように確認が可能となる。

$ cat /sys/module/mymod/parameters/sec   
5  

モジュールパラメータは insmod 時に指定が可能。

$ sudo insmod mymod.ko sec=10  
$ dmesg | tail  
[  470.202486] mymod: init  
[  480.214047] mymod: timer  
[  490.453917] mymod: timer  
[  500.693786] mymod: timer  
[  510.933656] mymod: timer  
[  521.173531] mymod: timer  
[  531.413402] mymod: timer  
[  541.653271] mymod: timer  
[  551.893143] mymod: timer  
[  562.133016] mymod: timer  

今度は 10 秒置きに実行されていることが確認できる。
なお、モジュールの削除は rmmod で可能。

$ sudo rmmod mymod.ko  
$ dmesg | tail  
[  510.933656] mymod: timer  
[  521.173531] mymod: timer  
[  531.413402] mymod: timer  
[  541.653271] mymod: timer  
[  551.893143] mymod: timer  
[  562.133016] mymod: timer  
[  572.372883] mymod: timer  
[  582.612756] mymod: timer  
[  592.852625] mymod: timer  
[  600.856307] mymod: exit  

 © 2023, Dealing with Ambiguity