param_set_charp の排他制御は大丈夫?

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

dump_stack() を使って追跡する

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

Expand allFold all
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 
 
 
 
 
 
 
 
 
!
|
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() しているいるのが見つかりました。リスト操作が平行処理(あるいは複数プロセッサの並列処理)で乱されないと確認できます。

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

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


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