Hello You

先の hello_world.c を修正して、課題だったパラメータを入力する、エラーコードを返す実装をしてみましょう。make 方法と Makefile は hello_world.c と変わらないので省略します。コンパイルが済んだと言う前提でこのページを書きます。

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
 28
 29
 30
 31
 32
 33
 34
 35
 36
 
 
 
 
 
 
 
 
 
 
 
 
-
|
|
|
!
 
-
|
!
 
-
|
|
!
 
-
!
 
 
 
 
 
 
 
#include <linux/kernel.h>
#include <linux/printk.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/stat.h>
 
static char *your_name = "Taro";
module_param(your_name, charp, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
 
static int return_value = 0;
module_param(return_value, int, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
 
/*
 * 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 %s san. return_value=%d\n", __func__, your_name, return_value);
        return return_value;
}
 
/*
 * Tutorial hello world module exit.
 * @return void
 */
static void __exit hello_world_exit(void)
{       printk(KERN_INFO "%s: Goodbye %s san.\n", __func__, your_name);
}
 
module_init(hello_world_init);
module_exit(hello_world_exit);
 
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Tutorial hello you.");
MODULE_AUTHOR("Your name or email address.");

パラメータを受け取る手段

モジュールはパラメータを受け取ることができます。主な手段は module_param() マクロ, DEVICE_ATTR マクロ, debugfs, procfs です。ここでは module_param() を試します。次の表にそれぞれの説明をまとめておきます。パラメータ引き渡し方法にファイル・システム名が書かれている場合は、そのファイル・システム上に作られたノードを通して設定します。設定単位はノードと設定対象の対応です。.config は linux-stable ディレクトリ(ソース・コードの基底ディレクトリ)にある .config に書かれた構築条件です。=Y になっている場合に利用可能です。

手段パラメータ引き渡し方法設定単位.configドキュメント、特徴
module_paraminsmod or modprobe command line,
boot parameter,
sysfs node
モジュール毎CONFIG_SYSFSもっとも簡単です。変数名とパラメータ名を独立に指定できる module_param_named()、Call Back を実装できる module_param_cb() があります。
ノードは /sys/module/module_name/parameters/nameにできます。
DEVICE_ATTRsysfs nodeデバイス毎CONFIG_SYSFSドキュメントは Documentation/driver-model/device.txt, Documentation/filesystems/sysfs.txt に有ります。デバイス毎にパラメータを設定できます。デバイス毎ですので、モジュール・ロード時にデバイスが無い場合はパラメータを設定できません。実装か必要なコード量は多いです。
ノードは /sys/devices/subsystem_base/subsystem/* にできます。バスやクラス構造的に追いやすい /sys/bus/* 以下、/sys/class/* 以下、/sys/block/* 以下からもシンボリックリンクを通じてアクセスできます。
debugfsdebugfs node任意構造CONFIG_DEBUG_FSドキュメントは debugfs にあります。主に使う関数は debugfs_create_dir(), debugfs_create_file(), debugfs_remove(), debugfs_remove_recursive() です。整数を扱う単純なノードであればドキュメントあるいは include/linux/debugfs.h から使用例を探して使うのが良いでしょう。
普通の Linux の構成であれば debugfs は /sys/kernel/debug に mount されます。あるいは mount して使うことができます。
procfsprocfs node任意構造CONFIG_PROC_FSprocfs を中心にした使い方のドキュメントは有りません。include/linux/proc_fs.h より辿れる関数と Documentation/filesystems/seq_file.txt に使用例があるので参考にしてください。proc_create(), proc_create_data(), proc_mkdir(), remove_proc_entry() などの関数とマクロの使われ方を参考に使用して下さい。モジュールロード時にパラメータとして指定できません。module_init() で指定した関数内でノードを作成し、ロード直後にパラメータを設定することができます。
procfs は /proc に mount されます。

sysfs, procfs, debugfs は必ずあるのか?

sysfs, procfs, debugfs は .config の記述により有無を選択できます。このうち sysfs, procfs とも必ず存在すると仮定しても良いでしょう。sysfs または procfs を無くすと相当に不便です。system call に匹敵する機能を分担しています。
debufs はディストリビューションや評価環境によっては「無し」に設定されていることが有ります。debugfs で提供されるノードの機能は開発時は便利です。しかし、製品としてリリースできないようなセキュリティ・ホール、コンテンツ保護違反、NDA(機密保持契約)違反、動作が不安定になる機能が実装される傾向があります。debugfs (あるいはその一部ノード)を製品リリースに含めるかどうか精査した方が良いでしょう。

いずれの方法もノードができます。ノードは属性の設定次第で read/write が可能です。userland - kernel 双方向で更新して情報交換できます(次の注意参照)。

module_param を pchar 型パラメータで使う場合の注意

module_param を pchar 型(文字列)で扱う場合は、バッファ確保動作に注意して下さい。ユーザーがノードに書き込む場合は param_set_charp() 読み出す場合は param_get_charp() が動作します。 param_set_charp() の内部で動的にメモリ確保が行われます。巧妙な kmalloc_parameter()maybe_kfree_parameter() の動作により初期値として指していたメモリ領域は誤って解放されないようになっています。一度でもユーザーがノードを経由して書き込んだ場合、動的にバッファは確保され、そのサイズは書き込んだ文字列を strlen() で測った長さ + 1 (NUL 終端された文字列を格納するのに必要十分な長さ) となります。十分なバッファが確保されていない可能性が有ります。Kernel 内から module_param 経由で文字列を返す場合ノードの属性を read only にして、初期文字列(バッファ)を使用し続ける様にするか、module_param_cb() を使用することを検討して下さい。

パラメータを入力する

まず、パラメータ無しで insmod, rmmod を試します。ソース・コードに書かれたデフォルト値で動作しているのがわかります。

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

dmesg が出力した最後は次のようになります。your_name と return_value の値がそのまま表示されました。

[355028.315777] hello_world: module verification failed: signature and/or required key missing - tainting kernel
[355028.319261] hello_world_init: Hello Taro san. return_value=0
[355036.306520] hello_world_exit: Goodbye Taro san.

パラメータを付けて起動しましょう。your_name=Hanako とします。

$ sudo insmod hello_world.ko your_name=Hanako
$ sudo rmmod hello_world.ko
$ dmesg

dmesg が出力した最後は次のようになります。your_name に指定した Hanako が表示されました。アプリケーション・プログラムと仕組みは違いますがパラメータを受け取る手段が有ります。

[355218.292794] hello_world_init: Hello Hanako san. return_value=0
[355236.027700] hello_world_exit: Goodbye Hanako san.

Kernel に組み込んだ後、パラメータを変えてみます。パラメータは /sys/module/hello_world/parameters の下にノードとして存在します。return_value と your_name のつのパラメータを作ったのでノードは 2 つできています。パーミッションは module_param の引数で指定したとおりになっています。

$ sudo insmod hello_world.ko
$ ls -la /sys/module/hello_world/parameters
total 0
drwxr-xr-x 2 root root    0  8月 22 10:34 .
drwxr-xr-x 6 root root    0  8月 22 10:34 ..
-rw-r--r-- 1 root root 4096  8月 22 10:34 return_value
-rw-r--r-- 1 root root 4096  8月 22 10:34 your_name

ノードのサイズが 4096 です。理由を探ってみましょう。sysfs_add_file_mode_ns() に size = PAGE_SIZE; と書いてあるところから由来します。binary data を直接扱わないノードであれば、4096 に固定されます。書き込みは最大で PAGE_SIZE バイトだけできます。内部では kernfs_fop_write() の実装により PAGE_SIZE + 1 だけバッファが確保されます。

module_param を charp 型で使う場合の長さ制限

module_param() を charp 型で使う場合、書き込みの長さは param_set_charp() の実装により終端 NUL を含まない文字列の長さは 1024 バイトに制限されます。

ノードを読み出してみます。ls -la (stat) で報告された長さではなく、格納されている内容に合ったサイズで読み出せます。ノードからの読み出しに高水準ファイル IO ライブラリを使う場合、stat で報告されたファイルサイズと実際に読めるサイズの食い違いがあっても問題なく動作するか確認しておくと良いでしょう。

$ cat /sys/module/hello_world/parameters/return_value
0
$ cat /sys/module/hello_world/parameters/your_name
Taro
$ cat /sys/module/hello_world/parameters/return_value | od -A x -t x1
000000 30 0a
000002
$ cat /sys/module/hello_world/parameters/your_name | od -A x -t x1
000000 54 61 72 6f 0a
000005

your_name ノードに書き込んでみます。その後、rmmod してみます。モジュールがロードされている間にノードを書き換えた結果が反映されています。

$ sudo sh -c "echo -n Hanako > /sys/module/hello_world/parameters/your_name"
$ sudo rmmod hello_world
$ dmesg
...省略...
[355334.490992] hello_world_init: Hello Taro san. return_value=0
[355455.780006] hello_world_exit: Goodbye Hanako san.

エラーを返してみよう

Linux Kernel 内の作法通り負の errno 値 (-Exxx) を返す

module_init() で指定した関数からエラーを返すとどうなるか試してみます。insmod の引数に return_value=-22 を追加します。-22 は -EINVAL です。EINVAL は /usr/include/asm-generic/errno-base.h または include/uapi/asm-generic/errno-base.h でマクロ定義されています。insmod が Invalid parameters エラーメッセージを表示します。

$ sudo insmod hello_world.ko return_value=-22
insmod: ERROR: could not insert module hello_world.ko: Invalid parameters
$ dmesg
...省略...
[790263.729065] hello_world_init: Hello Taro san. return_value=-22
$ sudo insmod hello_world.ko return_value=-22
insmod: ERROR: could not insert module hello_world.ko: Invalid parameters
$ dmesg
...省略...
[790263.729065] hello_world_init: Hello Taro san. return_value=-22
[790515.608190] hello_world_init: Hello Taro san. return_value=-22

何回試しても、同じようにエラーになります。insmod が失敗し、hello_world.ko モジュールは組み込まれません。Linux Kernel 内でエラーを関数の戻り値で伝搬する場合、負の errno 番号 (-Exxx) を返します。成功した場合は 0 を返します。

エラーが発生した場合、状況に合わせて include/uapi/asm-generic/errno-base.h または include/uapi/asm-generic/errno.h に定義されたマクロから値を 1 つ選び、負の値にして返します。値の選び方は errno-base.h で定義されている中から、状況に合っていそうな値を探します。どうしても見つからない場合は errno.h から選びます。チームで開発している場合は、errno 番号とその状況について仕様を決めてチーム内で了解しておくと良いでしょう。

仕様外の正の値を返す

module_init() で指定した関数から仕様外の正の値を返すとどうなるか試してみます。insmod の引数に return_value=22 を追加します。insmod コマンドは何も表示しないので成功しています。hello_world.ko は Kernel に組み込まれます。将来もこの挙動になるかというと、保証は無いでしょう。

$ sudo insmod hello_world.ko return_value=22
$ dmesg
...省略...
[792581.467679] hello_world_init: Hello Taro san. return_value=22
[792581.467700] do_init_module: 'hello_world'->init suspiciously returned 22, it should follow 0/-E convention
[792581.467700] do_init_module: loading module anyway...
[792581.467740] CPU: 1 PID: 15413 Comm: insmod Tainted: G           OE   4.1.27-local #1
[792581.467766] Hardware name: innotek GmbH VirtualBox/VirtualBox, BIOS VirtualBox 12/01/2006
[792581.467779]  0000000000000000 ffff8800776b7d18 ffffffff817d0d19 ffffffffc0148020
[792581.467793]  ffff8800048258c0 ffff8800776b7d48 ffffffff817cd175 00000000008d4ecc
[792581.467803]  ffff8800776b7eb0 0000000000000001 ffffffffc0148070 ffff8800776b7e98
[792581.467813] Call Trace:
[792581.467839]  [<ffffffff817d0d19>] dump_stack+0x63/0x81
[792581.467851]  [<ffffffff817cd175>] do_init_module+0x91/0x1b2
[792581.467867]  [<ffffffff811026bc>] load_module+0x1eec/0x2740
[792581.467916]  [<ffffffff810fe260>] ? store_uevent+0x40/0x40
[792581.467937]  [<ffffffff811030ee>] SYSC_finit_module+0x7e/0xa0
[792581.467950]  [<ffffffff8110312e>] SyS_finit_module+0xe/0x10
[792581.467963]  [<ffffffff817d8b72>] system_call_fastpath+0x16/0x75

dmesg 出力を見るとスタック・ダンプ (stack dump) が見つかります。今までは訳が分らない出力だと思っていたかもしれません。これからは貴重な情報として見る必要が有ります。Call Trace の一番始めの行に dump_stack() 関数が有ります。これは、do_init_module() から呼ばれたスタックの内容を表示する関数です。メッセージに dump_stack() の呼び出し元が表示されない場合は、メッセージの文字列を検索してソースの場所を特定します。この例では "init suspiciously returned" や "loading module anyway" を検索します。付近に条件式が書かれている場合が多く、なぜエラーになったのか理解の助けになります。

ogdefs(dump_stack(),dump_stack); は割り込みが遅延するなどの時間的問題を除いて、Kernel の動作に影響を与えません。万が一、dump_stack() が何かの問題でスタックをトレースできずにハングアップしてしまった場合はスタックが破壊されています。

dump_stack() を呼ぶまでにどのような関数 call を経て実行されたかを追跡できます。呼ばれた逆順で do_init_module(), load_module(), SYSC_finit_module(), system_call_fastpath() を通過したことが判ります。途中の分岐条件などから、変数の値を推測しおおよその状況を把握できます。

SyS_finit_module() の追跡は省略します

SYSCALL_DEFINEx() マクロ__SYSCALL_DEFINEx() マクロ の内容と使われ方を参考にして下さい。

インライン展開された関数はスタック・ダンプに現れません

gcc の最適化により関数がインライン展開されることがあります。この場合スタック・ダンプに関数は表示されません。関数呼び出しの入れ子を飛ばしてしまった様に見えるかもしれません。50 行程度の実装量がある関数もインライン展開されることがあります。

始めの方に表示される "CPU: 1 PID: 15413 Comm: insmod " も貴重な情報です。PID は process id です。ユーザー・ランドのプロセス稼働状況が分っていれば、どのプロセスからの呼び出しで問題が起きたのか判明します。Comm はプロセス名の一部です。これも貴重な情報です。

割り込み処理の場合は PID と Comm は割り込み処理が「乗った」プロセスです

割り込み処理内でも dump_stack() は使えます。この場合 PID と Comm は割り込み処理が「乗った」プロセスを表示します。「乗った」を詳細に言い換えます。割り込みが発生すると、実行中のプロセス・コンテキストをそのまま使い、割り込み処理を始めると言うことです。割り込みを起こしたデバイスに全く無関係なプロセスのコンテキストで割り込み処理が実行されることも普通に有ります。

バッファ・オーバーランが心配

グローバル変数 your_name 周辺の実装を見てバッファ・オーバーランが起きないのだろうか?と直感するでしょう。module_param 周りでかなり巧妙な実装がされています。Linux Kernel のソース・コードが追いにくい面も触れます。見ていくことにします。

module_param charp で扱える文字列の長さ

printk で扱える文字列の長さ


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