shutdownコマンドは昔のなごり(今はsystemd)

多くのLinuxの各ディストリビューションがsystemdを採用するようになって、電源周りのコマンドはsystemctlに統一された。shutdownコマンドが無くなったのは随分前になる。この記事では、systemdの電源管理と従来のコマンドとの関係を明らかにし、電源を切る際にはpoweroffコマンド・再起動の際にはrebootコマンドで十分であることを説明する。

# shutdownの理由
電源周りのコマンドにはshutdownの他にもpoweroffやhalt、rebootがある。しかし、これらのコマンドは、ファイルが失われる場合があるので使用しないほうが良いとされていた。電源を切る前に必要なsync処理などの電源切断前処理が行われないというのだ。だが、実際のLinuxの各ディストリビューションでは従来より、これらのコマンドを使用しても電源切断前処理は行われていた。現在のsystemdによる実装では、どの電源管理コマンドもsystemctlで行われているので、流れがより明確になっている。各電源管理コマンドを使用しても全く問題ない。次節よりsystemdの電源管理をザックリと検証していく。

# 電源管理
本題とは少しずれるが、パソコンの電源管理の話を歴史的経過から話そう。パソコンは一般の家電とは異なり、電源を突然切ることはできない。各種ソフトウェアの後処理があるからだ。当初パソコンの電源スイッチは遮断装置(つまり、物理的に電気が流れなくするスイッチ)が搭載されており、後処理用のコマンドがあった。Linuxの元になったUNIXでは、shutdownコマンドが用意されており、Linuxもそれに倣っている。これを実行しコマンドが終わったことを確認してから電源スイッチを手作業で操作する。
当然面倒なこの操作を自動化しようという考えが起こる。ACPIの登場である。1997年に設定され、実際の普及やOSの対応には2000年頃までかかったが、パソコンの電源はソフトェアから切れるようになった。これにより後処理を待ち、手作業で電源スイッチを切る手間は不要になった。
蛇足だが、ノートパソコンを中心にパソコンを休止モードという考え方がでてきた。OSを起動終了することなく、すぐに使えるコンピュータが望まれたのである。メモリ内容やCPUの状態を直接HDDなどのストレージに保存する方法(ハイバネーション)、CPUなどをスリープモードにし最小限の電力のみを供給する方法(サスペンド)で実現された。ACPIには、この管理も含まれている。もっとも、この方法はソフトウェアからすれば時間が突然大きく進むことになり不具合が発生することがある。この問題を持つソフトウェアは現在でも見かけることがある。

# systemdとは?
従来のLinux各ディストリビューションでは起動時終了時に/etc/init.dにあるスクリプトを順番に動作させ、各デーモンを起動やデバイスの初期化などを行っているものが多かった。この方法は、起動や終了に必要な時間が多かったり、場合によっては終了することができないこともあった。そこで、systemdと呼ばれるLinuxの起動終了やデーモンに関する一切のことを管理するソフトウェアが提案された。電源周りの処理はLinuxの起動終了に深く関係するのでこれも含まれることになった。

# systemctlコマンド
systemdを制御するコマンドがsystemctlだ。機能が多岐に渡るので、今回は電源関係部分の紹介に留める。

|コマンド|内容|
|--- |--- |
|systemctl poweroff|電源を切る|
|systemctl reboot|再起動|
|systemctl suspend|サスペンド|
|systemctl hibernate|ハイバネーション|

# shutdownは無い

lsコマンドで/sbin以下のshutdown/poweroff/halt/rebootの各コマンドを見てみる。

```bash
$ ls -l /sbin/shutdown /sbin/poweroff /sbin/halt /sbin/reboot
lrwxrwxrwx 1 root root 14 Jun 14 05:20 /sbin/halt -> /bin/systemctl
lrwxrwxrwx 1 root root 14 Jun 14 05:20 /sbin/poweroff -> /bin/systemctl
lrwxrwxrwx 1 root root 14 Jun 14 05:20 /sbin/reboot -> /bin/systemctl
lrwxrwxrwx 1 root root 14 Jun 14 05:20 /sbin/shutdown -> /bin/systemctl
```

すべて、/bin/systemctlのシンボリックリンクであることがわかる。

#systemctlの実装

次に、systemctlコマンドの実装を読んでみよう。ソースは次のurlにある。

https://github.com/systemd/systemd/blob/master/src/systemctl/systemctl.c

簡単に説明する。詳細は各自ソースを読んでほしい。

```c:systemctl.cの一部
static const struct {
        const char *target;
        const char *verb;
        const char *mode;
} action_table[_ACTION_MAX] = {
        [ACTION_HALT]                 = { SPECIAL_HALT_TARGET,                     "halt",                   "replace-irreversibly" },
        [ACTION_POWEROFF]             = { SPECIAL_POWEROFF_TARGET,                 "poweroff",               "replace-irreversibly" },
        [ACTION_REBOOT]               = { SPECIAL_REBOOT_TARGET,                   "reboot",                 "replace-irreversibly" },

```

シンボリックリンク元の名前によって動作が変わる。

|名前|処理名|
|---|---|
|halt|ACTION_HALT|
|poweroff|ACTION_POWEROFF|
|reboot|ACTION_REBOOT|

shutdownは特別にコードが書かれている。

```c:systemctl.cの一部
static int shutdown_parse_argv(int argc, char *argv[]) {
        enum {
                ARG_HELP = 0x100,
                ARG_NO_WALL
        };

        static const struct option options[] = {
                { "help",      no_argument,       NULL, ARG_HELP    },
                { "halt",      no_argument,       NULL, 'H'         },
                { "poweroff",  no_argument,       NULL, 'P'         },
                { "reboot",    no_argument,       NULL, 'r'         },
                { "kexec",     no_argument,       NULL, 'K'         }, /* not documented extension */
                { "no-wall",   no_argument,       NULL, ARG_NO_WALL },
                {}
        };

        char **wall = NULL;
        int c, r;

        assert(argc >= 0);
        assert(argv);

        while ((c = getopt_long(argc, argv, "HPrhkKat:fFc", options, NULL)) >= 0)
                switch (c) {
                case ARG_HELP:
                        return shutdown_help();
                case 'H':
                        arg_action = ACTION_HALT;
                        break;
                case 'P':
                        arg_action = ACTION_POWEROFF;
                        break;
                case 'r':
                        if (kexec_loaded())
                                arg_action = ACTION_KEXEC;
                        else
                                arg_action = ACTION_REBOOT;
                        break;
```

これより下には時間設定の処理などが記載されている。だが、つまるところshutdownコマンドのオプションにより、それぞれのコマンド(halt/poweroff/reboot)の動作に振り分けているのだ。少なくともsync処理は行われている。

# まとめと考察

再起動するには```reboot```コマンド、電源を切るには```poweroff```コマンドを使えば良い。Qiitaの記事でも```shutdown -r now```等を見かけるが、入力の手間が多いだけである。もちろん、この記事はsystemdを採用しているLinuxの話で他の環境ではOSに沿った終了や再起動の方法を取る必要がある。

#追記
その後、macOSやFreeBSDについても```reboot```コマンド```poweroff```コマンド について動作確認したが問題は見られなかった。現在メジャーなUnix系のOSでは全て問題ないと考えても良さそうだ。


#参考文献
http://lazy-dog.hatenablog.com/entry/2014/07/19/014123
ACPI https://ja.wikipedia.org/wiki/Advanced_Configuration_and_Power_Interface
systemd https://ja.wikipedia.org/wiki/Systemd