Hello World

ほぼ何もしない "Hello World" プログラムを作って Linux Kernel に組み込む単純なモジュールの作り方を見ていきます。まだ、デバイスドライバには遠いです。

ソースコード

file添付ファイル に一式をまとめておきます。C 言語で実行部を、Makefile で構築手順を書きます。Makefile を書かずにコンパイルできるかというと、自分では試したことがないです。煩雑な記述をした割には得るものが無いでしょう。

実行部

filehello_world.c
Expand allFold all
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 
 
 
-
|
|
|
!
 
-
|
!
 
-
|
|
!
 
-
!
 
 
 
 
 
 
 
#include <linux/kernel.h>
#include <linux/module.h>
 
/*
 * Tutorial hello world module init.
 * @return int == 0: Success. 
 *             < 0:  Fail, negative errno number.
 */
static int __init hello_world_init(void)
{       printk(KERN_INFO "%s: Hello world.\n", __func__);
        return 0;
}
 
/*
 * Tutorial hello world module exit.
 * @return void
 */
static void __exit hello_world_exit(void)
{       printk(KERN_INFO "%s: Goodbye world.\n", __func__);
}
 
module_init(hello_world_init);
module_exit(hello_world_exit);
 
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Tutorial hello world.");
MODULE_AUTHOR("Your name or email address.");

Makefile

fileMakefile
Expand allFold all
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
-
|
|
|
|
|
|
|
|
|
!
 
 
-
!
 
 
 
 
 
 
 
 
-
|
# Build Tutorial hello world module.
# To try this tutorial.
# make
# sudo insmod hello_world.ko
# dmesg
# lsmod
# sudo rmmod hello_world.ko
# dmesg
# lsmod
 
KERNEL_SOURCE_TREE:=/lib/modules/$(shell uname -r)/build
PWD:=$(shell pwd)
 
# specify object.
obj-m:=hello_world.o
 
.PHONY: modules clean # install
modules:
        make -C $(KERNEL_SOURCE_TREE) M=$(PWD) modules
 
clean:
        make -C $(KERNEL_SOURCE_TREE) M=$(PWD) clean
 
#install:
#       make -C $(KERNEL_SOURCE_TREE) M=$(PWD) install

hello_world.c が userland アプリケーションと違うところ

module_init, module_exit

main() はありません。モジュールは Kernel に組み込まれる時 module_init() で指定した関数を実行します。モジュールが Kernel から外されるとき module_exit() で指定した関数を実行します。

モジュールが「組み込まれた」、「外された」時に呼ばれる仕掛けを作ることができるのは分りました。しかし、デバイス・ドライバを構成するにはまだ足りません。「ハードウエアにドライバが対応するデバイスが追加されたら」、あるいは、「モジュールが組み込まれた時点で既にドライバが対応するハードウエアが存在したら」、呼び出される様な仕掛けをまだ作っていません。後々 probe, remove と呼ばれる「メソッドのような関数」を見ていきます。

__init, __exit

関数に __init__exit 修飾子がついています。 include/linux/init.h を読んでみると、広域変数に対して似たようなマクロ定義があることが判ります。__init と __exit 修飾子の目的は 次のようになります。

__init と __exit は Kernel のメモリ使用量をなるべく減らすために使われる修飾子です。

printk

printk は printf に相当する関数です。デバッグをするのに非常に有用な関数です。書式文字列を指定して書式文字列に続く引数の内容を表示します。書式文字列は printf とよく似ています。興味深く注意が必要な拡張は %p とその派生書式です。ここでは printk のヘッダファイル読み込みに linux/kernel.h を使用しました。もちろん、linux/printk.h を直接インクルードするのも良いでしょう。

printk に浮動小数点形式の書式が無い

printkの書式に浮動小数点形式はありません。kernel の中では double, float とその派生型は使えません。自分は実施したことが無いのですが、浮動小数点レジスタの値とこれに関係するコンテキストを保存・復帰すれば使えるはずです。ただし、処理の出入り口に実装する程度の簡単なことではなく、suspend/resume、コンテキスト・スイッチ、プロセッサ active/idle の状況などを細かに意識する必要が有ります。実装に問題があるとアプリケーションで行っている計算結果が不意にとんでもない値になるなど問題追跡が難しい状況に陥ります。

KERN_INFO

KERN_INFO は kernel message の表示レベルを意味します。include/linux/kern_levels.h には次のレベルが定義されています。おそらく普通に使うのは KERN_ERR より大きい Level 番号のメッセージでしょう。下の表の alternate function pr_xxx は printk に KERN_XXX マクロを組み合わせて定義したマクロです。デバイス・ドライバ向けに dev_xxx 関数も用意されています。dev_xxx はデバイスの場所や関連するコンテキストが格納された struct device を指すポインタを引数に渡します。

MacroLevelalternate functionalternate function
for device driver
Description
KERN_EMERG0pr_emergdev_emergsystem is unusable
KERN_ALERT1pr_alertdev_alertaction must be taken immediately
KERN_CRIT2pr_critdev_critcritical conditions
KERN_ERR3pr_errdev_errerror conditions
KERN_WARNING4pr_warn, pr_warningdev_warnwarning conditions
KERN_NOTICE5pr_noticedev_noticenormal but significant condition
KERN_INFO6pr_infodev_infoinformational
KERN_DEBUG7pr_debug, pr_develdev_dbgdebug-level messages
pr_debug, pr_devel, dev_dbg は DEBUG, CONFIG_DYNAMIC_DEBUG マクロにより、実装したコードそのものが削除されたり、/sys/kernel/debug/dynamic_debug/contol を通して動的に制御できる ようになります。

KERN_XXX は行の先頭で有効です

KERN_XXX は行の先頭にあるものが有効です。printk を何回か使い、行を分割して出力した場合に、途中に KERN_XXX を入れても効果がありません。途中に誤って入れても、致命的な問題は起こしません。表示やログファイルの内容が乱れるだけです。

/sys/kernel/debug の下に何もない

Linux ディストリビューションあるいは SoC メーカーが提供している Linux 環境によっては /sys/kernel/debug の下にノードが全くない場合が有ります。debugfs (debugfs の Kernel 内ヘッダ)というデバッグ専用のファイル・システムをマウントしてください。$ sudo mount -t debugfs none /sys/kernel/debug でマウントします。

/proc/sys/kernel/printk リンク先の printk 節を参照 にログレベルを書き込むことで console と ログファイル(厳密にはログ出力ノード /proc/kmsg (/proc/kmsg を実装している fs/proc/kmsg.c) に出力するレベルを制御できます。説明の中で順位に "Higher" とあるのは上記表 Level においてより小さい値です。

別のページで各種のデバッグ手法について集中的に説明する予定です。

MODULE_LICENSE

MODULE_LICENSE はモジュールのライセンスを指定します。このライセンスは シンボルの結合規則に関係しています。モジュールが結合可能な Kernel symbol は EXPORT_SYMBOL または EXPORT_SYMBOL_GPL によって Kernel から export されています。MODULE_LICENSE マクロの引数に指定する license 文字列と結合可能なシンボルは次の通りです。

license 文字列モジュールから結合可能な Kernel symbol export 方法
"GPL", "GPL v2", "GPL and additional rights", "Dual BSD/GPL", "Dual MIT/GPL", "Dual MPL/GPL"EXPORT_SYMBOL, EXPORT_SYMBOL_GPL
ProprietaryEXPORT_SYMBOL

ライセンスの宣言通り、ソースコードを配布できるようにする必要が有ります。

モジュールのソースを公開したくなければ "Proprietary" を指定します。このようにすると、結合可能なシンボルは限られます。開発段階で "GPL"を指定し、リリース直前に "Proprietary" を指定すると結合できないシンボルが出てきて、実装し直しあるいはライセンス変更と言った混乱が発生します。

Kernel に静的に結合する場合

Kernel に静的に結合する場合は結合先のシンボルが EXPORT_SYMBOL または EXPORT_SYMBOL_GPL で宣言されていなくても結合できます。ただし、結合させようとするコードのライセンスは GPL または GPL ライセンスのコードと(結合可能と見なされるライセンス include/linux/module.h 参照)でリリースする必要が有ります。

MODULE_DESCRIPTION, MODULE_AUTHOR

MODULE_DESCRIPTION, MODULE_AUTHOR はそれぞれモジュールの説明、モジュールの作者を設定します。 MODULE_AUTHOR は、フルネーム、メールアドレス、会社名などを入れます。ソース・コードに現れる例を参考に設定して下さい。これらの設定はモジュールの動作に影響を与えません。設定内容は次のコマンドで確かめることができます。

$  /sbin/modinfo module_name_or_module_path

Makefile が userland アプリ・ケーションと違うところ

module を構築するための Makefile の書き方 modules.txt に詳細が書かれています。ここでは、1 個のソース・ファイルから構築しているのでより簡単な Makefile にしました。Linux Kernel のソース・コードからバイナリを構築する Makefile の仕組みの上でビルドします。make の -C オプションでディレクトリを Linux Kernel のソース・コード・ツリーに移して M マクロにビルドするディレクトリのパスを設定します。

/lib/modules/$(shell uname -r)/build はどこのディレクトリ?

/lib/modules/$(shell uname -r)/build は今使っている Kernel を構築したソース(またはその中から構築に必要なファイルだけを残した)ディレクトリ・ツリーをシンボリック・リンクで指しています。手順通り進めていたならば、構築作業をしたばかりのソース・コードツリーを指しています。

Linux Kernel に動的に組み込むモジュールとして作成する

file添付ファイルを展開したディレクトリに移って、make します。

$ cd extracted_directory_base/hello_world
$ make

特に驚くような警告やエラーは出ないはずです。最終的なターゲットは hello_world.ko です。Linux ドライバを提供しているハードウエアの中には、すべて Makefile で書くのではなく、shell script で make を呼び出すものもあります。make の途中で出力された次のコマンドラインを実行する様に書かれています。

make -C /lib/modules/4.1.27-local/build M=/home/furuta/git/lkdq1stmodule modules

Linux Kernel に組み込む

make コマンドに続けて次のようにしてモジュールを組み込みます。何か起きましたか?

$ sudo insmod hello_world.ko
[sudo] password for user_name:password

何も起きない? dmesg で確かめよう

コンソールには insmod コマンドに反応した表示は何も出ないはずです(組み込み Linux 環境ならば Kernel message がコンソールに流れるように設定されている場合もあります)。プログラムに書かれた "Hello world." が出力されたのかは dmesg コマンドで確かめることができます。

$ dmesg
...省略...
[  910.921955] hello_world: module verification failed: signature and/or required key missing - tainting kernel
[  910.921955] hello_world_init: Hello world.

左側に表れている数値は printk timestamp, printk time と呼ばれているものです。nano second 単位の local_clock() 関数から得ている時刻です。

小数点付で時刻が表示されている

小数点付で時刻が表示されています。これは printk の表示を整数計算で行っているためです。print_time() がprintk time 表示処理です。整数計算で do_div() を使っています。kernel の中では long を超える語調の除算は / と % が使えない場合があります。アプリケーション・プログラムをコンパイルするときは演算子に対して自動的に計算するライブラリが結合します。しかし、Linux Kernel では演算子に対してライブラリを自動的に結合せず、商と余りを計算するマクロまたは関数を提供します。

Linux Kernel に組み込まれたことを確認する

モジュールが Linux に組み込まれていることを lsmod コマンドで確認します。下の様に下線がついた行が見つかるはずです。

$ lsmod
Module                  Size  Used by
hello_world            16384  0
rfcomm                 69632  0
bnep                   24576  2
bluetooth             516096  10 bnep,rfcomm
nfsd                  299008  13
autofs4                40960  4
auth_rpcgss            61440  1 nfsd
nfs_acl                16384  1 nfsd
nfs                   249856  0
lockd                  94208  2 nfs,nfsd
grace                  16384  2 nfsd,lockd
sunrpc                327680  19 nfs,nfsd,auth_rpcgss,lockd,nfs_acl
fscache                65536  1 nfs
snd_intel8x0           40960  2
snd_ac97_codec        135168  1 snd_intel8x0
ac97_bus               16384  1 snd_ac97_codec
snd_pcm               106496  2 snd_ac97_codec,snd_intel8x0
snd_seq_midi           16384  0
snd_seq_midi_event     16384  1 snd_seq_midi
snd_rawmidi            32768  1 snd_seq_midi
snd_seq                69632  2 snd_seq_midi_event,snd_seq_midi
joydev                 20480  0
snd_seq_device         16384  3 snd_seq,snd_rawmidi,snd_seq_midi
snd_timer              32768  2 snd_pcm,snd_seq
snd                    86016  11 snd_ac97_codec,snd_intel8x0,snd_timer,snd_pcm,snd_seq,snd_rawmidi,snd_seq_device
serio_raw              16384  0
soundcore              16384  1 snd
8250_fintek            16384  0
i2c_piix4              24576  0
mac_hid                16384  0
parport_pc             32768  0
ppdev                  20480  0
lp                     20480  0
parport                45056  3 lp,ppdev,parport_pc
hid_generic            16384  0
usbhid                 53248  0
hid                   118784  2 hid_generic,usbhid
psmouse               122880  0
ahci                   36864  5
libahci                32768  1 ahci
e1000                 139264  0
pata_acpi              16384  0

printk は何処と結合しているの?

多重に組み込めない

Hello Friend

パラメータを入力する

エラーを返してみよう


トップ   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS