#author("2016-08-09T02:05:02+09:00","default:afuruta","afuruta") * Hello World [#p0df494b] ほぼ何もしない "Hello World" プログラムを作って Linux Kernel に組み込む単純なモジュールの作り方を見ていきます。まだ、デバイスドライバには遠いです。 ** ソースコード [#r86823f5] &ref(hello_world.tar.gz,,添付ファイル); に一式をまとめておきます。C 言語で実行部を、Makefile で構築手順を書きます。Makefile を書かずにコンパイルできるかというと、自分では試したことがないです。煩雑な記述をした割には得るものが無いでしょう。 *** 実行部 [#h10ff46b] #code(c,hello_world.c) *** Makefile [#o9dc2a8d] #code(Makefile) ** userland アプリ・ケーションと違うところ [#yb45c026] *** module_init, module_exit [#d8f36181] main() はありません。モジュールは Kernel に組み込まれる時 &ogdefs(module_init(),module_init,init.h); で指定した関数を実行します。モジュールが Kernel から外されるとき &ogdefs(module_exit(),module_exit,init.h); で指定した関数を実行します。 モジュールが「組み込まれた」、「外された」時に呼ばれる仕掛けを作ることができるのは分りました。しかし、デバイス・ドライバを構成するにはまだ足りません。「ハードウエアにドライバが対応するデバイスが追加されたら」、あるいは、「モジュールが組み込まれた時点で既にドライバが対応するハードウエアが存在したら」、呼び出される様な仕掛けをまだ作っていません。後々 probe, remove と呼ばれる「メソッドのような関数」を見ていきます。 *** printk [#w0e5feae] &ogdefs(printk); は printf に相当する関数です。&ogfileone(書式文字列, Documentation/printk-formats.txt);を指定して書式文字列に続く引数の内容を表示します。書式文字列は printf とよく似ています。興味深く注意が必要な拡張は %p とその派生書式です。ここでは printk のヘッダファイル読み込みに &ogfileone(linux/kernel.h,include/linux/kernel.h); を使用しました。もちろん、&ogfileone(linux/printk.h,include/linux/printk.h); を直接インクルードするのも良いでしょう。 #textbox(note,printk に浮動小数点形式の書式が無いようだが...){{ printkの書式に浮動小数点形式はありません。kernel の中では double, float とその派生型は使えません。自分は実施したことが無いのですが、浮動小数点レジスタの値とこれに関係するコンテキストを保存・復帰すれば使えるはずです。ただし、処理の出入り口に実装する程度の簡単なことではなく、suspend/resume、コンテキスト・スイッチ、プロセッサ active/idle の状況などを細かに意識する必要が有ります。実装に問題があるとアプリケーションで行っている計算結果が不意にとんでもない値になるなど問題追跡が難しい状況に陥ります。 }} *** __init, __exit [#f0e4c8af] 関数に &ogdefs(__init,__init,linux/init.h); と &ogdefs(__exit,__exit,linux/init.h); 修飾子がついています。 &ogfileone(include/linux/init.h); を読んでみると、広域変数に対して似たようなマクロ定義があることが判ります。&ogfiledirect(__init と __exit 修飾子の目的は, Documentation/DocBook/kernel-hacking/routines-init.html); 次のようになります。 - __init: モジュールをロードするときに呼び出す関数を修飾します。より厳密に言えば &ogdefs(module_init(),module_init,init.h); で指定した関数から呼び出し関係を辿れて、辿った経路以外からの呼び出しが無い関数を修飾します。__init が付いた関数はモジュール・ロード(組み込み)が終了し module_init() からの呼び出しが完了した時点でメモリから削除されます。 - __exit: モジュールをアンロードする時に呼び出す関数を修飾します。より厳密に言えば &ogdefs(module_exit(),module_exit,init.h); で指定した関数から呼び出し関係を辿れて、辿った経路以外からの呼び出しが無い関数を修飾します。(このページでは取り上げないのですが)モジュールを Kernel に静的に結合した場合、モジュール・アンロードは起きません。__exit を付けた関数は Kernel のオブジェクトコードから除去されます。 __init と __exit は Kernel のメモリ使用量をなるべく減らすために使われる修飾子です。 *** KERN_INFO [#ac85fd7f] &ogdefs(KERN_INFO,KERN_INFO,linux/kern_levels.h); は kernel message の表示レベルを意味します。&ogfileone(include/linux/kern_levels.h); には次のレベルが定義されています。おそらく普通に使うのは KERN_ERR より大きい Level 番号のメッセージでしょう。下の表の alternate function pr_xxx は printk に KERN_XXX マクロを組み合わせて定義したマクロです。デバイス・ドライバ向けに dev_xxx 関数も用意されています。dev_xxx はデバイスの場所や関連するコンテキストが格納された &ogdefs(struct device,device,linux/device.h); を指すポインタを引数に渡します。 |Macro|Level|alternate function|alternate function&br;for device driver|Description|h |&ogdefs(KERN_EMERG,KERN_EMERG,linux/kern_levels.h);|CENTER:0|&ogdefs(pr_emerg,pr_emerg,printk.h);|&ogdefs(dev_emerg,dev_emerg,device.h);|system is unusable| |&ogdefs(KERN_ALERT,KERN_ALERT,linux/kern_levels.h);|CENTER:1|&ogdefs(pr_alert,pr_alert,printk.h);|&ogdefs(dev_alert,dev_alert,device.h);|action must be taken immediately| |&ogdefs(KERN_CRIT,KERN_CRIT,linux/kern_levels.h);|CENTER:2|&ogdefs(pr_crit,pr_crit,printk.h);|&ogdefs(dev_crit,dev_crit,device.h);|critical conditions| |&ogdefs(KERN_ERR,KERN_ERR,linux/kern_levels.h);|CENTER:3|&ogdefs(pr_err,pr_err,printk.h);|&ogdefs(dev_err,dev_err,device.h);|error conditions| |&ogdefs(KERN_WARNING,KERN_WARNING,linux/kern_levels.h);|CENTER:4|&ogdefs(pr_warn,pr_warn,printk.h);, &ogdefs(pr_warning,pr_warning,printk.h);|&ogdefs(dev_warn,dev_warn,device.h);|warning conditions| |&ogdefs(KERN_NOTICE,KERN_NOTICE,linux/kern_levels.h);|CENTER:5|&ogdefs(pr_notice,pr_notice,printk.h);|&ogdefs(dev_notice,dev_notice,device.h);|normal but significant condition| |&ogdefs(KERN_INFO,KERN_INFO,linux/kern_levels.h);|CENTER:6|&ogdefs(pr_info,pr_info,printk.h);|&ogdefs(dev_info,dev_info,device.h);|informational| |&ogdefs(KERN_DEBUG,KERN_DEBUG,linux/kern_levels.h);|CENTER:7|&ogdefs(pr_debug,pr_debug,printk.h);, &ogdefs(pr_devel,pr_devel,printk.h);|&ogdefs(dev_dbg,dev_dbg,device.h);|debug-level messages&br;pr_debug, pr_devel, dev_dbg は DEBUG, CONFIG_DYNAMIC_DEBUG マクロにより、実装したコードそのものが削除されたり、&ogfileone(/sys/kernel/debug/dynamic_debug/contol を通して動的に制御できる,Documentation/dynamic-debug-howto.txt); ようになります。| #textbox(note,KERN_XXX は行の先頭で有効です){{ KERN_XXX は行の先頭にあるものが有効です。printk を何回か使い、行を分割して出力した場合に、途中に KERN_XXX を入れても効果がありません。途中に誤って入れても、致命的な問題は起こしません。表示やログファイルの内容が乱れるだけです。 }} &ogfiledirect(/proc/sys/kernel/printk リンク先の printk 節を参照,Documentation/sysctl/kernel.txt); にログレベルを書き込むことで console と ログファイル(厳密にはログ出力ノード /proc/kmsg (&ogfileone(/proc/kmsg を実装している fs/proc/kmsg.c,fs/proc/kmsg.c);) に出力するレベルを制御できます。説明の中で順位に "Higher" とあるのは上記表 Level においてより小さい値です。 別のページで各種のデバッグ手法について集中的に説明する予定です。 *** MODULE_LICENSE [#p712afbe] &ogdefs(MODULE_LICENSE,MODULE_LICENSE,include/linux/module.h); はモジュールのライセンスを指定します。このライセンスは &ogfiledirect(シンボルの結合規則,Documentation/DocBook/kernel-hacking/symbols.html);に関係しています。モジュールが結合可能な Kernel symbol は &ogdefs(EXPORT_SYMBOL,EXPORT_SYMBOL,include/linux/export.h); または &ogdefs(EXPORT_SYMBOL_GPL,EXPORT_SYMBOL_GPL,include/linux/export.h); によって Kernel から export されています。MODULE_LICENSE マクロの引数に指定する license 文字列と結合可能なシンボルは次の通りです。 |license 文字列|モジュールから結合可能な Kernel symbol export 方法|h |"GPL", "GPL v2", "GPL and additional rights", "Dual BSD/GPL", "Dual MIT/GPL", "Dual MPL/GPL"|EXPORT_SYMBOL, EXPORT_SYMBOL_GPL| |Proprietary|EXPORT_SYMBOL| ライセンスの宣言通り、ソースコードを配布できるようにする必要が有ります。 モジュールのソースを公開したくなければ "Proprietary" を指定します。このようにすると、結合可能なシンボルは限られます。開発段階で "GPL"を指定し、リリース直前に "Proprietary" を指定すると結合できないシンボルが出てきて、実装し直しあるいはライセンス変更と言った混乱が発生します。 #textbox(note,Kernel に静的に結合する場合){{ Kernel に静的に結合する場合は結合先のシンボルが EXPORT_SYMBOL または EXPORT_SYMBOL_GPL で宣言されていなくても結合できます。ただし、結合させようとするコードのライセンスは GPL または GPL ライセンスのコードと(&ogfileone(結合可能と見なされるライセンス include/linux/module.h 参照,include/linux/module.h);)でリリースする必要が有ります。 }} *** MODULE_DESCRIPTION, MODULE_AUTHOR [#mdfbd846] ** Linux Kernel に動的に組み込むモジュールとして作成する [#rafbd0e4] *** Makefile を書く [#kea9666f] ** Linux Kernel に組み込む [#i3d8ce65] *** 何も起きない? dmesg で確かめよう [#v37a74c8] *** printk は何処と結合しているの? [#g9da7396] *** 多重に組み込めない [#c6e7352f] * Hello Friend [#x7542c70] ** パラメータを入力する [#a8504c49] ** エラーを返してみよう [#fca65edf]