はじめに
電源を入れてから OS が起動するまでの流れを ブート または ブートストラップ と言う。
ブート手順は アーキテクチャ によって異なる。
さらに近年では従来から広く利用されてきた SysV init 方式から systemd 方式へ移り変わりつつある。
SysV init は 直列 で各プログラムを起動していくのに対して、systemd は 並列 で起動を行うため、起動速度に違いがあるものの、基本的な流れは共通している。
- 電源 ON
- ファームウェアが起動
- ブートローダが起動
- カーネルが起動
-
init
(またはsystemd
)が起動 - ランレベル(SysVinit)またはターゲット(systemd)に応じたサービスが起動
ファームウェア
Firmware
BIOS (Basic Inpput / Output System)や UEFI (Unified Extensible Fireware Interface)に代表される、デバイスを制御するためのプログラム。
実体はソフトウェアでありながら、ハードウェアとソフトウェアの中間的な存在として位置づけられるため「ファームウェア」と呼ばれる(firm = 固い、堅牢)。
ブートローダ をメモリ上に展開して起動する役割を持つ。
UEFI は BIOS の後継プログラムであり、両者は別々のものだが、BIOS という用語が広く定着しているために UEFI を指して BIOS を言うことがある。
BIOS はマザーボード上の ROM に書き込まれているため、工場出荷後のプログラム更新が困難。
UEFI は、書き換え可能な フラッシュメモリ(NVRAM)に書き込まれるため、発売後にアップデートを行うことができる(ファームウェアアップデート)。
また UEFI には、デジタル署名済みの信頼できるソフトウェアだけを起動するセキュリティ機能(セキュアブート)もある。
ブートローダ
Boot Loader
GRUB 、Windows Boot Manager に代表される、カーネルをメモリにロードして起動するプログラム。
複数の OS がインストールされている PC などで、どの OS を起動するかを選択できる機能を デュアルブート や、マルチブート という。
/boot/grub/menu.lst
や /boot/grub/grub.conf
が設定ファイルとして使用される。
ACPI
advanced configuration and power interface
電力を管理するための規格。
- 電源ボタンを押す
- ノートPCを開ける・閉じる
などの物理的操作によって ACPI イベントが発生する。イベントは acpid
デーモンによって処理される。
SysVinit
System V(Five) Init
長年 Linux の標準的な init システムとして使用されてきた、システムの起動やプロセス管理の仕組み。
並列処理ができないことによる起動の遅さなどの課題があり、Red Hat 系(RHEL、CentOS、Fedora)や Debian 系(Debian、Ubuntu)では、既に後継版である systemd に移行されている。
ただし一部の軽量ディストリビューション(Alpine Linux)では、現在も SysVinit が採用されている。
- ランレベル でシステムの状態を管理する
- サービスの起動を 直列処理 で 1 つずつ順番に起動するため時間がかかる
- サービスが異常終了した場合、自動で復旧しない
SysVinit の主な役割は、システム起動時に必要なプロセスを適切に立ち上げ、シャットダウン時に適切に停止することだが、「デーモンを起動する」、「親プロセスが終了した孤立プロセスを引き取る」役割なども併せ持つ。
ランレベル
システムの状態を管理するための概念のこと。
例えば「シャットダウン」は「ランレベル 0
の呼び出し」を意味し、「システムの再起動」は「ランレベル 6
の呼び出し」を意味している。
ランレベル | 意味 |
---|---|
0 |
システムの停止(シャットダウン) |
1 |
シングルユーザモードroot ユーザのみがログインできる |
2 |
マルチユーザモード NFS なし(※) |
3 |
マルチユーザモード NFSあり(※)、GUIなし |
4 |
未使用 |
5 |
マルチユーザモード + GUI(X Window) |
6 |
システムの再起動 |
ランレベルの呼び出しは init
プロセスによって行われる。
システム起動時に init
は /etc/inittab
に記載されたランレベルに応じて、実際には /etc/rcX.d/
ディレクトリにあるスクリプトを サービス として起動する。rcX
の rc
の語源は run command。X
にはランレベルの番号が入る。
/etc/rcX.d/
ディレクトリ内の S
や K
で始まるファイルは、 /etc/init.d/
ディレクトリに配置されたスクリプトへの シンボリックリンク になっている。
S
は start
を意味し、K
は kill
を意味する。
$ ls -l /etc/rc3.d/
total 0
lrwxrwxrwx 1 root root 17 Jan 1 09:00 K01bluetooth -> ../init.d/bluetooth
lrwxrwxrwx 1 root root 15 Jan 1 09:00 K02cups -> ../init.d/cups
lrwxrwxrwx 1 root root 16 Jan 1 09:00 S01apache2 -> ../init.d/apache2
lrwxrwxrwx 1 root root 16 Jan 1 09:00 S02cron -> ../init.d/cron
lrwxrwxrwx 1 root root 18 Jan 1 09:00 S20networking -> ../init.d/networking
lrwxrwxrwx 1 root root 13 Jan 1 09:00 S99local -> ../init.d/rc.local
NFS
Network File System
リモートのサーバにあるファイルをローカルのディレクトリのように扱える仕組み。
例えばサーバー上の /home/shared/
をクライアントの /mnt/shared/
に マウント すれば、まるでローカルファイルのようにアクセスできるようになる。
ランレベル 2
では NFS クライアントが無効、3
では有効。
/sbin/init
カーネル起動後に最初に実行される /sbin/init
プロセス。
PID(プロセスID) 1
が割り当てられる。
ちなみに PID 0
はカーネル用の特別な番号として使われており、 1
はユーザプロセスが使用できる最初の PID である。
ユーザが手動で起動したプロセスは init
ではなく、プロセスを起動したシェル(/bin/bash
)などが親プロセスになる。
親プロセスは $ pstree
で確認することができる。
$ pstree
init
は設定ファイル /etc/inittab
を読み込んで、どの ランレベル で起動するかを決定しているする。
-
init
が/etc/inittab
を読み込む -
init
が/etc/rc.sysinit
を読み込む -
init
が/etc/rc
を実行する -
/etc/rc
が/etc/rcランレベル.d
ディレクトリ配下のスクリプトを実行する
/etc/inittab
SysVinit を使うディストリビューションで、デフォルトのランレベルが記述されたファイル。
init
により読み込まれる。
id:ランレベル:initdefault:
内容を変更した場合、$ init q
で即時反映させることができる。
$ init q
$ runlevel
$ runlevel
「一つ前のランレベル」と「現在のランレベル」が表示される。起動直後は一つ前のランレベルが N
で表示される。
$ init
/ $ telinit
$ init ランレベル
$ telinit ランレベル
$ telinit q
$ wall
$ wall
メッセージをここで入力 # Ctrl + D で送信終了
$ コマンド | wall
$ wall ファイル
$ service
SysV init で使用される サービス 管理用コマンド。
$ service サービス start
$ service サービス stop
$ service サービス restart
$ service サービス reload
$ service サービス status
systemd
従来の SysV init の後継版として開発され、現代の Linux で主流になっているブートシステム。
以下の特徴を持つ。
- 並列 処理により高速に起動
- すべての管理対象(サービス、デバイス、マウントポイントなど)を ユニット という概念で統一的に管理
- ターゲット によりシステム状態を管理(SysV init のランレベルに相当)
- 異常終了したサービスを自動で再起動可能
-
journald
というログ管理システムが組み込まれている
ただし、以下の批判をされることがある。
- SysV init はシンプルなシェルスクリプトで構成されていたのに対して、systemd はバイナリの実行可能ファイルで構成されており、内部動作が隠蔽されているのに加えて、仕組みが複雑すぎる
- UNIX の「小さなツールを組み合わせる」という設計思想に反して、多機能すぎる
- あまりにも多くを管理しすぎているため、 systemd に対する依存が強くなる
このため、一部のディストリビューション(Alpine Linux, Devuan, Artix Linux など)は systemd を採用せず、SysVinit や runit、OpenRC を使い続けている。
SysV init の init
が担っていた役割を、systemd では /usr/lib/systemd/systemd
デーモンが担当する。systemd
は init
同様に PID 1
が割り当てられる。
systemd
の他にも以下のデーモンが存在する。
デーモン | 役割 |
---|---|
systemd |
/usr/lib/systemd/systemd PID 1 の特別なメインプロセス |
systemd-journald |
ログ管理 |
systemd-logind |
ログイン処理 |
systemd-networkd |
ネットワーク管理 |
systemd-timesyncd |
システムクロック 管理 |
systemd-resolved |
ホスト名の名前解決 |
systemd-udevd |
udev |
ユニット
Unit
systemd では、すべてのサービスやリソースを ユニット という概念で管理する。
ユニットは ユニットファイル によって定義され、一つのユニットは複数のサービスから構成される。
systemd では起動や停止処理を「ユニット」単位で制御する。
ユニットの拡張子
ユニットにはいくつかの種類があり、それぞれ以下の拡張子が使用される。
拡張子 | 説明 |
---|---|
.target |
ターゲット を管理するユニット。ターゲットは SysV init の ランレベルに相当する。 【ランレベル:ターゲット】 0 :poweroff.target 1 :rescue.target 2 、3 、4 :multi-user.target 5 :graphical.target 6 :reboot.target
|
.service |
自身以外の他のデーモンやサービスを管理するユニット。 【Webサーバ】 httpd.service 、apache2.service 、nginx.service 【DBサーバ】 mysqld.service 、postgresql.service 【メールサーバ】 postfix.service 【SSHサーバ】 sshd.servic 【ジョブスケジューラ】 crond.service
|
.mount |
マウント を管理するユニット。 |
.device |
デバイスを管理するユニット。 |
.timer |
ジョブのスケジュールを管理するユニット(cron に相当)。 |
.socket |
ソケットを管理するユニット。 |
.swap |
スワップ を管理するユニット。 |
ユニットファイル
ユニットはユニットファイルによって定義される。
ユニットファイルは [セクション]
単位で記述され、一つのセクションは複数の ディレクティブ から構成される。
[Unit]
Description=
After=
Wants=
Requires=
[Service] (.service の場合)
ExecStart=
Restart=
User=
Group=
[Install]
WantedBy=
ディレクティブ | 説明 |
---|---|
Description |
説明書き |
After |
起動していることが前提とされる別ユニット(依存しているユニット。起動順の制御に関係するが、試みるだけで強制力を持たない。つまり、ここに記載されたユニットが起動していなくても、起動処理が走る) |
Wants |
推奨の依存関係(なくても起動可能) |
Requires |
必須の依存関係(これが無いと起動しないという別のユニット。強制的な依存関係となるため、一方が停止されるともう一方も停止する。起動も同様。) |
ExecStart |
ユニットが内部で実行するコマンドの絶対パス |
Restart |
再起動の設定(always、on-failure で指定) |
User |
実行ユーザ名 |
Group |
実行グループ名 |
WantedBy |
実行時にどのターゲットに関連付けるか |
ユニットファイルの配置
ユニットファイルは下記のディレクトリに格納されている。
ディレクトリ | 説明 |
---|---|
/etc/systemd/system |
ユーザがカスタマイズしたユニットファイルが格納される 優先順位が最も高い。 |
/run/systemd/system |
一時的な Unit ファイルが格納される。 再起動によって消失する。 |
/usr/lib/systemd/system |
Linux デフォルトのユニットファイルが格納される ユーザが直接編集しない。 |
/lib/systemd/system |
ディストリビューションによって /usr/lib/systemd/system の代わりに使用される。 |
~/.config/systemd/user |
ユーザ単位のユニットファイルが格納される。 |
/usr/lib/systemd/systemd
systemd の本体は /usr/lib/systemd/systemd
にあり、ブートローダがカーネルに制御を渡したあと、最初にこのプロセスが PID 1
として起動される。
$ systemctl
System Control
systemd で使用されるユニット管理用コマンド。
指定するユニットが ***.service
の場合、.service
を省略できる。
$ systemctl start ユニット
$ systemctl stop ユニット
$ systemctl poweroff
$ systemctl restart ユニット
$ systemctl reboot
$ systemctl reload ユニット # サービスごとに設定ファイルは異なる
$ systemctl daemon-reload
$ systemctl reload サービス
は特定のサービスの設定を再読み込みするためのコマンド。
$ systemctl daemon-reload
は systemd
に ユニットファイル の変更を反映させるためのコマンド。
$ systemctl enable ユニット
$ systemctl disable ユニット
$ systemctl status ユニット
$ systemctl is-active ユニット
$ systemctl list-dependencies ユニット
$ systemctl list-unit-files
$ systemctl list-unit-files -t service
$ systemctl list-unit-files -t service --state=enabled
$ systemctl list-units
$ systemctl mask ユニット
$ mask
はユニットファイルに /dev/null
への シンボリックリンク を作成するため、start
などの起動コマンドすら無効になる。enable
無効化とは異なり、手動起動もブロックされる。
$ systemctl unmask ユニット
$ systemctl get-default
$ systemctl set-default ターゲット
getty
/dev/tty1
~ /dev/tty6
までの 仮想コンソール のデバイスファイルは、起動時にカーネルによって作成される。
OS が起動すると、SysVinit
もしくは systemd
が getty
(get tty)プロセスを起動する。
getty
の役割は以下を行うことにある。
- デバイスとしての端末を
open()
する-
open()
は システムコール の一つ
-
- シェルの標準入出力を
open()
した端末へ接続する - ユーザ名の入力を促すログイン用のプロンプトを表示する
-
login
プロセスを起動し、ユーザが入力した情報を渡すことでログインを実行する
getty
は /dev/tty1
、/dev/tty2
、 ... のデバイスファイルを open()
によってオープンし、戻り値として取得した ファイルディスクリプタ の接続先を次のように割り当てる。
ファイルディスクリプタ | 接続先 |
---|---|
0 (0 ) |
/dev/tty1 (キーボード) |
1 (標準出力) |
/dev/tty1 (画面出力) |
2 (標準エラー) |
/dev/tty1 (画面出力) |
これにより、シェルは 標準入力 を /dev/tty1
(キーボード)から受け取り、標準出力を /dev/tty1
(ディスプレイ)へと流せるようになる。
getty
はその後、下記のようなログインプロンプトを表示し、ユーザー名から入力された「ユーザ名」と「パスワード」を login
プロセスに渡す。login
プロセスは getty
によって起動される。
login
プロセスは、入力された情報による認証が完了すると、ユーザのデフォルトシェル(bash
など)起動する。
/proc/cmdline
command line
カーネルが起動される時に、ブートローダから受け取るコマンドライン引数。
カーネルの起動時に渡す引数には以下のような種類がある。
-
ro
- read only
- ルートファイルシステムを読み取り専用でマウントする
- その後、
init
の中などの適切なタイミングで$ mount -o remout,rw /
される
-
root=
- ルートファイルシステムをマウントするパーティション
- UUID、LABEL でも指定可能
-
quiet
- 起動時のカーネルメッセージを最小限に抑える
$ uname
アーキテクチャを確認することができる。
$ grub-install
$ sudo grub-install インストール先ディスク
$ sudo grub-install /dev/sda
$ shutdown
$ shutdown -h now
$ shutdown -r now
$ shutdown -h 21:00
$ shutdown -c
$ dmesg
/var/log/dmesg
に保存された、起動時のカーネルのログを確認する。
$ dmesg
/var/log/dmesg
の情報は $ dmesg --clear
にて明示的に削除することができる(Ubuntu では /var/log/dmesg
は作成されない)。
$ dmesg --clear