前回に予告したようにLinux用ドライバを書いてみる。簡単なものから順に複数のドライバを書いていきたい。今回はもっとも単純なもので、ドライバのロード時とアンロード時にメッセージを出力するドライバを書く。
まぁこの内容、Raspberry Pi用に書いた以前の記事の内容に含まれているので、それの簡易版となる。すでに実践した人は飛ばしてもOK。
前準備
ドライバはカーネル関連のプログラムなので、Linuxのディストリビューションによる違いはあまりない。
そのため、試すのはLinuxならどんな物でもいいが、今回はVM環境でデスクトップ版Ubuntu Linuxを使ってドライバを作成する。
なぜUbuntuかと言うと単に使い慣れているから。18.04で試すがこれも環境がすでにあるからで、基本的に最近のLinuxならどれでもいいと思う。もともとRaspberry Piでドライバ作ろうとしていたのでRaspbianでもいいけど、自分はリモート環境が面倒なので、ある程度VMで作成して後からRaspberry Pi用にビルドしようと考えてた。
LinuxカーネルはC言語なので、基本Cで組むことになる。
テキストエディタはなんでもいいけど、ビルドするためにgccとかmakeは入れる必要がある。あと、カーネルの開発環境が必要になる。
$ sudo apt-get update
$ sudo apt-get install build-essential
$ sudo apt-get install linux-headers-$(uname -r)
最新版のUbuntuを使用していると、ヘッダは最新だよ、と言われた。
作法として、ディレクトリを切っておこう。今後のことも考えてちょっと深くまで。
$ mkdir ~/drivers
$ cd ~/drivers
$ mkdir simple
$ cd simple
ここを今回の作業ディレクトリとする。
Linuxドライバとは?
Linuxでのドライバの枠組みはどのようなものか?
LinuxはOSの核となるカーネルとアプリケーションからなるが、カーネルには「モジュール」というプログラムを動的にロード・アンロードできる枠組みがある。ドライバは主にこのモジュールの枠組みで作成される。
以前、「ドライバはOSのプラグインみたいなもの」と説明したのは、この辺りの構造による。
ちなみに、以前は動的な組み込みはできず、カーネルを再コンパイルしていた。カーネルの再コンパイル、聞いただけで地獄の釜の底が見えるような気になるトラウマワードだ。それくらい大変だった。
モジュールはカーネルの一部でありカーネルの機能を使用するため、モジュールのコンパイルにはカーネルのヘッダなどが必要になる。この辺りはCをやってたらわかるね?
また、動作しているOSのカーネルバージョンと、モジュールのビルドに使用したカーネルヘッダのバージョンはあっていなければロードできない。
上でさっとインストールコマンドを書いたが、クロスコンパイルの場合は若干めんどくさくなる。今回はLinux向けなので1行でヘッダをインストールできた。
モジュールは通常のアプリケーションと同様にC言語で書くことができる。内容はmain()などのエントリポイントが無く、イベントハンドラを実装していく形となる。
Hello World!
実際のコードは下記のようなものとなる。
hello.c
#include <linux/module.h>
#include <linux/init.h>
MODULE_LICENSE("Dual MIT/GPL");
MODULE_DESCRIPTION("Hello Driver");
static int mod_init(void)
{
printk(KERN_ALERT "driver loaded\n");
return 0;
}
static void mod_exit(void)
{
printk(KERN_ALERT "driver unloaded\n");
}
module_init(mod_init);
module_exit(mod_exit);
このドライバは、ロード時とアンロード時にメッセージを出力するだけのドライバとなる。(機能的にドライバですらないが)
上から順に説明する。
MODULE_LICENSEでドライバのライセンスを指定する。ここではDualライセンス。
MODULE_DESCRIPTIONでドライバの簡単な説明を指定する。
mod_init関数は、モジュールがロードされた際に呼ばれるハンドラ。
mod_exit関数は、モジュールがアンロードされた際に呼ばれるハンドラ。
これは名前が決まっているわけではない。そのあとのmodule_init、module_exitで関数名を指定する。
なので、関数名は好きにきめてもOK。
各ハンドラ内にprintkという関数があるかと思う。printfではない。
カーネル内ではprintkを使う。ただし、ドライバにはコンソールとかないので、出力はdmesgというカーネルメッセージ確認コマンドでみることができる。
早速ビルドしたいが、モジュール用コンパイルコマンドは少し書き方が複雑なのでMakefileを作成しよう。
Makefile
obj-m := hello.o
all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
モジュールビルド用Makeの基本形。
Makefileはインデントがタブでないと怒られるので、上のコードをコピーした後に調整してね。
おもむろにmakeする。
$ ls
hello.c Makefile
$ make
.oだの.mod.cだの色々ファイルができるが、この中のhello.koファイルがモジュールとなる。
ロード・アンロード
ロード・アンロードにはそれぞれinsmodコマンドとrmmodを使用する。
その前に、一度dmesgコマンドを実行して、現状のメッセージを確認しておくと、違いがわかっていいかも。
さあ、ロードしてみよう。
$ sudo insmod hello.ko
一度ここでもdmesgを打ってみると、mod_init関数内でprintkしたメッセージが表示されるはず。
アンロードは
$ sudo rmmod hello
とする。またdmesgでメッセージを確認してみよう。
プログラムのprintkの内容を変えて試してみるのもいい。
今回は、Linuxドライバの基本形を書いた。
もちろんハンドラはinit/exitのみではないので、色々実装していくとちゃんとドライバになっていく。
C言語のプログラムなので、規模が大きくなってくるとファイル分割をする必要があるだろう。そうなってくるとIDEとかを導入してもいいかもしれない。
次回は、アプリケーションとの連携を行ってみたい。
0 件のコメント:
コメントを投稿