これは「[痛デバドラ] /dev/louise_love作ってみた [Linux]」の解説記事です。
今回は「Linux2.6系(今回は2.6.32)からドライバとして認識されるには最低限何をしないといけないのか」をまとめてみます。
えらく貧相な話ですが、最初のとっかかりとしては需要のある話かなあ、と。というかですね、自分自身が欲しかったのですよ。
■キャラクタ型とブロック型デバイス
Linuxのデバイスは、おおよそ二つの種類に分けられる…らしいです。ブロック型というのは固定長の長さずつでしか入出力出来ないデバイスで、キャラクタ型はそれ以外、だそうです。とりあえず、The Linux Kernel APIを見ると別々にAPIが定義されてるので、OSからもかなり別物として定義されているようです。
今回は、とりあえず情報も多く楽なキャラクタ型デバイスを作ります。
■サンプルソースコード
「[痛デバドラ] /dev/louise_love作ってみた [Linux]」のソースコードを使って解説します。githubでも公開していて、Webから見るならこちらの方が便利かもしれません。
■デバイスドライバに最低限必要な関数
カーネルモジュールとして名乗るために、名前やライセンスを名乗る必要があります。
MODULE_DESCRIPTION("\"I love Louise tan!\" MODULE");
MODULE_AUTHOR("PSI");
MODULE_DESCRIPTION("\"I love Louise tan!\" MODULE");
MODULE_LICENSE("GPL");
MODULE_AUTHOR("PSI");
MODULE_DESCRIPTION("\"I love Louise tan!\" MODULE");
MODULE_LICENSE("GPL");
なお、”MODULE_LICENSE(“GPL”);”ですが、これがオープンソース系ライセンスでないととロードした時に
Warning: loading /lib/modules/..../***.o will taint the kernel: non-GPL License - Proprietary
Warning: loading /lib/modules/..../***.o will taint the kernel: non-GPL License - Proprietary
Warning: loading /lib/modules/..../***.o will taint the kernel: non-GPL License - Proprietary
という感じのワーニングメッセージが出るそうですが、Linuxantという会社は
MODULE_LICENSE("GPL\0for files in the \"GPL\" directory; for others, only LICENSE file applies");
MODULE_LICENSE("GPL\0for files in the \"GPL\" directory; for others, only LICENSE file applies");
MODULE_LICENSE("GPL\0for files in the \"GPL\" directory; for others, only LICENSE file applies");
としてその汚染メッセージの表示を回避したとか…。よう考えるわ。
さて。まず、初期化と終了のための関数が必要です。初期化はinit_module、終了処理はcleanup_moduleという関数で行ってください。どうしても別の名前が良い時は、
module_exit( 終了処理をする関数名 );
module_init( 初期化する関数名 );
module_exit( 終了処理をする関数名 );
module_init( 初期化する関数名 );
module_exit( 終了処理をする関数名 );
というのを関数外に書くことで代用できます。
■初期化処理
if ( register_chrdev( 0x0721, "louise_love", &louise_fops ) ) {
printk( KERN_INFO "louise_love : louise chan ha genjitsu ja nai!?\n" );
buildDictionary();//ルイズコピペ表示のための辞書作成処理
printk( KERN_INFO "louise_love : kunka kunka.\n" );
int init_module( void ){
if ( register_chrdev( 0x0721, "louise_love", &louise_fops ) ) {
printk( KERN_INFO "louise_love : louise chan ha genjitsu ja nai!?\n" );
return -EBUSY;
}
buildDictionary();//ルイズコピペ表示のための辞書作成処理
printk( KERN_INFO "louise_love : kunka kunka.\n" );
return 0;
}
int init_module( void ){
if ( register_chrdev( 0x0721, "louise_love", &louise_fops ) ) {
printk( KERN_INFO "louise_love : louise chan ha genjitsu ja nai!?\n" );
return -EBUSY;
}
buildDictionary();//ルイズコピペ表示のための辞書作成処理
printk( KERN_INFO "louise_love : kunka kunka.\n" );
return 0;
}
0を返すと初期化に成功したとカーネルが判断し、ロードされます。register_chrdevでデバイスとしてカーネルに登録でき、この時の0x0721はメジャー番号と言って、あとで/dev/louise_loveとドライバの紐付けを行う際に必要な番号、”louise_love”はデバイスドライバの名前(lsmodしたときに見れるやつ)、louise_fopsはfile_operations構造体で、カーネルからデバイスを操作するときに使われる関数で、あとで定義します。
register_chrdevの詳しい説明はこちらで。
printkというのが出てきますが、これはprintfのカーネル版で、dmesgした時等に表示されます。フォーマットストリングの前にはKERN_INFO等を付けないと表示されません。詳しくはこちら。
■終了処理
void cleanup_module( void ){
unregister_chrdev( 0x0721, "louise_love" );
printk( KERN_INFO "louise_love : Harukeginia no louise he todoke !!\n" );
void cleanup_module( void ){
unregister_chrdev( 0x0721, "louise_love" );
printk( KERN_INFO "louise_love : Harukeginia no louise he todoke !!\n" );
}
void cleanup_module( void ){
unregister_chrdev( 0x0721, "louise_love" );
printk( KERN_INFO "louise_love : Harukeginia no louise he todoke !!\n" );
}
unregister_chrdevでアンロードするだけです。特に説明しません。詳しくはこちら。
■デバイス操作関数
カーネルはregister_chrdevに渡すfile_operations構造体に記された関数を通してデバイスを操作します。デバイスドライバを作るには、そのうち最低限以下を実装しなければなりません。
ここを見てプロトタイプの通りに実装してください。
open( struct inode* inode, struct file* filp )のinodeはとりあえず無視していいみたいで、filpのfilp->private_dataに自分でkmallocを使って確保したデータを渡すことで、このデータも使ってwriteやreadが制御できます。
あとは多分ソース見れば分かるかな…?
■デバイスドライバをコンパイルするための最低限のMakefile
”-DMODULE -D__KERNEL__”を付ければ普通にコンパイルできるよ!って話もあったんですが出来ませんでした。-Iとかも色々いじったりもしたんですが通りません。昔の話みたいです。
というわけで、こちらのサイトのMakefileを流用しましょう。
ファイル名はMakefile(先頭大文字)じゃないとエラーが出るみたいです。
今回は、このMakefileにこんな行も追加してインストール・アンインストールもできるようになっています。
sudo mknod /dev/louise_love c 0x0721 0
sudo chmod 0666 /dev/louise_love
sudo insmod louise_love.ko
sudo rm -f /dev/louise_love
install: all
sudo mknod /dev/louise_love c 0x0721 0
sudo chmod 0666 /dev/louise_love
sudo insmod louise_love.ko
uninstall:
sudo rmmod louise_love
sudo rm -f /dev/louise_love
install: all
sudo mknod /dev/louise_love c 0x0721 0
sudo chmod 0666 /dev/louise_love
sudo insmod louise_love.ko
uninstall:
sudo rmmod louise_love
sudo rm -f /dev/louise_love
mknodで特殊ファイルを作成&0x0721というメジャー番号を使って作成しています。cはキャラクタデバイスで、0x0721はメジャー番号で、register_chrdevしたときの数字とあわせてください。0(マイナー番号)はちょっとよくわかりまへん…。マニュアルはこちら。
chmod 666はただたんに、誰でも読み書きできるようにしているだけです。
特殊ファイルを作ったあとにinsmodして作ったカーネルモジュールを読み込んで、/dev/louise_loveの制御を作ったドライバに任せています。
uninstallでrmmod <モジュール名>することでドライバをアンロード、スペシャルファイルは普通にrmdirで削除できます。
今回分かったんですが、bashでのif文はこういう構造なんですね。
if <条件:[ -e /tmp/...]など>; ←文の句切れ
then コマンド1; ←then~コマンドまでで一文
else コマンド2; 同様 else~コマンドで一文
if <条件:[ -e /tmp/...]など>; ←文の句切れ
then コマンド1; ←then~コマンドまでで一文
else コマンド2; 同様 else~コマンドで一文
fi
if <条件:[ -e /tmp/...]など>; ←文の句切れ
then コマンド1; ←then~コマンドまでで一文
else コマンド2; 同様 else~コマンドで一文
fi
いやーこれに気づかずに結構時間が掛かりました…。thenとコマンド1のあいだ、elseとコマンド2のあいだに;をいれるとエラーがでちゃうんだもん。
■さらなる情報を得るには(参考サイト一覧)
デバイスドライバのインターフェーイスは割と変わるみたいなので、他の記事を参考にする場合はバージョン情報や書かれた月日は参考にしたほうがいいです。
ちなみにこの記事はカーネル2.6.32を対象に書いています。