Linux Magic SysRq Key 調査

# ■ はじめに

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

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

# ■ 使い方と機能を知る

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

## 1. 使い方と機能の要約

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

## 2. 基本的な使い方

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

- **コマンドラインから利用する場合**
  `$ echo [コマンド] > /proc/sysrq-trigger`

- **キーボードから利用する場合**
  <kbd>Alt</kbd> + <kbd>SysRq</kbd> + `[コマンド]`

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

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

## 3. 機能(コマンド)一覧

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

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

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

## 4. 有効なコマンドの制御

システムで有効なコマンドは次のように制御できます[^恒久的な変更]。

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

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

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

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

## 5. 動作確認

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

### 5.1. 有効なコマンドの確認

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

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

```console
[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 Sync` と `Emergency Sync complete` が出力されました。前者はコマンド成功時に出力されるコマンド毎のメッセージで、後者はコマンドの実行結果です。今回は <kbd>s</kbd> なので出力されるログが少ないのですが、コマンドによっては、大量にログが出力されます。

### 5.2.  無効なコマンドの確認

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

```console
[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.` と出力され無効であることが確認できました。有効なコマンドのときにあったコマンド毎のメッセージも出力されていません。

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

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

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

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

```console
[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` というコマンド毎のメッセージが表示された後、実行結果(メモリの情報)が表示されました。意図したとおりにコマンドが有効であることが確認できました。

## 6. 使いどころ

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

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

[^語呂合わせ補足]: このコマンドの組み合わせを覚えるための語呂合わせとして 「**R**aising **E**lephants **I**s **S**o **U**tterly **B**oring」 とか 「**R**eboot **E**ven **I**f **S**ystem **U**tterly **B**roken」 というのがあるようです。

# ■ カーネル実装を読む

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

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

```console
$ echo s > /proc/sysrq-trigger
```

## 1. 処理の全体像

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

```console
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
```

## 2. proc_reg_write

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

```c: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-trigger` の `write` オペレーションのアドレスを格納しています。`sysrq-trigger` のオペレーションでは `.write = write_sysrq_trigger` と定義 (**Memo_A**) されているため、245 行目では `write_sysrq_trigger` 関数が呼び出されます。

<details><summary>**Memo_A**: `sysrq-trigger` のオペレーションの定義</summary><div>

```c:drivers/tty/sysrq.c
    1118 static const struct file_operations proc_sysrq_trigger_operations = {
    1119         .write          = write_sysrq_trigger,
    1120         .llseek         = noop_llseek,
    1121 };
```

</div></details>

## 3. write_sysrq_trigger

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

```c: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` 関数に変数 `c` と `buf` を渡して呼び出しています。調べたところ `get_user` 関数はユーザ空間からカーネル空間へ変数をコピーするためのもので、第一引数にコピー元の変数、第二引数にコピー元ユーザ空間のアドレスが渡されるようです。

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

<details><summary>**Memo_B**: `EFAULT` の定義 (18 行目)</summary><div>

```c: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
```

</div></details>

## 4. __handle_sysrq

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

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

<details><summary>**Memo_C**: コマンド毎のオペレーション定義(sysrq_key_table)</summary><div>

```c: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 };
```

</div></details>


```c
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**)。

<details><summary>**Memo_D**: `sysrq_on_mask` の定義(sysrq_key_table)</summary><div>

```c: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 }
```

</div></details>

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

<details><summary>**Memo_E**: `sysrq_toggle_support` の定義(sysrq_key_table)</summary><div>

```c: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 }
```
</div></details>

```c
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`) が呼び出されます。

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

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

```c
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` である場合にとおるルートで、ヘルプとして機能します。たとえば、オペレーションが `NULL` の <kbd>h</kbd> コマンドを実行 (`echo h > /proc/sysrq-trigger`) すると 570 行目で `HELP : ` と表示した後、581 行目で全てのコマンドに対応する `help_msg` オペレーション (ヘルプメッセージ) をリングバッファに出力します (**Memo_F**)。

<details><summary>**Memo_F**: `echo h > /proc/sysrq-trigger` 実行例</summary><div>

```console
    [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) 
```

</div></details>

## 5. sysrq_handle_sync

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

```c: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);
```

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

```c: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` です。

```c:drivers/tty/sysrq.c
169 static void sysrq_handle_sync(int key)
170 {
171         emergency_sync();
172 }
```

`sysrq_handle_sync` は `emergency_sync` 関数を呼び出しているだけです。

## 6. emergency_sync

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

```c: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` フラグを立てているため、このスレッドは即座に実行されます。 

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

## 7. do_sync_work

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

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

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

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

142 行目では <kbd>s</kbd> コマンドの処理が完了したことを示す `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 カーネのソースコードは次の手順で取得しました。

```console
$ 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** の使い方や機能については把握できましたが、今の私のレベルだと使いどころがよく分からない機能が多い印象でした。<kbd>m</kbd> で表示されるメモリの情報にはどのような意味があるのかなど、一つ一つの機能を掘り下げて理解することで、使いどころが見えてくるような気もしますが、今はそこまでのモチベがないので、また今度にします。

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

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

<!-- 気が向いたら

キープレス時の挙動

```c
input_handle_event
input_pass_values
input_to_handler
sysrq_filter
```

```c:driver/input/input.c
 369 static void input_handle_event(struct input_dev *dev,
 370                                unsigned int type, unsigned int code, int value)
 371 {
 372         int disposition = input_get_disposition(dev, type, code, &value);
 373 
 374         if (disposition != INPUT_IGNORE_EVENT && type != EV_SYN)
 375                 add_input_randomness(type, code, value);
 376 
 377         if ((disposition & INPUT_PASS_TO_DEVICE) && dev->event)
 378                 dev->event(dev, type, code, value);
 379 
 380         if (!dev->vals)
 381                 return;
 382 
 383         if (disposition & INPUT_PASS_TO_HANDLERS) {
 384                 struct input_value *v;
 385 
 386                 if (disposition & INPUT_SLOT) {
 387                         v = &dev->vals[dev->num_vals++];
 388                         v->type = EV_ABS;
 389                         v->code = ABS_MT_SLOT;
 390                         v->value = dev->mt->slot;
 391                 }
 392 
 393                 v = &dev->vals[dev->num_vals++];
 394                 v->type = type;
 395                 v->code = code;
 396                 v->value = value;
 397         }
 398 
 399         if (disposition & INPUT_FLUSH) {
 400                 if (dev->num_vals >= 2)
 401                         input_pass_values(dev, dev->vals, dev->num_vals);
 402                 dev->num_vals = 0;
 403         } else if (dev->num_vals >= dev->max_vals - 2) {
 404                 dev->vals[dev->num_vals++] = input_value_sync;
 405                 input_pass_values(dev, dev->vals, dev->num_vals);
 406                 dev->num_vals = 0;
 407         }
 408 
 409 }
```

```c:driver/input/input.c
  91 /*
  92  * Pass event first through all filters and then, if event has not been
  93  * filtered out, through all open handles. This function is called with
  94  * dev->event_lock held and interrupts disabled.
  95  */
  96 static unsigned int input_to_handler(struct input_handle *handle,
  97                         struct input_value *vals, unsigned int count)
  98 {
  99         struct input_handler *handler = handle->handler;
 100         struct input_value *end = vals;
 101         struct input_value *v;
 102 
 103         if (handler->filter) {
 104                 for (v = vals; v != vals + count; v++) {
 105                         if (handler->filter(handle, v->type, v->code, v->value))
 106                                 continue;
 107                         if (end != v)
 108                                 *end = *v;
 109                         end++;
 110                 }
 111                 count = end - vals;
 112         }
 113 
 114         if (!count)
 115                 return 0;
 116 
 117         if (handler->events)
 118                 handler->events(handle, vals, count);
 119         else if (handler->event)
 120                 for (v = vals; v != vals + count; v++)
 121                         handler->event(handle, v->type, v->code, v->value);
 122 
 123         return count;
 124 }
```

```c:driver/input/input.c
 131 static void input_pass_values(struct input_dev *dev,
 132                               struct input_value *vals, unsigned int count)
 133 {
 134         struct input_handle *handle;
 135         struct input_value *v;
 136 
 137         if (!count)
 138                 return;
 139 
 140         rcu_read_lock();
 141 
 142         handle = rcu_dereference(dev->grab);
 143         if (handle) { 
 144                 count = input_to_handler(handle, vals, count);
 145         } else {
 146                 list_for_each_entry_rcu(handle, &dev->h_list, d_node)
 147                         if (handle->open) {
 148                                 count = input_to_handler(handle, vals, count);
 149                                 if (!count)
 150                                         break;
 151                         }
 152         }
 153 
 154         rcu_read_unlock();
 155 
 156         /* trigger auto repeat for key events */
 157         if (test_bit(EV_REP, dev->evbit) && test_bit(EV_KEY, dev->evbit)) {
 158                 for (v = vals; v != vals + count; v++) {
 159                         if (v->type == EV_KEY && v->value != 2) {
 160                                 if (v->value)
 161                                         input_start_autorepeat(dev, v->code);
 162                                 else
 163                                         input_stop_autorepeat(dev);
 164                         }
 165                 }
 166         }
 167 }
```
-->