my_knowledge.ko

Linux | Debug, Analyze, Trace | Tech | etc...

Magic SysRq Key 調査

はじめに

Magic SysRq Key (以降、SysRqKey と省略) とはシステムの障害時やデバッグ等で利用できる Linux カーネルの機能 です。キーボード上から Alt + SysRq + b を実行することでシステムを再起動させたり、Alt + SysRq + c で意図的にカーネルパニックを発生させたり等、その他にも様々な機能を備えています。

前々から SysRqKey に興味があった (勉強したかった) ので、使い方と機能と実装について調査しました。調査は主にv4.19 時点の公式ドキュメント (Linux Magic System Request Key Hacks) とソースコードから行いました。

使い方と機能を知る

まずは、使い方と機能について調査しました。

使い方と機能の要約

  • SysRqKey はコマンドラインとキーボードの両方から使用可能
  • コマンドラインの場合は $ echo [コマンド] > /proc/sysrq-trigger で実行
  • キーボードの場合は Alt + SysRq + [コマンド] で実行
  • SysRqKey で有効な機能は $ echo [制御値] > /proc/sys/kernel/sysrq で制限可能
  • システムフリーズ時になるべく安全に再起動するには 「reisub」を順次実行

基本的な使い方

SysRqKey はコマンドラインとキーボードからそれぞれ利用できます。

  • コマンドラインから利用する場合 ---> $ echo [コマンド] > /proc/sysrq-trigger
  • キーボードから利用する場合 ---> Alt + SysRq + [コマンド]

[コマンド] には後述の 「1-3. 機能(コマンド)一覧」 に記載のコマンド(半角英数字)が入ります。この部分を変更することで様々な機能が利用できます。

ただし、SysRqKey を使うには、前提として CONFIG_MAGIC_SYSRQ が有効 (y) である必要があります。この設定は /boot/config-<カーネルバージョン> から確認できます。私の環境ではデフォルトで有効になっていました。

機能(コマンド)一覧

SysRqKey で使用可能な機能(コマンド)は次の表に記載のとおりです。

なお SysRqKey の実行結果は dmesg コマンドなどから確認できます。

コマンド 挙動
b 即座にシステムを再起動します。その際ディスクの同期(sync)もアンマウントもしません。
c NULL ポインタデリファレンスによりシステムをクラッシュさせます。
d 現在保持している全てのロックを表示します (CONFIG_LOCKDEP 有効時)。
e init プロセス(PID 1)を除く全プロセスへ SIGTERM シグナルを送信し、プロセスを終了させます。
f OOM Killer を呼びメモリを多く消費しているプロセスを強制終了させます。
g kgdb (kernel debugger) によって使用されます。
h ヘルプを表示します。
i init プロセス(PID 1)を除く全プロセスへ SIGKILL シグナルを送信し、プロセスを強制終了させます。
j FIFREEZE ioctlによって凍結されたファイルシステムを解凍します (CONFIG_BLOCK 有効時)。
k 仮想端末上の全プロセスに対して Secure Access Key (SAK) を実行し、終了させます (CONFIG_VT 有効時)。
l 全てのアクティブな CPU のスタックバックトレースを表示します (CONFIG_SMP 有効時)。
m 現在のメモリ情報をコンソールに表示します。
n 優先度の高いリアルタイムタスクの nice レベルをリセットします。
o システムをシャットダウンさせます。
p 現在のレジスタおよびフラグをコンソールに表示します。
q 全ての高精度タイマおよびクロックソースを表示します。
r キーボードの raw モードを無効にして XLATE モードに設定します (CONFIG_VT 有効時)。
s マウントされている全てのファイルシステムを同期(sync)しようとします。
t 現在のタスクとそれらの情報の一覧を表示します。
u 全てのマウントされたファイルシステムを読み取り専用で再マウントしようとします。
v フレームバッファコンソールを強制的に復元します。
v [ARM 固有] ETM バッファ表示します。
w 割り込み不可能なスリープ状態 (D ステート) のタスクを表示します。
x ppc/powerpc プラットフォームの xmon インタフェースによって使用されます。sparc 64 でグローバル PMU レジスタを表示します。MIPS の全ての TLB エントリを表示します。
y [SPARC-64 固有] グローバル CPU レジスタを表示します。
z ftrace バッファを表示します (CONFIG_TRACING 有効時)。
0 - 9 どのログレベルでカーネルのメッセージを表示するかを設定します (例えば、0 なら PANIC や OOPS のような緊急度の高いメッセージのみが表示されます)。

有効なコマンドの制御

システムで有効なコマンドは次のように制御できます1

$ echo [制御値] > /proc/sys/kernel/sysrq

[制御値] には次の表に記載の制御値が入ります。たとえば、4 を指定すると kr が有効になります。複数のコマンドを有効にしたい場合は、それらに対応する制御値の合計値を指定します。416 なら 20 です。この場合は kr に加え s が有効になります。

制御値 挙動 対象のコマンド
0 sysrq の全機能を無効にします。 全てのコマンド
1 sysrq の全機能を有効にします。 全てのコマンド
2 コンソールのログレベルの制御を有効にします。 0 - 9
4 キーボードの制御を有効にします。 kr
8 プロセスなどのデバッグダンプを有効にします。 clptwzm
16 同期 (sync) コマンドを有効にします。 s
32 読み取り専用で再マウントするコマンドを有効にします。 u
64 プロセスのシグナル送信を有効にします。 efji
128 再起動/電源オフを許可します。 b
256 全ての RT タスクの命名を許可します。 n

動作確認

使い方と機能については以上です。ここからは、動作確認です。

有効なコマンドの確認

まずは有効なコマンドを確認します。

私の環境では /proc/sys/kernel/sysrq16 がデフォルトで設定されていました。16 で有効なコマンドは s なので、これを実行します。

[root@fdr28 ~]# echo s > /proc/sysrq-trigger
[root@fdr28 ~]# dmesg
...(snip)...
[394887.857272] sysrq: SysRq : Emergency Sync
[394887.860045] Emergency Sync complete

sysrq: SysRq : Emergency SyncEmergency Sync complete が出力されました。前者はコマンド成功時に出力されるコマンド毎のメッセージで、後者はコマンドの実行結果です。今回は s なので出力されるログが少ないのですが、コマンドによっては、大量にログが出力されます。

無効なコマンドの確認

16 で無効なコマンドである m を確認します(無効なコマンドは他にもあります)。

[root@fdr28 ~]# echo m > /proc/sysrq-trigger
[root@fdr28 ~]# dmesg
...(snip)...
[395027.990391] sysrq: SysRq : This sysrq operation is sdisabled.

This sysrq operation is disabled. と出力され無効であることが確認できました。有効なコマンドのときにあったコマンド毎のメッセージも出力されていません。

複数コマンドの有効化の確認

ここでは例として、既存で有効なコマンド s (制御値は 16)に加え、前述の「1-5-2. 無効なコマンドの確認」で無効だったコマンド m (制御値は 8)を有効にします。両方とも有効にするには 8 + 16 24/proc/sys/kernel/sysrq に書き込みます。再起動などの特別な操作は不要です。

[root@fdr28 ~]# echo 24 > /proc/sys/kernel/sysrq

m が有効になっているか確認します。

[root@fdr28 ~]# echo m > /proc/sysrq-trigger
[root@fdr28 ~]# dmesg
...(snip)...
[200590.430782] sysrq: SysRq : Show Memory
[200590.430798] Mem-Info:
[200590.430815] active_anon:581575 inactive_anon:210237 isolated_anon:0
                 active_file:728564 inactive_file:304530 isolated_file:0
                 unevictable:0 dirty:178 writeback:0 unstable:0
                 slab_reclaimable:61448 slab_unreclaimable:33847
                 mapped:103241 shmem:95282 pagetables:16075 bounce:0
                 free:56782 free_pcp:1557 free_cma:0
...(snip)...
[200590.431115] 0 pages hwpoisoned

先ほどとは違い sysrq: SysRq : Show Memory というコマンド毎のメッセージが表示された後、実行結果(メモリの情報)が表示されました。意図したとおりにコマンドが有効であることが確認できました。

使いどころ

一応、SysRqKey の使いどころについても書いておきます。公式ドキュメントによると次のようなケースが使いどころのようです。障害時に役立ちそうな印象があります。

  • X サーバや svgalib プログラムがクラッシュしたとき (r)
  • トロイの木馬プログラムがコンソールで実行されていないことを確認したいとき (k)
  • 何かしらの理由でシャットダウンできないとき (s -> y -> b)
  • システムハング時にカーネルダンプを取得したいとき (c)
  • システムフリーズ時になるべく安全に再起動したいとき (r -> e -> i -> s -> u -> b)2
  • カーネルメッセージのログレベル絞りたい (0 - 9)
  • 暴走しているプロセスを殺したいとき (ei)
  • FIFREEZE ioctl を介してフリーズしたファイルシステムが原因でシステムが応答しなくなったとき (j)

カーネル実装を読む

ここからはソースコードを元に SysRqKey の実装を読んでいきます。

なお、ここでは次のコマンドを実行した際に処理されるコードを元に読み進めていきます。

$ echo s > /proc/sysrq-trigger

処理の全体像

まず $ echo s > /proc/sysrq-trigger を実行した際の関数呼び出しを記載しておきます。今回は SysRqKey への理解を深める目的があるため、あまり関係のないコード (do_syscall_64 から _vfs_write) は読み飛ばし、proc_reg_write 関数から読んでいくことにします。

do_syscall_64
 ksys_write
  vfs_write
    __vfs_write
    proc_reg_write <--- ここから一番下までが読む範囲です。
     write_sysrq_trigger
      __handle_sysrq
       sysrq_handle_sync
        emergency_sync
         do_sync_work

proc_reg_write

この関数は procfs への書き込みをトリガーに呼び出されます。今回は /proc/sysrq-trigger ファイルへ s を書き込んでいるので呼び出されます。

fs/proc/inode.c

237 static ssize_t proc_reg_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
238 {
239         ssize_t (*write)(struct file *, const char __user *, size_t, loff_t *);
240         struct proc_dir_entry *pde = PDE(file_inode(file));
241         ssize_t rv = -EIO;
242         if (use_pde(pde)) {
243                 write = pde->proc_fops->write;
244                 if (write)
245                         rv = write(file, buf, count, ppos);
246                 unuse_pde(pde);
247         }
248         return rv;
249 }

読み進める上で重要な箇所は 243 行目です。ここで sysrq-triggerwrite オペレーションのアドレスを格納しています。sysrq-trigger のオペレーションでは .write = write_sysrq_trigger と定義 (Memo_A) されているため、245 行目では write_sysrq_trigger 関数が呼び出されます。

Memo_A: sysrq-trigger のオペレーションの定義

drivers/tty/sysrq.c

    1118 static const struct file_operations proc_sysrq_trigger_operations = {
    1119         .write          = write_sysrq_trigger,
    1120         .llseek         = noop_llseek,
    1121 };

write_sysrq_trigger

proc_reg_write 関数の 245 行目から呼び出されます。

drivers/tty/sysrq.c

1100 #ifdef CONFIG_PROC_FS
1101 /*
1102  * writing 'C' to /proc/sysrq-trigger is like sysrq-C
1103  */
1104 static ssize_t write_sysrq_trigger(struct file *file, const char __user *buf,
1105                                    size_t count, loff_t *ppos)
1106 {
1107         if (count) {
1108                 char c;
1109 
1110                 if (get_user(c, buf))
1111                         return -EFAULT;
1112                 __handle_sysrq(c, SYSRQ_FROM_PROC);
1113         }
1114 
1115         return count;
1116 }

1110 行目では get_user 関数に変数 cbuf を渡して呼び出しています。調べたところ get_user 関数はユーザ空間からカーネル空間へ変数をコピーするためのもので、第一引数にコピー元の変数、第二引数にコピー元ユーザ空間のアドレスが渡されるようです。

また、1111 行目で返る EFAULT は「アドレスが不正」という意味のエラーのようです。EFAULT の定義元 (Memo_B) に「Bad adress」とコメントされていました。そのため、この関数では変数 buf のアドレスが何かしらの理由で不正な場合は EFAULT エラーが返り、そうでない場合は 1112 行目で __handle_sysrq 関数が呼び出されるのだと思います。

Memo_B: EFAULT の定義 (18 行目)

include/uapi/asm-generic/errno-base.h

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

__handle_sysrq

write_sysrq_trigger の 1112行目から呼び出されます。SysRqKey においてメインな関数だと思います。

drivers/tty/sysrq.c 

534 void __handle_sysrq(int key, unsigned int from)
535 {
536         struct sysrq_key_op *op_p;
537         int orig_log_level;
538         int i;
539 
540         rcu_sysrq_start();
541         rcu_read_lock();
542         /*
543          * Raise the apparent loglevel to maximum so that the sysrq header
544          * is shown to provide the user with positive feedback.  We do not
545          * simply emit this at KERN_EMERG as that would change message
546          * routing in the consumers of /proc/kmsg.
547          */
548         orig_log_level = console_loglevel;
549         console_loglevel = CONSOLE_LOGLEVEL_DEFAULT;
550         pr_info("SysRq : ");
551
552         op_p = __sysrq_get_key_op(key);
553         if (op_p) {
554                 /* Ban synthetic events from some sysrq functionality */
555                 if ((from == SYSRQ_FROM_PROC || from == SYSRQ_FROM_SYNTHETIC) &&
556                     op_p->enable_mask & SYSRQ_DISABLE_USERSPACE)
557                         printk("This sysrq operation is disabled from userspace.\n");

552 行目で指定のコマンド (s) とリンクするオペレーションを取得しているようです。コマンド毎にリンクするオペレーションは次の「Memo_C」のように定義されています。取得したオペレーションが NULL でないコマンドは 554 行目から処理され、h など NULL であるコマンドは後述の 570 行目で処理されます。

Memo_C: コマンド毎のオペレーション定義(sysrq_key_table)

drivers/tty/sysrq.c

    437 static struct sysrq_key_op *sysrq_key_table[36] = {
    438         &sysrq_loglevel_op,             /* 0 */
    439         &sysrq_loglevel_op,             /* 1 */
    440         &sysrq_loglevel_op,             /* 2 */
    441         &sysrq_loglevel_op,             /* 3 */
    442         &sysrq_loglevel_op,             /* 4 */
    443         &sysrq_loglevel_op,             /* 5 */
    444         &sysrq_loglevel_op,             /* 6 */
    445         &sysrq_loglevel_op,             /* 7 */
    446         &sysrq_loglevel_op,             /* 8 */
    447         &sysrq_loglevel_op,             /* 9 */
    448 
    449         /*
    450          * a: Don't use for system provided sysrqs, it is handled specially on
    451          * sparc and will never arrive.
    452          */
    453         NULL,                           /* a */
    454         &sysrq_reboot_op,               /* b */
    455         &sysrq_crash_op,                /* c */
    456         &sysrq_showlocks_op,            /* d */
    457         &sysrq_term_op,                 /* e */
    458         &sysrq_moom_op,                 /* f */
    459         /* g: May be registered for the kernel debugger */
    460         NULL,                           /* g */
    461         NULL,                           /* h - reserved for help */
    462         &sysrq_kill_op,                 /* i */
    463 #ifdef CONFIG_BLOCK
    464         &sysrq_thaw_op,                 /* j */
    465 #else
    466         NULL,                           /* j */
    467 #endif
    468         &sysrq_SAK_op,                  /* k */
    469 #ifdef CONFIG_SMP
    470         &sysrq_showallcpus_op,          /* l */
    471 #else
    472         NULL,                           /* l */
    473 #endif
    474         &sysrq_showmem_op,              /* m */
    475         &sysrq_unrt_op,                 /* n */
    476         /* o: This will often be registered as 'Off' at init time */
    477         NULL,                           /* o */
    477         NULL,                           /* o */
    478         &sysrq_showregs_op,             /* p */
    479         &sysrq_show_timers_op,          /* q */
    480         &sysrq_unraw_op,                /* r */
    481         &sysrq_sync_op,                 /* s */
    482         &sysrq_showstate_op,            /* t */
    483         &sysrq_mountro_op,              /* u */
    484         /* v: May be registered for frame buffer console restore */
    485         NULL,                           /* v */
    486         &sysrq_showstate_blocked_op,    /* w */
    487         /* x: May be registered on mips for TLB dump */
    488         /* x: May be registered on ppc/powerpc for xmon */
    489         /* x: May be registered on sparc64 for global PMU dump */
    490         /* x: May be registered on x86_64 for disabling secure boot */
    491         NULL,                           /* x */
    492         /* y: May be registered on sparc64 for global register dump */
    493         NULL,                           /* y */
    494         &sysrq_ftrace_dump_op,          /* z */
    495 };

558                 /*
559                  * Should we check for enabled operations (/proc/sysrq-trigger
560                  * should not) and is the invoked operation enabled?
561                  */
562                 if (from == SYSRQ_FROM_KERNEL || sysrq_on_mask(op_p->enable_mask)) {

562 行目では実行コマンドが有効であるかをチェックしています。sysrq_on_mask 関数では「1-4. 有効なコマンドの制御」で登場した /proc/sys/kernel/sysrq の制御値 (sysrq_enabled) とコマンド毎のマスク値 (enable_mask) で 論理積をとっています (Memo_D)。

Memo_D: sysrq_on_mask の定義(sysrq_key_table)

drivers/tty/sysrq.c

    66 /*
    67  * A value of 1 means 'all', other nonzero values are an op mask:
    68  */
    69 static bool sysrq_on_mask(int mask)
    70 {
    71         return sysrq_always_enabled ||
    72                sysrq_enabled == 1 ||
    73                (sysrq_enabled & mask);
    74 }

なお「Memo_D」で使用している sysrq_enabled/proc/sys/kernel/sysrq の値なわけですが、その値は「Memo_E」の関数で設定されているようです (sysrq_enableddrivers/tty/sysrq.c でグローバルな変数です)。

Memo_E: sysrq_toggle_support の定義(sysrq_key_table)

drivers/tty/sysrq.c 

    1048 int sysrq_toggle_support(int enable_mask)
    1049 {
    1050         bool was_enabled = sysrq_on();
    1051 
    1052         sysrq_enabled = enable_mask;
    1053 
    1054         if (was_enabled != sysrq_on()) {
    1055                 if (sysrq_on())
    1056                         sysrq_register_handler();
    1057                 else
    1058                         sysrq_unregister_handler();
    1059         }
    1060 
    1061         return 0;
    1062 }

563                         pr_cont("%s\n", op_p->action_msg);
564                         console_loglevel = orig_log_level;
565                         op_p->handler(key);

コマンドコマンドが有効な場合は、563行目で実行コマンドとリンクする action_msg オペレーション (Emergency Sync) がリングバッファに出力され、565 行目で handler オペレーション (sysrq_handle_sync) が呼び出されます。

566                 } else {
567                         pr_cont("This sysrq operation is disabled.\n");
568                 }

567 行目は実行コマンドが無効である場合にとおるルートです。その場合、リングバッファに This sysrq operation is disabled. が出力されます。

569         } else {
570                 pr_cont("HELP : ");
571                 /* Only print the help msg once per handler */
572                 for (i = 0; i < ARRAY_SIZE(sysrq_key_table); i++) {
573                         if (sysrq_key_table[i]) {
574                                 int j;
575  
576                                 for (j = 0; sysrq_key_table[i] !=
577                                                 sysrq_key_table[j]; j++)
578                                         ;
579                                 if (j != i)
580                                         continue;
581                                 pr_cont("%s ", sysrq_key_table[i]->help_msg);
582                         }
583                 }
584                 pr_cont("\n");
585                 console_loglevel = orig_log_level;
586         }
587         rcu_read_unlock();
588         rcu_sysrq_end();
589 }

570 行目からは 552 行目の op_p = __sysrq_get_key_op(key); で取得したオペレーションが NULL である場合にとおるルートで、ヘルプとして機能します。たとえば、オペレーションが NULLh コマンドを実行 (echo h > /proc/sysrq-trigger) すると 570 行目で HELP : と表示した後、581 行目で全てのコマンドに対応する help_msg オペレーション (ヘルプメッセージ) をリングバッファに出力します (Memo_F)。

Memo_F: echo h > /proc/sysrq-trigger 実行例

    [root@fdr28 ~]# dmesg
    ...(snip)...
    [  175.628520] sysrq: SysRq : HELP : loglevel(0-9) reboot(b) crash(c) terminate-all-tasks(e) memory-full-oom-kill(f) kill-all-tasks(i) thaw-filesystems(j) sak(k) show-backtrace-all-active-cpus(l) show-memory-usage(m) nice-all-RT-tasks(n) poweroff(o) show-registers(p) show-all-timers(q) unraw(r) sync(s) show-task-states(t) unmount(u) force-fb(V) show-blocked-tasks(w) dump-ftrace-buffer(z) 

sysrq_handle_sync

すこし話が戻りますが「2-4. __handle_sysrq」の 565 行目で呼び出される実行コマンドの handler オペレーションの先を読んでいきます。565 行目付近を再掲します。

drivers/tty/sysrq.c

562                 if (from == SYSRQ_FROM_KERNEL || sysrq_on_mask(op_p->enable_mask)) {
563                         pr_cont("%s\n", op_p->action_msg);
564                         console_loglevel = orig_log_level;
565                         op_p->handler(key);

今回の調査対象である実行コマンドである s のオペレーションの定義は次のとおりです。

drivers/tty/sysrq.c

173 static struct sysrq_key_op sysrq_sync_op = {
174         .handler        = sysrq_handle_sync,
175         .help_msg       = "sync(s)",
176         .action_msg     = "Emergency Sync",
177         .enable_mask    = SYSRQ_ENABLE_SYNC,
178 };

そのため、565 行目で実行される handler オペレーションは sysrq_handle_sync です。

drivers/tty/sysrq.c

169 static void sysrq_handle_sync(int key)
170 {
171         emergency_sync();
172 }

sysrq_handle_syncemergency_sync 関数を呼び出しているだけです。

emergency_sync

emergency_sync 関数の定義は次のとおりです。

fs/sync.c

146 void emergency_sync(void)
147 {
148         struct work_struct *work;
149 
150         work = kmalloc(sizeof(*work), GFP_ATOMIC);
151         if (work) {
152                 INIT_WORK(work, do_sync_work);
153                 schedule_work(work);
154         }
155 }

150 行目の work には 「高優先度、スリープ不可」を意味する GFP_ATOMIC フラグを立ててメモリを割り当てています。

152 行目の INIT_WORK は第二引数に渡された関数である do_sync_work を 153 行目のワークキューに追加する前に必要な初期化や設定を行うマクロで、153 行目の schedule_work 関数でワークキューに追加します。

ワークキューに追加された関数はワーカスレッドは、遅延させて呼び出すことができますが、今回は 150 行目で「高優先度、スリープ不可」の GFP_ATOMIC フラグを立てているため、このスレッドは即座に実行されます。

(と、書きましたがワークキューやらワーカースレッド周りの知識は自信ありません)

do_sync_work

ワーカ・スレッドにより実行する do_sync_work 関数の定義は次のとおりです。

fs/sync.c

128 static void do_sync_work(struct work_struct *work)
129 {
130         int nowait = 0;
131 
132         /*
133          * Sync twice to reduce the possibility we skipped some inodes / pages
134          * because they were temporarily locked
135          */
136         iterate_supers(sync_inodes_one_sb, &nowait);
137         iterate_supers(sync_fs_one_sb, &nowait);
138         iterate_bdevs(fdatawrite_one_bdev, NULL);
139         iterate_supers(sync_inodes_one_sb, &nowait);
140         iterate_supers(sync_fs_one_sb, &nowait);
141         iterate_bdevs(fdatawrite_one_bdev, NULL);

136 ~ 137、139 ~ 140 行目の iterate_supersfs/super.c にある定義されている関数でコメントによると全てのアクティブなスーパーブロックに対して第一引数の関数を実行するための関数のようです (???)。138、140 行目の iterate_bdevs 関数は fs/bloc_dev.c にある関数で特にコメントありませんでしたが、bdevs とあるので、全てのブロックデバイスに対して第一引数の関数を実行するめの関数とか、そうゆう感じだと思います (この辺はちゃんと読んでないです)。

sync_inodes_one_sbsync_fs_one_sbfdatawrite_one_bdev 関数を 2 回ずつ実行しています。これは、133 ~ 134 行目のコメントによると、一時的なロックにより、同期できなかった inode やページを減らすための処置のようです。

142         printk("Emergency Sync complete\n");
143         kfree(work);
144 }

142 行目では s コマンドの処理が完了したことを示す Emergency Sync complete をリングバッファに出力し、143 行目で emergency_sync 関数の 150 行目で確保したメモリを解放しています。

調査環境

調査環境および関連ツールのバージョンは次のとおりです。

  • ディストリビューション (Fedora 28)
  • カーネル (4.19.16-200.fc28.x86_64)
  • echo コマンド (coreutils-8.29-7.fc28.x86_64)
  • glibc (glibc-2.27-37.fc28.x86_64)

調査に使用したソースコードも上記カーネルと同じ Fedora のカーネル(4.19.16-200.fc28)です。

なお、Fedora カーネのソースコードは次の手順で取得しました。

$ git clone git://git.kernel.org/pub/scm/linux/kernel/git/jwboyer/fedora.git
$ git checkout -b <branch_name> kernel-4.19.16-200.fc28

おわりに

調査の結果、SysRqKey の使い方や機能については把握できましたが、今の私のレベルだと使いどころがよく分からない機能が多い印象でした。m で表示されるメモリの情報にはどのような意味があるのかなど、一つ一つの機能を掘り下げて理解することで、使いどころが見えてくるような気もしますが、今はそこまでのモチベがないので、また今度にします。

また、コードリーディングの記録を残すという試みは今回が初めてでしたが非常に勉強になりました。アウトプットすることで考えが整理され、実装を理解する助けになりましたし、普段なら見過すようなコードにも目を向けることができ、知見が広がったような感覚があります (普段はなんとなく理解できれば良いやという感じでナナメヨミすることが多いです)。

なお、私が Linux カーネルを学び始めたのは最近であるため、理解が浅く、おかしな解釈や言葉遣いをしている個所もあるかと思います。そういった箇所はご指摘いただけると幸いです。


  1. この手順は一時的な変更です。恒久的に変更するには /etc/sysctl.confkernel.sysrq = [制御値] と設定した後、$ sysctl -p を実行するか、システムを再起動します。

  2. このコマンドの組み合わせを覚えるための語呂合わせとして 「Raising Elephants Is So Utterly Boring」 とか 「Reboot Even If System Utterly Broken」 というのがあるようです。