[Linux] 起動時間を*極限まで*短くする

前回は主に`systemctl`を使っていろいろいじってだいたい17秒短縮できました。

https://qiita.com/hayattgd/items/731f3c03387d54b490d8

しかし、「やっぱり30秒近くあるのって長くね？」と思ってしまいました。
そこで今回は**できるだけ**ではなく**極限まで**短くするというのを目標にしていきます。

(前回やった最適化もここに書きますのでわざわざ前のは見なくても大丈夫です)

# 環境

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3886798/ae404530-e93a-4b5e-8217-51d2258ac69a.png)

```terminal
$ systemd-analyze #現在の起動時間
Startup finished in 17.469s (firmware) + 6.790s (loader) + 11.084s (kernel) + 7.852s (userspace) = 43.196s 
graphical.target reached after 4.802s in userspace.
```

- firmware : BIOS/UEFIの処理
- loader : GRUBやsystemd-bootがlinuxを読み込むまでの時間
- kernel：カーネルの初期化
- usespace：initがログイン画面を出すまで

# 目標

- 少なくともログイン後にはsshdやNetworkManagerが動いていて、しっかり`/`直下のディレクトリは全てマウントされてる状態になること
- Corebootを入れたりするような起動しなくなるリスクがあること(~~危険行為~~)はしない
- **とにかく、起動を早く、爆速に**

# 本題
今起動時間が多いのは
```
firmware > kernel > userspace > loader
```
の順番ですね。

（　）のところには使ってるものを書いておきます。

## Linux ディストリビューション
これに関してはインストール後にカスタマイズすれば結局どうにかなるものですが、インストール時から最適化されているものは

- [Void](https://voidlinux.org/)（systemdの代わりにrunit）
- [Alpine Linux](https://www.alpinelinux.org/)（systemdの代わりにopenrc）

後からカスタマイズしやすいものは

- [Artix Linux](https://artixlinux.org/)（いろいろなinitから選べる）
- [CachyOS](https://cachyos.org/)(または[Arch Linux](https://archlinux.org/))（カーネルがカスタマイズされてる）

## firmware (UEFI)

ここは使ってるマザーボードによってだいぶ変わってくると思います。
CorebootができればやりたかったのですがBIOSが壊れても自分じゃ直せない自信しかないのでやめておきました。

自分のマザーボードではこのような設定になりました

| 名前 | 値 |
|:-:|:-:|
| MSI Fast Boot | 有効 |
| Memory Fast Boot | No Training |

これにプラスでLinuxが入っているディスクを起動順序で一番最初にしたり、使っていないもの(ネットワークブートなど)を無効化しました。


`firmware`の時間はだいぶ気まぐれなのでもう参考にしてません()

あとこれは別に今回やったことじゃないんですがBIOSのアップデートをしたら早くなってた気がします


## kernel (linux-cachyos-gcc -> linux-cachyos-eevdf-lto)
#### mkinitcpio.conf
`/etc/mkinitcpio.conf`の`HOOK`で使わないものを削る(自分の場合は`plymouth`)
`MODULES`にはnvidia系しかないので削れませんでした。

圧縮方式を`lz4`にしてオプションに`-1`を指定することでサイズは多少大きくなるかもしれませんが、最速の読み込みができます。

#### カーネルのコンパイル
カーネルを`linux-cachyos-gcc`から`linux-cachyos-eevdf-lto`に変えるついでに色々コンフィグをいじって自分のパソコンの構成に最適化するようにしました。

:::note warn
カーネルのコンフィグで`modprobed-db`というものを使うと今の起動で使われたモジュールのみを含むようになるのでコンパイルは早く、サイズも小さくなります。
ですが、逆に今使っていないキーボードなどが使えないので気をつけましょう
<sub>なので今回は`modprobed-db`を無効化した構成になります</sub>
:::

`cachyos-kernel-manager`を使ってコンパイルしたので実際の値の名前と異なっているかもしれません

| 名前 | 値 |
|:-:|:-:|
| Enable LTO | Thin |
| CPU compiler optimizations | Native Intel |
| Disable NUMA | 有効 |

#### カーネルパラメーター

`systemd.zram=0`を渡して`zram`を無効化しておきました。
効果があるのかは知りませんがRAMがおかしいほどにあるのでメモリが大量に消費されることになっても大丈夫でしょう多分<sub>**きっと**おそらく~~99%~~</sub>

## userspace (systemd)
ここに関してはsystemdからopenrcやrunitに乗り換えることも検討しましたが、仮想環境でやってみたらほぼ同じ速度(なんならsystemdの方がちょい早い)感じだったのと、systemdから乗り換えるならLinuxディストリビューションごと移動しないと互換性が終わってたりするのでsystemdのままにしました

### ディスクのマウント
目標は起動速度の最適化なので`/dev/sdc3`(/home)や前のArch Linuxが入っていた場所、Windowsのデータがある場所は、それぞれのディスクに対してなにか要求(読み書き)があったときだけマウントするよう設定します。

```sh:/etc/fstab
# /dev/sdc2
UUID=..   /run/media/hayattgd/Arch    ext4    noauto,x-systemd.automount,x-systemd.idle-timeout=600 . . .

# /dev/sdb2 LABEL=HDD
UUID=..   /run/media/hayattgd/HDD    ntfs    noauto,x-systemd.automount,x-systemd.idle-timeout=600 . . .

# /dev/sdc3 LABEL=Linux\134x20home
UUID=..   /home    ext4    noauto,x-systemd.automount,x-systemd.idle-timeout=300 . . .
```

これでアクセスしたときにマウントされて、一定時間アクセスがなければアンマウントする仕組みになりました。
再起動してみて、普通にログインしてみてもあまり遅い印象はありませんでした。

:::note info
ここで指定しても自動的にマウントされてしまう場合はなにかのサービスがそのディスクを必要としていないか確認（特に`libvirtd`）
:::

### .socketがあるサービス類
.serviceに対応する.socketがある場合はサービスを無効化しておいても必要に応じてソケットが有効化してくれるので、サービスだけ無効化しておくと起動時にサービスがつかないので早くなります。

### .socketがないが起動してすぐには使わないサービス

`systemd-hostnamed`や`systemd-timesyncd`などは必要に応じて手動で起動すればいいので無効化しました。

しかし`NetworkManager`など、起動に直接関係はないがログイン後に必要になるものは`.timer`とスクリプトを組み合わせて作りました。

```bash:/usr/local/bin/start-delayed-services.sh
#!/bin/bash
systemctl start libvirtd.service
systemctl start NetworkManager.service
systemctl start avahi-daemon.service
systemctl start tor.service
systemctl start cockpit.socket
```
```ini:/etc/systemd/system/start-delayed-services.service
[Unit]
Description=Start delayed services to speed up boot

[Service]
Type=oneshot
ExecStart=/usr/local/bin/start-delayed-services.sh
```
```ini:/etc/systemd/system/start-delayed-services.timer
[Unit]
Description=Actual timer file for delayed services

[Timer]
OnBootSec=30s
AccuracySec=1s

[Install]
WantedBy=timers.target
```

## loader (GRUB -> systemd-boot -> efistub)
前は機能が豊富な`GRUB`を使っていたのですが、その次に速度を優先して`systemd-boot`、その次に一つのOSだけを起動することを目的に`efistub`にしました。

### systemd-boot
`systemd-boot`では`timeout 0`にしていても[スペースキーを押しているとOSを選べます](https://wiki.archlinux.jp/index.php/Systemd-boot#:~:text=timeout%200%20%E3%82%92%E8%A8%AD%E5%AE%9A%E3%81%97%E3%81%A6%E3%81%84%E3%82%8B%E5%A0%B4%E5%90%88%E3%80%81Space%20%E3%82%92%E6%8A%BC%E3%81%99%E3%81%93%E3%81%A8%E3%81%A7%E3%83%96%E3%83%BC%E3%83%88%E3%83%A1%E3%83%8B%E3%83%A5%E3%83%BC%E3%81%AB%E3%82%A2%E3%82%AF%E3%82%BB%E3%82%B9%E3%81%A7%E3%81%8D%E3%81%BE%E3%81%99%E3%80%82)
このメリットは他のOSも起動できるようにしつつ、起動速度は早くなることです。

しかしこれをやった後に何故か`firmware`が劇的に減って`loader`がちょい増えるという謎現象が発生
前後にBIOSアップデートやらカーネルの更新やらも挟んだのでそれが原因かも？
まあ総合的に起動時間は減ってるので結果オーライ

### efistub
しかし、やっぱりloaderが増えてfirmwareが減るという謎現象が気になったので、efistubにしました。
これはUEFIから直接Linuxを起動する方法で、ブートローダーを挟まない(firmwareを除き)ので、最速の方法だと思います。
ただ、直でLinuxへ行くため、他のOSへ行くにはBIOSを経由しないといけません。

```terminal
$ efibootmgr --create
    --disk /dev/sda
    --part 1
    --label "CachyOS-boot"
    --loader /vmlinuz-linux-cachyos
    --unicode 'root=UUID=c3ba369e-e2fc-430e-9b86-d3dba462a742 rw ... initrd=\initramfs-linux-cachyos.img'
```

# 結果発表

```terminal
$ systemd-analyze #前の起動時間
Startup finished in 17.469s (firmware) + 6.790s (loader) + 11.084s (kernel) + 7.852s (userspace) = 43.196s 
graphical.target reached after 4.802s in userspace.

$ systemd-analyze #現在の起動時間
Startup finished in 21.492s (firmware) + 1.702s (loader) + 3.752s (kernel) + 1.956s (userspace) = 28.903s 
graphical.target reached after 1.806s in userspace.
```
43.196 - 28.903 = 14.293

14.239秒短縮できた

`firmware`の時間は結構気まぐれです