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 が可能です。user space - 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.

モジュールを組み込んだ後にパラメータを入力する

パラメータは /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 だけバッファが確保されます。

fileOpenGrok(linux-4.1.27/fs/sysfs/file.c:238)
Expand allFold all
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
 
 
 
-
|
|
|
|
|
-
|
|
|
|
|
|
|
|
|
-
|
|
|
|
-
|
|
|
|
-
|
|
|
|
!
|
|
|
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
!
|
|
|
|
|
|
|
-
|
|
|
!
|
!
int sysfs_add_file_mode_ns(struct kernfs_node *parent,
                           const struct attribute *attr, bool is_bin,
                           umode_t mode, const void *ns)
{
        struct lock_class_key *key = NULL;
        const struct kernfs_ops *ops;
        struct kernfs_node *kn;
        loff_t size;
 
        if (!is_bin) {
                struct kobject *kobj = parent->priv;
                const struct sysfs_ops *sysfs_ops = kobj->ktype->sysfs_ops;
 
                /* every kobject with an attribute needs a ktype assigned */
                if (WARN(!sysfs_ops, KERN_ERR
                         "missing sysfs attribute operations for kobject: %s\n",
                         kobject_name(kobj)))
                        return -EINVAL;
 
                if (sysfs_ops->show && sysfs_ops->store) {
                        if (mode & SYSFS_PREALLOC)
                                ops = &sysfs_prealloc_kfops_rw;
                        else
                                ops = &sysfs_file_kfops_rw;
                } else if (sysfs_ops->show) {
                        if (mode & SYSFS_PREALLOC)
                                ops = &sysfs_prealloc_kfops_ro;
                        else
                                ops = &sysfs_file_kfops_ro;
                } else if (sysfs_ops->store) {
                        if (mode & SYSFS_PREALLOC)
                                ops = &sysfs_prealloc_kfops_wo;
                        else
                                ops = &sysfs_file_kfops_wo;
                } else
                        ops = &sysfs_file_kfops_empty;
 
                size = PAGE_SIZE;
        } else {
                struct bin_attribute *battr = (void *)attr;
 
                if (battr->mmap)
                        ops = &sysfs_bin_kfops_mmap;
                else if (battr->read && battr->write)
                        ops = &sysfs_bin_kfops_rw;
                else if (battr->read)
                        ops = &sysfs_bin_kfops_ro;
                else if (battr->write)
                        ops = &sysfs_bin_kfops_wo;
                else
                        ops = &sysfs_file_kfops_empty;
 
                size = battr->size;
        }
 
#ifdef CONFIG_DEBUG_LOCK_ALLOC
        if (!attr->ignore_lockdep)
                key = attr->key ?: (struct lock_class_key *)&attr->skey;
#endif
        kn = __kernfs_create_file(parent, attr->name, mode & 0777, size, ops,
                                  (void *)attr, ns, key);
        if (IS_ERR(kn)) {
                if (PTR_ERR(kn) == -EEXIST)
                        sysfs_warn_dup(parent, attr->name);
                return PTR_ERR(kn);
        }
        return 0;
}

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

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

fileOpenGrok(linux-4.1.27/kernel/params.c:272)
Expand allFold all
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
 
-
-
|
|
!
|
|
|
-
!
-
|
|
|
|
!
|
|
|
!
int param_set_charp(const char *val, const struct kernel_param *kp)
{
        if (strlen(val) > 1024) {
                pr_err("%s: string parameter too long\n", kp->name);
                return -ENOSPC;
        }
 
        maybe_kfree_parameter(*(char **)kp->arg);
 
        /* This is a hack.  We can't kmalloc in early boot, and we
         * don't need to; this mangled commandline is preserved. */
        if (slab_is_available()) {
                *(char **)kp->arg = kmalloc_parameter(strlen(val)+1);
                if (!*(char **)kp->arg)
                        return -ENOMEM;
                strcpy(*(char **)kp->arg, val);
        } else
                *(const char **)kp->arg = val;
 
        return 0;
}

ノードを読み出してみます。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 内のエラーの扱い方を見ていきます。関数の結果が 成功 or 失敗(エラー) を示す場合、int 型の戻り値で成功は 0、エラーは負の errno 番号 (-Exxx) を使うというのが作法になります。モジュール外から呼ばれる関数、あるいは他のモジュールの関数を呼び出す場合はこの作法に従う(従っている)と考えて実装・ソースの読解を進めて下さい。

自分が作るモジュール内の作法は?

自分で作るモジュール内は色々な都合で独自の作法でも良いでしょう。ただし、エラーの伝搬過程でややこしい事にならないように注意が必要です。関数の結果が成功か失敗ではなく論理的な true か false の場合は、bool 型もしくは (int)1 or (int)0 を使う所も普通に見られます。どんな関数でも戻り値が 0 か 負の値である必要はありません。

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 番号とその状況について仕様を決めてチーム内で了解しておくと良いでしょう。

fileOpenGrok(linux-4.1.27/include/uapi/asm-generic/errno-base.h:1)
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
 37
 38
 39
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
#ifndef _ASM_GENERIC_ERRNO_BASE_H
#define _ASM_GENERIC_ERRNO_BASE_H
 
#define EPERM            1      /* Operation not permitted */
#define ENOENT           2      /* No such file or directory */
#define ESRCH            3      /* No such process */
#define EINTR            4      /* Interrupted system call */
#define EIO              5      /* I/O error */
#define ENXIO            6      /* No such device or address */
#define E2BIG            7      /* Argument list too long */
#define ENOEXEC          8      /* Exec format error */
#define EBADF            9      /* Bad file number */
#define ECHILD          10      /* No child processes */
#define EAGAIN          11      /* Try again */
#define ENOMEM          12      /* Out of memory */
#define EACCES          13      /* Permission denied */
#define EFAULT          14      /* Bad address */
#define ENOTBLK         15      /* Block device required */
#define EBUSY           16      /* Device or resource busy */
#define EEXIST          17      /* File exists */
#define EXDEV           18      /* Cross-device link */
#define ENODEV          19      /* No such device */
#define ENOTDIR         20      /* Not a directory */
#define EISDIR          21      /* Is a directory */
#define EINVAL          22      /* Invalid argument */
#define ENFILE          23      /* File table overflow */
#define EMFILE          24      /* Too many open files */
#define ENOTTY          25      /* Not a typewriter */
#define ETXTBSY         26      /* Text file busy */
#define EFBIG           27      /* File too large */
#define ENOSPC          28      /* No space left on device */
#define ESPIPE          29      /* Illegal seek */
#define EROFS           30      /* Read-only file system */
#define EMLINK          31      /* Too many links */
#define EPIPE           32      /* Broken pipe */
#define EDOM            33      /* Math argument out of domain of func */
#define ERANGE          34      /* Math result not representable */
 
#endif

仕様外の正の値を返す

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" を検索します。付近に条件式が書かれている場合が多く、なぜエラーになったのか理解の助けになります。

fileOpenGrok(linux-4.1.27/kernel/module.c:3097)
Expand allFold all
3097
3098
3099
3100
3101
3102
3103
3104
3105
3106
3107
3108
3109
3110
3111
 
 
 
-
|
!
-
|
-
!
|
|
!
 
 
        /* Start the module */
        if (mod->init != NULL)
                ret = do_one_initcall(mod->init);
        if (ret < 0) {
                goto fail_free_freeinit;
        }
        if (ret > 0) {
                pr_warn("%s: '%s'->init suspiciously returned %d, it should "
                        "follow 0/-E convention\n"
                        "%s: loading module anyway...\n",
                        __func__, mod->name, ret, __func__);
                dump_stack();
        }
 
        /* Now it's a first class citizen! */

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 です。user space のプロセス稼働状況が分っていれば、どのプロセスからの呼び出しで問題が起きたのか判明します。Comm はプロセス名の一部です。これも貴重な情報です。

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

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

fileOpenGrok(linux-4.1.27/kernel/printk/printk.c:3067)
Expand allFold all
3067
3068
3069
3070
3071
3072
3073
3074
3075
3076
3077
3078
3079
3080
 
-
|
|
|
|
|
|
|
|
|
|
|
!
void dump_stack_print_info(const char *log_lvl)
{
        printk("%sCPU: %d PID: %d Comm: %.20s %s %s %.*s\n",
               log_lvl, raw_smp_processor_id(), current->pid, current->comm,
               print_tainted(), init_utsname()->release,
               (int)strcspn(init_utsname()->version, " "),
               init_utsname()->version);
 
        if (dump_stack_arch_desc_str[0] != '\0')
                printk("%sHardware name: %s\n",
                       log_lvl, dump_stack_arch_desc_str);
 
        print_worker_info(log_lvl, current);
}

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

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

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

先に結論から示します。param_set_charp() 関数内部で 1024 文字(NUL 終端を含めれば 1025 バイト) に制限されています。ここまで追ってみましょう。

module_param() を出発点にします。マクロは次々と module_param_named(), module_param_cb() に展開されることが分ります。module_param_named() の中で param_ops_##type と言う記述に注目します。このことから、何処かに param_ops_charpparam_ops_int が有るのでしょうか?

fileOpenGrok(linux-4.1.27/include/linux/moduleparam.h:144)
Expand allFold all
144
145
146
147
 
 
 
 
#define module_param_named(name, value, type, perm)                        \
        param_check_##type(name, &(value));                                \
        module_param_cb(name, &param_ops_##type, &value, perm);            \
        __MODULE_PARM_TYPE(name, #type)

幸いなことに param_ops_charp は見つかりました。param_ops_charp のメンバ初期値に param_set_charp() が見つかります。

param_ops_int は見つかりません。気になるなら、探し方を変えて param_ops_int を追跡しましょう。リンクは OpenGrok の symbol フィールドで param_ops_int を探すようになっています。include/linux/moduleparam.h で external 宣言されています。

fileOpenGrok(linux-4.1.27/include/linux/moduleparam.h:394)
Expand allFold all
394
395
396
397
398
399
400
401
402
403
404
 
 
 
 
 
 
 
 
 
 
 
extern struct kernel_param_ops param_ops_int;
extern int param_set_int(const char *val, const struct kernel_param *kp);
extern int param_get_int(char *buffer, const struct kernel_param *kp);
#define param_check_int(name, p) __param_check(name, p, int)
 
extern struct kernel_param_ops param_ops_uint;
extern int param_set_uint(const char *val, const struct kernel_param *kp);
extern int param_get_uint(char *buffer, const struct kernel_param *kp);
#define param_check_uint(name, p) __param_check(name, p, unsigned int)
 
extern struct kernel_param_ops param_ops_long;

何処かにシンボルを定義する記述があるはずです。param_ops_charpkernel/params.c の中で見つかりました。このファイルの全体を読んでみます。記述量が多い STANDARD_PARAM_DEF() マクロ が見つかります。マクロの ## 演算子を使って巧妙なテンプレートを構成しています。この中に struct kernel_param_ops param_ops_##name いう記述が見つかります。ここで param_ops_int はグローバル変数として確保されています。

fileOpenGrok(linux-4.1.27/kernel/params.c:244)
Expand allFold all
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
 
 
-
|
!
 
-
|
|
!
-
|
|
!
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
#define STANDARD_PARAM_DEF(name, type, format, strtolfn)                \
        int param_set_##name(const char *val, const struct kernel_param *kp) \
        {                                                               \
                return strtolfn(val, 0, (type *)kp->arg);               \
        }                                                               \
        int param_get_##name(char *buffer, const struct kernel_param *kp) \
        {                                                               \
                return scnprintf(buffer, PAGE_SIZE, format,             \
                                *((type *)kp->arg));                    \
        }                                                               \
        struct kernel_param_ops param_ops_##name = {                    \
                .set = param_set_##name,                                \
                .get = param_get_##name,                                \
        };                                                              \
        EXPORT_SYMBOL(param_set_##name);                                \
        EXPORT_SYMBOL(param_get_##name);                                \
        EXPORT_SYMBOL(param_ops_##name)
 
 
STANDARD_PARAM_DEF(byte, unsigned char, "%hhu", kstrtou8);
STANDARD_PARAM_DEF(short, short, "%hi", kstrtos16);
STANDARD_PARAM_DEF(ushort, unsigned short, "%hu", kstrtou16);
STANDARD_PARAM_DEF(int, int, "%i", kstrtoint);
STANDARD_PARAM_DEF(uint, unsigned int, "%u", kstrtouint);
STANDARD_PARAM_DEF(long, long, "%li", kstrtol);
STANDARD_PARAM_DEF(ulong, unsigned long, "%lu", kstrtoul);
STANDARD_PARAM_DEF(ullong, unsigned long long, "%llu", kstrtoull);
 
int param_set_charp(const char *val, const struct kernel_param *kp)

ここにたどり着くのに kernel_param_ops 構造体が使われている場所を探す筋もあります。あるいは構築して得られた .o ファイルを grep で探す筋も有りでしょう。

改めて見つかった結果をよく見ると、kernel_param_ops 構造体は get, set メソッド(関数ポインタ)をメンバとして持っています。 param_get_##name, param_set_##name です。include/linux/moduleparam.hの中でこのパターンを持つ関数群が external 宣言されているのも確認できます。文字列のモジュール・パラメータを扱う関数が param_get_charp()param_set_charp() だということが分りました。

マクロを使った巧妙なテンプレートを使っていると思われる場合、関数を追いかけるため param_ops_, param_get_, param_set_ の様に短縮してみて探すのも手です。

param_get_charp()param_set_charp() は user space の system call と次のように対応しています。他のメソッド名も合わせて示します。

メソッド名接尾辞または一部system call との対応
_getread()
_setwrite()
_readread()
_writewrite()

▼ここからちょっと脱線

Linux kernel の中では _get, _put の組み合わせもよく使われます。この組は参照カウンタを操作する組です。

メソッド名接尾辞または一部機能
_get参照カウンタを増やす。
_put参照カウンタを減らす。カウンタが 0 になったら _release を呼び出す(あるいは相当する解放処理を実行する)。

▲ここまでちょっと脱線

param_set_charp()の実装を見ていきます。関数の入り口で (strlen(val) > 1024) が成立した場合、-ENOSPCでエラーになるように作られています。strlen(ノードに書き込んだ文字列) <= 1024 で制限されていることが分ります。

fileOpenGrok(linux-4.1.27/kernel/params.c:272)
Expand allFold all
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
 
-
-
|
|
!
|
|
|
-
!
-
|
|
|
|
!
|
|
|
!
int param_set_charp(const char *val, const struct kernel_param *kp)
{
        if (strlen(val) > 1024) {
                pr_err("%s: string parameter too long\n", kp->name);
                return -ENOSPC;
        }
 
        maybe_kfree_parameter(*(char **)kp->arg);
 
        /* This is a hack.  We can't kmalloc in early boot, and we
         * don't need to; this mangled commandline is preserved. */
        if (slab_is_available()) {
                *(char **)kp->arg = kmalloc_parameter(strlen(val)+1);
                if (!*(char **)kp->arg)
                        return -ENOMEM;
                strcpy(*(char **)kp->arg, val);
        } else
                *(const char **)kp->arg = val;
 
        return 0;
}

次に興味深いのは maybe_kfree_parameter(*(char **)kp->arg); と言う処理です。list_for_each_entry() というマクロによってリストkmalloced_paramsを全て走査し、一致する entry があれば、kfree() で解放して、list_del()でリストより削除しています。リストに追加する (list_add() する)側は kmalloc_parameter() です。この関数も param_set_charp() より呼ばれます。これらの実装より、module_param() に初期値として与えられた静的な領域は解放しない様になっています。

fileOpenGrok(linux-4.1.27/kernel/params.c:51)
Expand allFold all
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 
-
|
|
-
-
|
|
|
!
!
!
static void maybe_kfree_parameter(void *param)
{
        struct kmalloced_param *p;
 
        list_for_each_entry(p, &kmalloced_params, list) {
                if (p->val == param) {
                        list_del(&p->list);
                        kfree(p);
                        break;
                }
        }
}
fileOpenGrok(linux-4.1.27/kernel/params.c:38)
Expand allFold all
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 
-
|
|
|
|
|
|
|
|
!
static void *kmalloc_parameter(unsigned int size)
{
        struct kmalloced_param *p;
 
        p = kmalloc(sizeof(*p) + size, GFP_KERNEL);
        if (!p)
                return NULL;
 
        list_add(&p->list, &kmalloced_params);
        return p->val;
}

リスト操作は別のページで扱う予定です

リスト操作は排他制御を伴うのが普通です。一見した限りでは maybe_kfree_parameter()kmalloc_parameter() には排他制御が見当たりません。大丈夫なのか「param_set_charp の排他制御は大丈夫?」探ってみました。

slab_is_available() とは何でしょうか?モジュールを kernel に静的に結合すればモジュール・パラメータは kernel の boot command line からも与えることができます。一部の kernel の起動処理はメモリ・アロケータが初期化されていない状態で進みます。その特殊な状態を判断するための関数です。start_kernel(), parse_args(), parse_one(), param_sysfs_builtin() 辺りのコードが関係します。

kernel boot command line の module パラメータ書式

次の書式で kernel の boot command line (あるいは kernel parameter、boot parameter とも呼ばれている) にモジュール・パラメータを含めることができます。

module.parameter_name=value

printk で構築できる文字列の長さ

printk で構築できる文字列の長さは vprintk_emit() で 991 (== 1024 - 32 - 1) 文字に制限されています。kernel/printk/printk.c の中で LOG_LINE_MAXPREFIX_MAX でマクロ定義された値に由来します。実装部分 (.c) で定義された値です。他から参照することはできません。

fileOpenGrok(linux-4.1.27/kernel/printk/printk.c:257)
Expand allFold all
257
258
 
 
#define PREFIX_MAX              32
#define LOG_LINE_MAX            (1024 - PREFIX_MAX)

printk() から追いかけると printk_func, vprintk_default(), vprintk_emit(), vscnprintf() 引数に指定したバッファサイズ LOG_LINE_MAX まで辿れます。

fileOpenGrok(linux-4.1.27/kernel/printk/printk.c:1610)
Expand allFold all
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
 
 
 
-
|
|
|
|
|
|
|
|
|
|
|
|
-
|
|
!
asmlinkage int vprintk_emit(int facility, int level,
                            const char *dict, size_t dictlen,
                            const char *fmt, va_list args)
{
        static int recursion_bug;
        static char textbuf[LOG_LINE_MAX];
        char *text = textbuf;
        size_t text_len = 0;
        enum log_flags lflags = 0;
        unsigned long flags;
        int this_cpu;
        int printed_len = 0;
        bool in_sched = false;
        /* cpu currently holding logbuf_lock in this function */
        static unsigned int logbuf_cpu = UINT_MAX;
 
        if (level == LOGLEVEL_SCHED) {
                level = LOGLEVEL_DEFAULT;
                in_sched = true;
        }

次のコードは vprintk_emit() の中ほどです。

fileOpenGrok(linux-4.1.27/kernel/printk/printk.c:1669)
Expand allFold all
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
 
!
|
-
 
 
!
|
|
|
-
 
 
!
                                         strlen(recursion_msg));
        }
 
        /*
         * The printf needs to come first; we need the syslog
         * prefix which might be passed-in as a parameter.
         */
        text_len = vscnprintf(text, sizeof(textbuf), fmt, args);
 
        /* mark and strip a trailing newline */
        if (text_len && text[text_len-1] == '\n') {
                text_len--;
                lflags |= LOG_NEWLINE;
        }

このページのサンプルでは幅指定が無い %s 書式を使いました。vprintk_emit() にて制限されていることを前提としています。用心をするならば %s に幅指定をして下さい。printk に含める他の文字列の分を差し引いて、最大で 800 ~ 960 文字程度にします。x64 アーキテクチャのスタック・サイズ Documentation/x86/x86_64/kernel-stacks に有るように kernel の中はスタック・サイズが小さいです。x64 アーキテクチャにて 8kibyte (== THREAD_SIZE == PAGE_SIZE * 2) です。kernel 内の stack overflow をより慎重に避けるならば、数 10 文字程度に制限し、思わぬ攻撃を避けて下さい。

次はよりデバイスドライバらしく

ここまでは、単純なモジュールの作り方を見てきました。次は少しだけデバイスドライバらしくする予定です。


添付ファイル: filehello_world.c 237件 [詳細]

トップ   編集 凍結 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2017-11-03 (金) 22:05:48 (2368d)