はじめに
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 KernelSYNOPSIS
lsmodDESCRIPTION
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 KernelSYNOPSIS
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 KernelSYNOPSIS
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」という投稿がありました。
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へのアクセスを実験してみます。