2018/10/16

デバイスドライバを書いてみよう03 Linuxデバイスドライバを書いてみよう

前回まででドライバの概要やデータのやり取り方法を説明した。では実際にデバイスドライバを書いてみよう。
前回に予告したように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 件のコメント:

コメントを投稿