このページは linux-4.1.27 について書いたものです。

param_set_charp の排他制御は大丈夫?

param_set_charp()から呼び出している maybe_kfree_parameter()kmalloc_parameter() の中でリスト操作をしています。リスト操作は list_add(), list_del(), list_for_each_entry() です。これらは基本的な双方向リンク・チェインの操作です。user space の複数の process または thread から write() system call は平行して使うことができます。何処かで排他制御をして、リンク・リスト操作を一貫して行う必要が有ります。排他制御がされているのか追ってみましょう。

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;
}

dump_stack() を使って追跡する

dump_stack() を使って追跡することにします。param_set_charp() が呼ばれるまでの Call 経路は構造体に格納された関数を指すポインタを使って決まっています。追跡難度か高いです。kernel/params.c を次のように修正します(file修正済みファイル)。kernel を make、インストールして様子を探ることにします。

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
diff --git a/kernel/params.c b/kernel/params.c
index a22d6a75..9a1fe5a 100644
--- a/kernel/params.c
+++ b/kernel/params.c
@@ -285,6 +285,7 @@ int param_set_charp(const char *val, const struct kernel_param *kp)
                if (!*(char **)kp->arg)
                        return -ENOMEM;
                strcpy(*(char **)kp->arg, val);
+               dump_stack();
        } else
                *(const char **)kp->arg = val;
 

dump_stack()を通過する頻度に注意

ここでは dump_stack() を無条件に呼ぶように実装しました。通過頻度が少ないと見込んだためです。高頻度に通過する場合、過剰なログ出力で Linux が起動するまでに数分以上の長時間を要したり、起動したとして応答が悪くコマンド投入が困難な状況になります。予め高頻度に通過する場所だと予想されるならば、dump_stack() を条件付きで呼び出すように組み込んで下さい。

module_param を実装した hello_world モジュールを使い call trace を得ます。

[  212.911142] hello_world: module verification failed: signature and/or required key missing - tainting kernel
[  212.913390] hello_world_init: Hello Taro san. return_value=0
[  241.250795] CPU: 0 PID: 2252 Comm: bash Tainted: G           OE   4.1.27-local+ #3
[  241.250819] Hardware name: innotek GmbH VirtualBox/VirtualBox, BIOS VirtualBox 12/01/2006
[  241.250834]  0000000000000000 ffff88007bac3d58 ffffffff817d0d29 ffffffffc025a0d8
[  241.250852]  ffff88007b781988 ffff88007bac3d88 ffffffff8109943b ffff88007bac3d88
[  241.250867]  ffff88005cd62ea8 0000000000000006 ffff88007b781988 ffff88007bac3db8
[  241.250881] Call Trace:
[  241.250915]  [<ffffffff817d0d29>] dump_stack+0x63/0x81
[  241.250939]  [<ffffffff8109943b>] param_set_charp+0xdb/0xf0
[  241.250957]  [<ffffffff8109988d>] param_attr_store+0x4d/0xb0
[  241.251015]  [<ffffffff81275072>] ? kernfs_fop_write+0xf2/0x180
[  241.251033]  [<ffffffff81098cad>] module_attr_store+0x1d/0x30
[  241.251047]  [<ffffffff81275bfd>] sysfs_kf_write+0x3d/0x50
[  241.251063]  [<ffffffff812750aa>] kernfs_fop_write+0x12a/0x180
[  241.251082]  [<ffffffff811fa198>] __vfs_write+0x28/0xf0
[  241.251099]  [<ffffffff811fcda9>] ? __sb_start_write+0x49/0xf0
[  241.251118]  [<ffffffff81320373>] ? security_file_permission+0x23/0xa0
[  241.251133]  [<ffffffff811fa889>] vfs_write+0xa9/0x1b0
[  241.251148]  [<ffffffff811fb656>] SyS_write+0x46/0xb0
[  241.251166]  [<ffffffff81068080>] ? do_page_fault+0x30/0x80
[  241.251227]  [<ffffffff817d8b72>] system_call_fastpath+0x16/0x75

vfs_write() が Linux kernel の仮想ファイルシステム(VFS) レイヤです。vfs_write() の中では write() システムコールを平行して扱えます。呼ばれた順に関数を並べると __vfs_write(), kernfs_fop_write(), sysfs_kf_write(), module_attr_store(), param_attr_store(), param_set_charp() となります。 これらの中から効果がある排他制御を探します。

グローバルな mutex param_lock

param_set_charp() から逆にたどって排他制御を探します。方針として、だんだんと粒度が大きくなる方向に辿ります。param_attr_store()DEFINE_MUTEX() で宣言されたグローバルな param_lock という mutex を使って排他制御 mutex_lock(), mutex_unlock() しているいるのが見つかりました。リスト操作が平行処理(あるいは複数プロセッサの並列処理)で乱されないと確認できます。

fileOpenGrok(linux-4.1.27/kernel/params.c:29)
Expand allFold all
 29
 30
 31
 32
 33
 34
 35
 
 
 
-
|
|
!
static DEFINE_MUTEX(param_lock);
 
/* This just allows us to keep track of which parameters are kmalloced. */
struct kmalloced_param {
        struct list_head list;
        char val[];
};
fileOpenGrok(linux-4.1.27/kernel/params.c:553)
Expand allFold all
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
 
 
 
-
|
|
|
|
|
|
|
|
|
|
|
|
|
!
static ssize_t param_attr_store(struct module_attribute *mattr,
                                struct module_kobject *km,
                                const char *buf, size_t len)
{
        int err;
        struct param_attribute *attribute = to_param_attr(mattr);
 
        if (!attribute->param->ops->set)
                return -EPERM;
 
        mutex_lock(&param_lock);
        param_check_unsafe(attribute->param);
        err = attribute->param->ops->set(buf, attribute->param);
        mutex_unlock(&param_lock);
        if (!err)
                return len;
        return err;
}

kernel 4.2.x では排他制御の実装が変わっている

kernel 4.2.x の maybe_kfree_parameter()の実装を見てみると spin_lock(), spin_unlock() を直前・直後に使用した実装に変わっています。Linux kernel の実装は常に大きく変わっている例の一つと言えます。


添付ファイル: fileparams.c 276件 [詳細]

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