Help us understand the problem. What is going on with this article?

カーネルモジュールの作り方

1 はじめに

カーネルの様々な機能をカーネルモジュールを使って実行してみます。
なお、ソースコードのエラー処理は、見やすくするため、省略しています。

2 環境

VMware Workstation 14 Playerで仮想マシンを作成しました。
仮想マシンのOS版数は以下のとおりです。

[root@server ~]# cat /etc/redhat-release
CentOS Linux release 7.4.1708 (Core)

[root@server ~]# uname -r
3.10.0-693.el7.x86_64

3 基本

ソースファイル
[root@server modules]# cat test.c
#include <linux/module.h>

static int __init test_init(void)
{
  printk(KERN_INFO "Hello!\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");

Makefileを作成します。
なお、$(MAKE)rmから行頭までの間は、空白ではなくTABです。

Makefile
[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

makeコマンドを実行します。

コンパイル
[root@server modules]# make
make -C /lib/modules/3.10.0-693.el7.x86_64/build M=/root/modules KBUILD_VERBOSE=0 modules
make[1]: ディレクトリ `/usr/src/kernels/3.10.0-693.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-693.el7.x86_64' から出ます

makeコマンド実行したあと、ファイルを確認してみます。

ファイルの確認
[root@server modules]# ls
Makefile  Module.symvers  modules.order  test.c  test.ko  test.mod.c  test.mod.o  test.o

insmodを実行して、モジュールをロードします。

モジュールのロード
[root@server modules]# insmod test.ko
[root@server modules]# lsmod |grep test
test                   12424  0

rmmodを実行して、モジュールをアンロードします。

モジュールのアンロード
[root@server modules]# rmmod test
[root@server modules]# lsmod |grep test
[root@server modules]#

insmod,rmmod実行時、下記ログが出力されます。

実行結果の確認
[root@server modules]# journalctl -f
May  7 20:58:41 server kernel: Hello!
May  7 21:00:17 server kernel: Bye!

4 モジュールにパラメータを渡す方法

4.1 module_paramの書式

第1引数:変数名
第2引数:型名(int,long,short,charp等がある。詳細はmanを参照)
第3引数:読み込み専用属性

4.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!,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
ログの確認
[root@server modules]# journalctl -f
May  7 21:29:53 server kernel: Hello!,cnt=5

4.3 文字列へのポインタを渡す方法

ソース
[root@server modules]# cat test.c
#include <linux/module.h>

static char *name;
module_param(name, charp, S_IRUGO);

static int __init test_init(void)
{
  printk(KERN_INFO "Hello!,name=%s\n", name);
  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");

"test1","test2"を指定してモジュールをロードします。

実行例
[root@server modules]# insmod test.ko name=test1
[root@server modules]# rmmod test.ko
[root@server modules]# insmod test.ko name=test2
[root@server modules]# rmmod test.ko
ログの確認
[root@server modules]# journalctl -f
12月 13 20:41:31 server kernel: Hello!,name=test1
12月 13 20:41:37 server kernel: Bye!
12月 13 20:41:38 server kernel: Hello!,name=test2
12月 13 20:41:40 server kernel: Bye!

5 割り込み

既存の割り込み以外を使った場合、割り込みをあげる方法が思いつかなかったので、
既存の割り込み(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");
モジュールのロード
[root@server modules]# insmod test.ko
[root@server modules]# cat /proc/interrupts |grep "17:"
            CPU0       CPU1       CPU2       CPU3 ★ここの行はあとで追記。
  17:       3495          0          0       2887   IO-APIC-fasteoi   ehci_hcd:usb1, ioc0, test_interrupt

IRQ=17の割り込みがCPU3に8回(2895-2887)発生していることがわかります。

モジュールのアンロード
[root@server modules]# rmmod test
[root@server modules]# cat /proc/interrupts |grep "17:"
            CPU0       CPU1       CPU2       CPU3 ★ここの行はあとで追記。
  17:       3495          0          0       2895   IO-APIC-fasteoi   ehci_hcd:usb1, ioc0

ログの結果より、割り込みが6回発生していることがわかります。
/proc/interruptsの数とあいませんね。。。

ログの確認
[root@server modules]# journalctl -f
12月 13 21:15:41 server kernel: Loading Interrupt Handler
12月 13 21:15:49 server kernel: Interrupt Counter=1
12月 13 21:16:03 server kernel: Interrupt Counter=2
12月 13 21:16:03 server kernel: Interrupt Counter=3
12月 13 21:16:14 server kernel: Interrupt Counter=4
12月 13 21:16:19 server kernel: Interrupt Counter=5
12月 13 21:16:19 server kernel: Interrupt Counter=6
12月 13 21:16:19 server kernel: Unloading Interrupt handler

6 スラブオブジェクト

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");

スラブオブジェクトを3個獲得してみます。

モジュールのロード
[root@server modules]# insmod test.ko num=3

128バイトのスラブオブジェクトが3個獲得されていることがわかります。

ログの確認
[root@server modules]# journalctl -f
12月 13 21:34:05 server kernel: Hello!,The number of slab object is 3
12月 13 21:34:05 server kernel: start=0xffff8800b38a6280, end=0xffff8800b38a6300
12月 13 21:34:05 server kernel: start=0xffff8800b38a6200, end=0xffff8800b38a6280
12月 13 21:34:05 server kernel: start=0xffff8800b38a6480, end=0xffff8800b38a6500

7 カーネルスレッド

ソース
[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");
モジュールのロード
[root@server modules]# insmod test.ko name=test1
[root@server modules]# ps -C test1 -o comm,pid,ppid,%cpu
COMMAND            PID   PPID %CPU
test1             4423      2 98.1
ログの確認
[root@server ~]# journalctl -f
12月 13 21:08:18 server kernel: test1 kthread started.PID is 4423

8 スピンロック

ソース
[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
ログの確認
[root@server modules]# journalctl -f
12月 13 22:26:41 server kernel: spin lock initialization
12月 13 22:26:41 server kernel: lock spin lock
モジュールのアンロード
[root@server modules]# rmmod test.ko
ログの確認
[root@server modules]# journalctl -f
12月 13 22:27:12 server kernel: unlock spin lock

9 タイマ

ソース
[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
ログの確認
[root@server modules]# journalctl -f
12月 14 19:45:02 server kernel: Hello. Time interval is 2 second
12月 14 19:45:04 server kernel: Timer expired
12月 14 19:45:06 server kernel: Timer expired
12月 14 19:45:08 server kernel: Timer expired
12月 14 19:45:10 server kernel: Timer expired
12月 14 19:45:12 server kernel: Timer expired
12月 14 19:45:14 server kernel: Timer expired
12月 14 19:45:14 server kernel: Bye

10 sysfs

ソース
[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ディレクトリが作成されます。
そして、そのディレクトにjiffiesとtimeという名前のファイルが2つ作成されます。

実行結果の確認
[root@server stat]# pwd
/sys/kernel/test/stat

[root@server stat]# ls -l
合計 0
-r--r--r--. 1 root root 4096 12月 14 19:51 jiffies
-r--r--r--. 1 root root 4096 12月 14 19:51 time

[root@server stat]# cat jiffies
4295979110

[root@server stat]# cat time
1544784692

11 デバイスの登録、削除

ソース
[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
実行結果の確認
[root@server modules]# ls -l /dev/test_device
crw-------. 1 root root 10, 55  1月 21 16:44 /dev/test_device
モジュールのアンロード
[root@server modules]# rmmod test.ko

[root@server modules]# ls -l /dev/test_device
ls: /dev/test_device にアクセスできません: そのようなファイルやディレクトリはありません

Z 参考情報

カーネルモジュールを作ってみる
insmodの時に引数を渡したい
Linux デバイスドライバ -よりプログラムらしく

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away