Raspberry Piでカーネルモジュールを作成

はじめに

2017年3月、Raspberry Pi2に搭載しているARM Cortex-A7の特権レベルで動作するプログラムの作成方法について調べました。

ARMのシステム制御コプロセッサ(CP15)は、プロセッサが実装している機能のステータス参照や制御を行うものです。CP15を操作するほとんどの命令は、特権レベルで実行しなければなりません。

Linuxが動作している場合は、次のようになります。

  • 非特権モード : アプリケーション用の動作モードで、ハードウェアに直接アクセスできません
  • 特権モード : カーネル用の動作モードで、あらゆるハードウェアにアクセスできます

すなわち、Linux上の一般的なアプリケーションソフトは特権レベルの命令を使用できません。特権レベルで動作するプログラムは、カーネルに組み込むことになりそうです。

カーネルとカーネルモジュール

私は、Linuxのカーネルのソースコードを読んだことはありません。また、カーネルの再構築をしたこともありません。そんなわけで、技術的な経験不足からカーネルに何かを組み込むことは、とてもハードルの高い作業と考えておりました。

カーネルモジュール

カーネルモジュール

 

しかし、少し調べてみるとカーネルに追加で組み込むことが可能なカーネルモジュールというものがありました。

  • 常駐カーネル : 起動時にメモリロードするカーネル
  • カーネルモジュール : カーネルの機能を拡張するモジュール

カーネルモジュールには、デバイスの制御を行うデバイスドライバなどがあります。デバイスドライバは、下記の組み込み方法があります。

  • カーネルにビルドインして、システム起動時にカーネルをメモリロードした時点でデバイスをサポート
  • カーネルモジュールで組み込んで、必要なときにカーネルモジュールをロードしてデバイスをサポート

デバイスドライバは、通常のアプリケーションプログラムのように「ユーザー空間」で動くのではなく、「カーネル空間」で動く特権モードのプログラムです。

特権レベルの命令を使用するために、デバイスドライバのプログラム作成ならば、少しハードルを下げられそうです。

カーネルモジュール関連のコマンド

lsmod :モジュールの一覧表示

lsmodコマンドで、現在ロードされているカーネルモジュールを示します。

NAME
lsmod – Show the status of modules in the Linux Kernel

SYNOPSIS
lsmod

DESCRIPTION
lsmod is a trivial program which nicely formats the contents of the /proc/modules, showing what kernel modules are currently loaded.

insmod :モジュールのロード

insmodコマンドで、モジュールをカーネルに組み込みます。

NAME
insmod – Simple program to insert a module into the Linux Kernel

SYNOPSIS
insmod [filename] [module options…]

DESCRIPTION
insmod is a trivial program to insert a module into the kernel. Most users will want to use modprobe(8) instead, which is more clever and can handle module dependencies.
Only the most general of error messages are reported: as the work of trying to link the module is now done inside the kernel, the dmesg usually gives more information about errors.

rmmod :モジュールのアンロード

rmmodコマンドで、モジュールをカーネルから削除します。

NAME
rmmod – Simple program to remove a module from the Linux Kernel

SYNOPSIS
rmmod [-f] [-s] [-v] [modulename]

DESCRIPTION
rmmod is a trivial program to remove a module (when module unloading support is provided) from the kernel. Most users will want to use modprobe(8) with the -r option instead.

デバイスドライバの開発環境構築

デバイスドライバは、/dev/hda1などのデバイスファイルに対して、open/ioctl/read/write/closeなどのシステムコールを使用してデバイス制御や入出力を行います。

今回の目的は、特権モードの命令を使用することですから、デバイスドライバの初歩的なプログラムについて記載します。

カーネルモジュールを作成するには、カーネルヘッダーやカーネルソースが必要になります。

公式サイトのForumに「Linux kernel source installer」という投稿がありました。

rpi-source

rpi-source

 

I have made a script that can install and prepare the kernel source for the running kernel.
This makes it easy to build kernel modules, both in-tree and external.
It works on a fresh Raspian image and on a rpi-update’d image.
Wiki: https://github.com/notro/rpi-source/wiki

【要約】
実行中のカーネル(と同じバージョン)のカーネルソースをインストールして準備できるスクリプトを作成しました。これにより、ツリー内外のカーネルモジュールの構築が容易になります。

rpi-sourceのwikiを参照します。

rpi-source installs the kernel source used to build rpi-update kernels and the kernel on the Raspian image.
This makes it possible to build loadable kernel modules.
It is not possible to build modules that depend on missing parts that need to be built into the kernel proper (bool in Kconfig).

【要約】
rpi-sourceは、rpi-updateカーネルとカーネルをビルドするためのカーネルソースをRaspianイメージにインストールします。これにより、ロード可能なカーネルモジュールを構築することができます。
カーネル本体に組み込む必要があるパーツに依存するモジュールを構築することはできません。

rpi-sourceというツールは、カーネルモジュールを構築するために必要なカーネルソースを入手できることがわかりました。


① rpi-sourceスクリプトを /usr/bin/にダウンロードします。

$ sudo wget https://raw.githubusercontent.com/notro/rpi-source/master/rpi-source -O /usr/bin/rpi-source

–2017-03-13 17:37:45– https://raw.githubusercontent.com/notro/rpi-source/master/rpi-source
raw.githubusercontent.com (raw.githubusercontent.com) をDNSに問いあわせています… 151.101.100.133
raw.githubusercontent.com (raw.githubusercontent.com)|151.101.100.133|:443 に接続しています… 接続しました。
HTTP による接続要求を送信しました、応答を待っています… 200 OK
長さ: 12670 (12K) [text/plain]
`/usr/bin/rpi-source’ に保存中

・・・

2017-03-13 17:37:45 (863 KB/s) – `/usr/bin/rpi-source’ へ保存完了

② rpi-sourceスクリプトに実行権を付与して実行します。

$ sudo chmod +x /usr/bin/rpi-source
$ sudo /usr/bin/rpi-source -q --tag-update
$ sudo rpi-source

*** gcc version check: OK
*** rpi-update: https://github.com/Hexxeh/rpi-firmware
*** Firmware revision: b30461d2621e71eb9b8845b38738af177b9181ed
*** Linux source commit: a599f69212b051db4cd00a02f9312dc897beba70
*** Download kernel source
・・・

$ sudo ls -l /root
lrwxrwxrwx  1 root root        52  3月 14 20:20 linux -> /root/linux-a599f69212b051db4cd00a02f9312dc897beba70
drwxrwxr-x 25 root root      4096  3月 15 12:57 linux-a599f69212b051db4cd00a02f9312dc897beba70
-rw-r--r--  1 root root 144899023  3月 14 20:14 linux-a599f69212b051db4cd00a02f9312dc897beba70.tar.gz

rpi-sourceで、/root にカーネルソースがダウンロードできました。

$ ls -l /lib/modules

drwxr-xr-x 3 root root 4096 7月  9 2016 4.4.14+
drwxr-xr-x 3 root root 4096 7月  9 2016 4.4.14-v7+
drwxr-xr-x 3 root root 4096 2月  2 15:26 4.4.45+
drwxr-xr-x 3 root root 4096 2月  2 19:22 4.4.45-v7+
drwxr-xr-x 3 root root 4096 3月 14 20:09 4.9.14+
drwxr-xr-x 3 root root 4096 3月 14 20:20 4.9.14-v7+
$ ls -l /lib/modules/4.9.14-v7+/

lrwxrwxrwx  1 root root     11  3月 14 20:20 build -> /root/linux
drwxr-xr-x 11 root root   4096  3月 14 20:08 kernel
-rw-r--r--  1 root root 473352  3月 14 20:08 modules.alias
-rw-r--r--  1 root root 487249  3月 14 20:08 modules.alias.bin
-rw-r--r--  1 root root   4807  3月 14 20:08 modules.builtin
-rw-r--r--  1 root root   6304  3月 14 20:08 modules.builtin.bin
-rw-r--r--  1 root root 150075  3月 14 20:08 modules.dep
-rw-r--r--  1 root root 217705  3月 14 20:08 modules.dep.bin
-rw-r--r--  1 root root    302  3月 14 20:08 modules.devname
-rw-r--r--  1 root root  60295  3月 14 20:08 modules.order
-rw-r--r--  1 root root     55  3月 14 20:08 modules.softdep
-rw-r--r--  1 root root 197088  3月 14 20:08 modules.symbols
-rw-r--r--  1 root root 243290  3月 14 20:08 modules.symbols.bin
lrwxrwxrwx  1 root root     11  3月 14 20:20 source -> /root/linux

これで、デバイスドライバの開発環境が構築できました。

デバイスドライバの初歩的なサンプルコード

デバイスドライバの初歩的なサンプルコードとして、「Hello World」のようにメッセージを表示するデバイスドライバを書きます。

デバイスドライバのサンプルコード

下記に device_drv.c プログラムコードを示します。

$ cat device_drv.c
#include 
#include 


MODULE_LICENSE("Dual BSD/GPL");

static int device_init(void)
{
     printk(KERN_ALERT "Start Test Device \n");

     return 0;
}

static void device_exit(void)
{
     printk(KERN_ALERT "End.. Test Device  \n");
}

module_init(device_init);

module_exit(device_exit);


サンプルコードの説明

  • MODULE_LICENSEマクロは、ドライバのライセンスを定義します。
  • module_initマクロは、ドライバロード時のエントリーポイントを示します。
  • module_exitマクロは、ドライバアンロード時のエントリーポイントを示します。

ドライバロード時に、module_initで指定した関数 device_init()を呼び出します。正常終了時は、戻り値「0」とします。異常終了の場合は、戻り値「0以外」を返します。

ドライバアンロード時に、module_exitで指定した関数 device_exit()を呼び出します。戻り値はありません。

関数 printk(const char*fmt, …)を使用すると、メッセージをカーネルバッファに出力します。カーネルバッファはdmesgコマンドで参照することができます。

書式の先頭にメッセージのログレベルを付けます。

  • KERN_EMERG : An emergency condition; the system is probably dead
  • KERN_ALERT : A problem that requires immediate attention
  • KERN_CRIT : A critical condition
  • KERN_ERR : An error
  • KERN_WARNING : A warning
  • KERN_NOTICE : A normal, but perhaps noteworthy, condition
  • KERN_INFO : An informational message
  • KERN_DEBUG : A debug messagetypically superfluous

デバイスドライバのビルド

Makefileを作成します。

$ cat Makefile

KERNEL_DIR = /lib/modules/$(shell uname -r)/build

obj-m := devicemod.o

devicemod-objs := device_drv.o

all:
        make -C $(KERNEL_DIR) M=$(PWD) modules

clean:
        make -C $(KERNEL_DIR) M=$(PWD) clean

obj-m に作成するモジュール名(devicemod)を指定します。拡張子は、.oとします。
モジュール名-objs(devicemod-objs) にビルド対象のオブジェクト名を列記します。
今回は、ビルド対象のオブジェクトがひとつなので、device_drv.o のみを記載します。

カレントディレクトリには、ソースプログラムとMakefileがあります。

$ ls -l

-rw-r--r-- 1 pi pi 199 3月 15 12:55 Makefile
-rw-r--r-- 1 pi pi 324 3月 14 10:01 device_drv.c

カーネルモジュールをビルドします。ルート権限が必要なのでスーパーユーザーでmakeします。

$ sudo su
# make
make -C /lib/modules/4.9.14-v7+/build M=/home/pi/myHome/device_drv0 modules
make[1]: Entering directory '/root/linux-a599f69212b051db4cd00a02f9312dc897beba70'
CC [M] /home/pi/myHome/device_drv0/device_drv.o
LD [M] /home/pi/myHome/device_drv0/devicemod.o
Building modules, stage 2.
MODPOST 1 modules
CC /home/pi/myHome/device_drv0/devicemod.mod.o
LD [M] /home/pi/myHome/device_drv0/devicemod.ko
make[1]: Leaving directory '/root/linux-a599f69212b051db4cd00a02f9312dc897beba70'
$ ls -l

-rw-r--r-- 1 pi   pi    199  3月 15 12:55 Makefile
-rw-r--r-- 1 root root    0  3月 15 14:25 Module.symvers
-rw-r--r-- 1 pi   pi    324  3月 14 10:01 device_drv.c
-rw-r--r-- 1 root root 2668  3月 15 14:25 device_drv.o
-rw-r--r-- 1 root root 3152  3月 15 14:25 devicemod.ko
-rw-r--r-- 1 root root  612  3月 15 14:25 devicemod.mod.c
-rw-r--r-- 1 root root 1956  3月 15 14:25 devicemod.mod.o
-rw-r--r-- 1 root root 1936  3月 15 14:25 devicemod.o
-rw-r--r-- 1 root root   48  3月 15 14:25 modules.order

カーネルモジュール形式のデバイスドライバ devicemod.ko を生成しました。

ドライバをビルドした環境のカーネルバージョンと、そのドライバをロードする環境のカーネルバージョンを必ず一致させる必要があります。

生成したカーネルモジュールの情報で、ドライバをビルドした環境のカーネルバージョンを調べます。

# modinfo devicemod.ko
filename: /home/pi/myHome/device_drv0/devicemod.ko
license: Dual BSD/GPL
srcversion: 9F2051DBE5FF9547732CE8C
depends:
vermagic: 4.9.14-v7+ SMP mod_unload modversions ARMv7 p2v8

ドライバをロードする環境のカーネルバージョンを調べます。

$ uname -a
Linux pi2 4.9.14-v7+ #977 SMP Mon Mar 13 18:25:19 GMT 2017 armv7l GNU/Linux

カーネルバージョン番号(4.9.14-v7+)は、一致しています。

デバイスドライバのロードとアンロード

スーパーユーザーで実行します。

$ sudo su

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

# insmod devicemod.ko

ロードしているカーネルモジュール一覧で、ロードされたことを確認します。

# lsmod
Module Size Used by
devicemod 1086 0
・・・

カーネルバッファにメッセージを記録したことを確認します。

# dmesg | tail -2
[ 1971.424369] devicemod: loading out-of-tree module taints kernel.
[ 1971.424944] Start Test Device

カーネルモジュールをアンロードします。

# rmmod devicemod

カーネルバッファにメッセージを記録したことを確認します。

# dmesg | tail -2
[ 1971.424944] Start Test Device
[ 2286.810442] End.. Test Device

最後に

特権レベルの命令を使用するために、カーネルモジュール形式デバイスドライバの初歩的なプログラム作成方法がわかりました。カーネルモジュールのドライバロード時の処理に、特権レベルの命令を使用して、システム制御コプロセッサ(CP15)にアクセスすることができます。

次は、いよいよCP15へのアクセスを実験してみます。