【Linux】OSブートの仕組み

たまにカーネル周り触っててわからなくなるのでまとめておく。
前提として超基本的なところ

# コンピュータの五大装置
`入力`：コンピュータに命令を与える。キーボード,マウス
`記憶`：コンピュータ内の情報を格納し、記憶しておくメモリ、※補助記憶装置⇒HDD,SSD
`演算`：CPU内にあり、算術演算（四則演算）や論理演算（andやor）などの計算を行う装置
`制御`：CPU内にあり、記憶装置から読み込んだプログラムを解釈し、コンピュータ全体の動作を制御。
`出力`：コンピュータの処理結果を出力する。(モニター、スピーカー、プリンタ)
　※装置間のデータや制御はマザーボードで繋がっている

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/673840/8b74b70f-fccf-b386-141e-33c87d54ab4b.png)
(https://academy.gmocloud.com/know/20150413/929 より)

## CPU
(中央処理装置：Central Processing Unit)の略。
`演算装置`：レジスタにあるデータを0/1で計算。ALU(ArithmeticLogicUnit=算術論理演算装置)など。
`レジスタ`：すばやく演算するためにメモリからデータを保持。CPUはここのデータしか処理できない
`制御装置`：メモリからレジスタへのデータのやり取りなど、全体の動きをコントロール
`クロック`：1秒間隔で信号を出し、作業タイミングを合わせる指揮者。2GHz⇒1秒で20億回処理
　⇒発熱などにより性能に限度があるため、コアを増やすことで向上できる。(2コア,4コア等)

スペック例はこんな感じ
`Intel Xeon Platinum 8352V 2.1GHz 1P36C CPU x 2CPUs`
⇒ 1CPU(プロセッサ/ソケット) に 36コア搭載されていて、それが2つ付いてる。
※ハイパースレッドが有効化だと36コア72スレッドとなるので、論理プロセッサは144となる。

コアやスレッドに関しては以下サイトがわかりやすい。

https://chimolog.co/bto-cpu-core-thread/

## メモリ
`RAM`:データを読み書きするための作業机。Random Access Memory
　⇒`SRAM`:フリップフロップ回路。電源ONであればデータは消えない。CPUとメインメモリの間でキャッシュメモリとして使用され、高速でデータをやり取りできる。
　⇒`DRAM`:コンデンサ。時間の経過と共にデータが消えるため、上書き=リフレッシュが必要。メインメモリで使用される。
 
メモリに関しては以下の記事がとてもわかりやすいです。

https://qiita.com/kunihirotanaka/items/70d43d48757aea79de2d

## ディスク
大きくは従来型の`HDD(hard disk drive)`と`SSD(solid state drive)`に分かれる。
SSDはUSBメモリと同じくNAND型フラッシュメモリを用いたストレージで、仕組上は従来のHDDではなくメモリに近い、いわばメモリの不揮発版といえる。

### HDD
HDDは、その性能とインターフェース規格の違いから`SATA`と`SAS`に分類され、ともに大きさの違いから3.5インチ、2.5インチという2つの規格がある。
SASは"シリアルSCSI"とも言われ、SATAと比べて高速・高信頼の設計で、主に企業の重要なシステムで使用されるHDD。その分高価。
 ⇒高速というメリットは、SSDの普及によりなくなりつつある

### SSD
接続インターフェースで規格を分けると、NVMe、SATA/SASに分かれる。
アクセス速度が高速なSSDの能力を生かせるのはNVMeの方だが、対応する機器が少ない上に高価である。

### RAID
RAIDカードを搭載することで、サーバでは一般的に使用されるRAID機能を使用できる。
```
RAID0 ⇒ ストライピング。1つのデータを2台のHDDに分散して読み書き。1台壊れると使えなくなる。速度重視。
RAID1 ⇒ ミラーリング。2台のディスクにそれぞれ同じデータを同時に書き込む。安全性重視。
RAID5 ⇒ "パリティ"と呼ばれる冗長コードを全ディスクに分散して保存。同時に2台以上が壊れると回復不可能になる。
RAID6 ⇒ RAID5にもう1つ独立したパリティを追加し信頼性を向上。同時に2台のディスク装置が故障してもデータ修復が可能
```

なお、OS(Linux)上からHW周りの情報を確認する方法は以下にあります(宣伝)

https://qiita.com/dan-go/items/7720b6582a6c7f83ae86


# OSブートシーケンス
ここから本題。

## 1.電源を入れる
ポチっと

## 2.マザーボード上のBIOS/UEFI起動
現在はUEFIが主流。マザーボードの内臓チップに組み込まれている小さなプログラム

## 3.BIOS/UEFIによるハードウェアの認識・初期化
POST(Power On Self Test)でCPUやメモリ，接続されている各種拡張カード等をチェック/初期化する。
ブートデバイス（HDD、CDROM、USB）等を認識する。

## 4.ブートローダー起動
BIOS/UEFIが認識されたブートデバイス(HDD等)に記録されているブートローダー(起動プログラム)を読み込む。

・`BIOS`:
マザーボード上のNVRAMに格納されたFW。電源を投入すると設定されたデバイスの優先順位に従って、ディスクの先頭ブロックにある`MBR`内のブートローダーを検索して最初に検知したデバイスブートローダーを起動する。MBRは下記のような構造になっている。
①ブートローダ (446B)
②パーティションテーブル (64B)
③Boot Signature (2B)

・`UEFI`:
NVRAMのブートエントリーに設定された優先順位に従って、GPTで管理しているUEFIシステムパーティション(ESP)に格納されているブートローダーを起動する。EFIシステムパーティションはFAT(またはVFAT)でフォーマットされ、`/boot/efi`ディレクトリにマウントされる。

Linuxでは`GRUB`または後継の`GRUB2`というブートローダーが採用されている。


## 5.カーネルとinitramfsを読み込む
ブートローダー(GRUB Legacy/GRUB2)がカーネル(`vmlinuz`)と`initramfs`をメモリにロードし、カーネルを起動する。

### ■GRUB Legacy
・Stage1
=ブートローダがブート可能なストレージデバイスから読み込む最小限のコードセット。

・Stage1.5
=ディスク上のファイルシステムを解釈し、Stage2をロードする。ディスク上ではMBRと最初のパーティションの間に存在する。

・Stage2
=GRUBの本体。メモリにロードされたカーネルイメージを読み取り、初期RAMディスク(initramfs)をロードする拡張セット

Stage2のプログラムが設定ファイル`/boot/grub/menu.lst`を読み、その記述に従ってカーネルとinitramfsをメモリにロードする。
①セクタ番号1にあるStage1ではMBRの0x44~0x47領域に記載されているStage1.5かStage2のブロック番号を読み取る
②stage1.5の場合はstage2をファイルシステムとして読み込む。
③`/boot/grub/stage2`はmenu.lstを読み込む
④`/boot/grub/menu.lst`の記述内容に従って、カーネルとinitramfsをメモリに読み込む

```shell: /boot/grub/menu.lst
boot=/dev/sda    #ブートデバイスの定義。GRUBのインストール先
default=0        #デフォルトで起動するエントリ番号。デフォルトは1番目のエントリ
timeout=5        #メニューを表示する時間。デフォルトは5秒
hiddenmenu       #起動時に選択メニューを表示しない
splashimage=(hd0,0)/grub/splash.xpm.gz    #メニューの表示画面の画像
title CentOS (2.6.32-358.el6.i686)        #メニューに表示されるエントリ名。1番目のエントリ
root (hd0,0)     #ルートファイルシステムの指定。GRUBでは、/dev/sda1を(hd0,0)、/dev/sdaを(hd0,1)と表示
kernel /vmlinuz-2.6.32-358.el6.i686 ro root=UUID=15a29eb8-6f0c-4e3b ~ quiet    #起動するカーネルイメージファイルと起動オプションの指定
initrd /initramfs-2.6.32-358.el6.i686.img    #初期RAMディスクファイルの指定
```

### ■GRUB2
GRUB LegacyであったStage1~2がない代わりに`boot.img`と`core.img`、動的にロードされる複数のモジュールで構成される。
以下はBIOSの場合
①セクタ番号1にある`boot.img`を読み込む
②セクタ番号2~64の`core.img`で`grub.cfg`をもとに少数のモジュールを動的リンクする
③その後`GRUB`を起動し、カーネルとinitramfsをロードする

UEFIの場合はEFIパーティション内の FAT32 ファイルシステム上にある`shim.efi`というブートローダを呼び出す。

### ■GRUB2-設定ファイル
設定ファイルは以下。
`/boot/grub/grub.cfg`：設定ファイル(BIOS)
`/boot/efi/EFi/centos/grub.cfg`：設定ファイル(UEFI)
`/usr/lib/grub/i386-pc`：モジュールの置かれたディレクトリ
`/etc/grub.d`：設定ファイル grub.cfg の生成時に実行するスクリプトが置かれたディレクトリ

```shell:/boot/grub/grub.cfg(抜粋。BIOS起動の場合)
set root='hd0,msdos1'          #ブートするカーネルと initrd の置かれルパ＝ディションとディスクを ‘ディスク,パーティション’ で指定
linux /bootvmlinuz-3.9.5 root=/dev/sda1 ro    #カーネルと引数を指定。ランレベルを追加すると指定したランレベルで立ち上げる。GRUB1ではkernelで始まり、GRUB2ではlinuxで始まる
initrd /boot/initramfs-3.9.5.img    #initramfs を指定。GRUB1ではパーティションは0始まり、GRUB2では 1で始まる
```
ディスクとデバイス名との対応は`/boot/grub/device.map`に格納されている。

```shell:/boot/efi/EFi/centos/grub.cfg(抜粋。UEFI起動の場合)
set root='hd0,gpt2'
linuxefi /vmlinuz-3.10.0-1160.el7.x86_64 root=UUID=6a4853a1-61c8-486c-8177-966ad3cb2cba ro
initrdefi /initramfs-3.10.0-1160.el7.x86_64.img
```

※上記で設定は確認できるが、GRUB2の設定ファイルは直接編集NG。設定を変更したい場合は`/etc/default/grub`を修正し、`grub-mkconfig`コマンドにて反映する必要がある。

```shell:/etc/default/grub
GRUB_TIMEOUT=5    #デフォルトのOSを起動するまでの秒数
GRUB_DISTRIBUTOR="$(sed 's, release .*$,,g' /etc/system-release)"    #メニューエントリに表示するコマンド
GRUB_DEFAULT=saved    #デフォルトで起動するOSの番号またはメニュータイトル。savedは前回起動したもの
GRUB_DISABLE_SUBMENU=true    #全てのOSをフラットメニューで並べる
GRUB_TERMINAL_OUTPUT="console"    #コンソール出力する
GRUB_CMDLINE_LINUX="crashkernel=auto rhgb quiet"    #カーネルに渡すブートオプション
GRUB_DISABLE_RECOVERY="true"    #メニューにリカバリモードを表示する
```
反映コマンド
```
grub2-mkconfig -o /boot/efi/EFI/centos/grub.cfg
```
GRUB_CMDLINE_LINUXの変更が適用されているかは、以下コマンドで確認可能
```
cat /proc/cmdline
```

上記設定により表示されたブートメニューより、ユーザがどれか1つのOSを選択すると、ブートローダはそのOSのカーネルの実体バイナリ`vmlinuz-*`と RAMディスク`initramfs-*.img`をメモリ上にロードし、カーネル本体の先頭アドレスにジャンプした後、自身の初期化シーケンスを実行する。


## 6.カーネルが初期RAMディスク(initramfs)を読み込む
・カーネルが自身の初期化処理を終えると、cpioでアーカイブされgzipで圧縮された`initramfs`をメモリ上に展開し、一時的なルートファイルシステムとしてマウントする。

※initramfsは、ルートディレクトリをcpioという形式でアーカイブし、gzipで圧縮したもので、/bin /lib /dev などのディレクトリがあり、それぞれのディレクトリには必要最低限のコマンドやデバイスファイルなどが含まれている。小さなLinuxシステムであるともいえる。
実際に/bootにあるinitramfsの中身は以下コマンドで確認できる。
```
zcat initramfs-xxx.img | cpio -idv
```

また、レスキューブートからの`dracut`コマンドでinitramfsを自前で作成することも可能。
```
※例
mount --bind /proc /mnt/sysimage/proc
mount --bind /dev /mnt/sysimage/dev
mount --bind /sys /mnt/sysimage/sys
chroot /mnt/sysimage/
cp /boot/initramfs-4.19.14-300.fc29.x86_64.img /boot/initramfs-4.19.14-300.fc29.x86_64.img.bak
dracut -f /boot/initramfs-4.19.14-300.fc29.x86_64.img 4.19.14-300.fc29.x86_64
```
`lsinitrd imageFILE`コマンドで中身の確認もできる。

## 7.initプロセス実行
・initramfsをマウントしたカーネルは、initramfs内のinitからシンボリックリンクされた`systemd`を起動し、以下の流れで処理を行う。
```shell
initramfs の /initコマンドが実行される。
①/bin/mknod コマンドにより必要なデバイスファイルを作成
②/bin/mkdir コマンドにより/tmp、/proc、/sys、/devなど必要なディレクトリを作成
③/bin/mount コマンドにより擬似ファイルシステムをマウント
④/bin/mount コマンドによりディスク内のルートファイルシステムを/sysrootにマウント
⑤/sbin/switch_root コマンドによりinitramfsからディスク内のルートファイルシステムに切り替え
```
※SysV initの場合: initramfs内のinitを起動し、ディスク内のルートファイルシステムをマウントする


## 8./sbin/initを実行し、起動シーケンスを開始
・カーネルは切り替えたディスク内のルートファイルシステムの`/sbin/init`を実行し、シンボリック先の`/lib/systemd/sytemd`を再実行して各ユニット設定ファイルを参照し起動シーケンスを開始する。
Initは実行される最初のプロセスであり、PIDは常に1となる。

※SysV initではディスク内のルートファイルシステムの/sbin/initを実行し、`/etc/inittab`を参照して起動シーケンスを開始する

※ブートシーケンス内でsystemdは2回起動されることになる。
initramfs内のsystemdはプロセス空間を制御し、必要なファイルシステムをマウントするために必要である一方、ディスク内のsystemdはシステムを完全に起動し、サービスを管理するために必要。

カーネル起動中のメッセージは`dmesg`コマンドで確認できる。

## 9.ターゲットにしたがってサービスを起動する
`systemd`では、Unitという考え方が採用されており、OS の起動シーケンスの起点となる`default.target`に記述されている依存関係を満たすように、各種ユニットを立ちあげる。(udev によりハードウェアを扱えるようにしたり、各種デーモンなど他のプロセスを実行する)

はじめに`/etc/systemd/system/default.target`を読みに行くことで指定されたターゲットで処理される。
また以下コマンドで、default.target を起点としてそこから要求される形でどのようなユニットが起動されたかを確認できる。
```
systemctl list-dependencies
```
※SysV initではランレベルとなる。

## 10.ログインプロンプト/ログイン画面からログインする
ここでようやくいつものログイン画面へ


# インストールって？
前述④のブートデバイスが外部インストールメディアか、ネットワーク経由でのPXEかとなる。
簡単な流れは以下の通り。
```
1.OSインストーラーをメモリにロードする。
2.インストーラを起動し、インストールするHDD/SSDを指定する。
3.指定したHDD/SSDをファイルシステムにフォーマット(xfs,ext4など)
4.ブートセクタの作成及びOS機能を提供するプログラムやライブラリファイルをファイルシステムにインストール(配置)
```
インストール後はマシンを起動すると前述④のブートデバイスが配置したHDD/SSDのブートセクタからブートローダをメモリに展開し、以降の流れは同じ。


# PXEブートって？
`PXE (Preboot eXection Environment)` はネットワークブートのプロトコル。クライアントがネットワーク上のサーバーからNetwork Bootstrap Programをダウンロードし、NBPがカーネルとinitramfsをダウンロードして起動する。

### PXEサーバ
”PXEサービス”が存在するわけではなく、PXEブートに必要なDHCP,TFTPサービスを提供できるサーバのことを指す
概要としては以下のサイトがわかりやすい

https://runningdog.mond.jp/blog/2024/04/28/pxe%E3%83%96%E3%83%BC%E3%83%881-%E5%9F%BA%E6%9C%AC/


## 概要
### Network Bootstrap Program (NBP)
`pxelinux.0`: このファイルは SYSLINUX プロジェクトの一部で、PXE ブートのためのブートローダ
`grubx64.efi`: これは GRUB2 ブートローダの UEFI バージョンで、ネットワークブートにも対応している。
これら以外にも、特定の環境や要件に応じた独自の NBP を使用することもある。

以下ブートの流れ。NBPファイルはメモリ上にロードされ、ディスクには保存されない。

```
1.クライアントでブート順序をPXE優先にして電源ON ※NICにてPXEブートを有効に設定する必要がある
2.クライアントが持つPXE対応のNICは、接続されたネットワークからDHCP DISCOVERパケットを送り、DHCPサーバはこのNICのMacアドレスに対してIPアドレスを割り当てる(DHCP OFFER,Request,Ack)。
3.クライアントはブートローダ(NPBファイル)のパスを問い合わせ、DHCPサーバはdhcpd.confに定義されたTFTPサーバのIPアドレスとブートローダのパスを回答する。
4.クライアントはブートローダをダウンロードして起動し、設定ファイル(grub.cfg or pxelinux.cfg)に定義されたOSカーネル(vmlinuz)とRAMイメージ(initrd.img)をTFTPサーバからダウンロードして起動する。
    ※grub.cfgやpxelinux.cfgファイルには、インストーラ(またはレスキューシステム)が使用するkickstartファイルとインストールで使用するレポジトリの場所を定義することができる。
5.起動されたインストーラは、kickstartファイルの定義に従って(本番用の)OSのインストール処理を行う。インストールで使用するパッケージはレポジトリからダウンロードして取得し、インストーラはサーバのストレージにOSをインストールして完了。以降は通常のローカルブートへ
```

## 設定例
### DHCPサーバの設定

```
# yum install -y dnsmasq
```

```conf:/etc/dnsmasq.conf
# DHCP設定
interface=eth0                                 # ネットワークインターフェースを指定
dhcp-range=192.168.1.100,192.168.1.200,12h     # DHCPレンジとリース時間を指定
dhcp-boot=pxelinux.0                           # 起動させるPXEブートローダ
pxe-service=x86PC, "PXE Boot", pxelinux        # PXEサービス

# TFTP設定
enable-tftp
tftp-root=/var/lib/tftpboot                    # TFTPのルートディレクトリ
```
```
# systemctl start dnsmasq
```

### TFTPサーバの設定
```
yum install -y tftp-server
```
```conf:/etc/xinetd.d/tftp
service tftp
{
  socket_type          = dgram
  protocol             = udp
  wait                 = yes
  user                 = root
  server               = /usr/sbin/in.tftpd
  server_args          = -s /var/lib/tftpboot
  disable              = no                # ここをnoに変更
  per_source           = 11
  cps                  = 100 2
  flags                = IPv4
}
```
```
# systemctl restart xinetd
```
PXEブートファイルの配置
```
# mkdir -p /var/lib/tftpboot/pxelinux.cfg
# yum install -y syslinux
# cp /usr/share/syslinux/pxelinux.0 /var/lib/tftpboot/
# cp /usr/share/syslinux/menu.c32 /var/lib/tftpboot/
# cp /usr/share/syslinux/memdisk /var/lib/tftpboot/
# cp /usr/share/syslinux/mboot.c32 /var/lib/tftpboot/
# cp /usr/share/syslinux/chain.c32 /var/lib/tftpboot/
```

PXE設定ファイルを作成
```conf:/var/lib/tftpboot/pxelinux.cfg/default
DEFAULT menu.c32
PROMPT 0
TIMEOUT 300
ONTIMEOUT local
MENU TITLE PXE Boot Menu

LABEL local
    MENU LABEL Boot from local drive
    LOCALBOOT 0

LABEL linux
    MENU LABEL Install Linux
    KERNEL vmlinuz               # カーネルイメージのパス
    APPEND initrd=initrd.img     # 初期RAMディスクのパス
```

OSイメージの準備
例えば、CentOSをインストールする場合、必要なカーネルイメージ（vmlinuz）と初期RAMディスク（initrd.img）をTFTPルートディレクトリにコピー
```
# cp /path/to/vmlinuz /var/lib/tftpboot/
# cp /path/to/initrd.img /var/lib/tftpboot/
```

クライアントがネットワークからブートするように設定し、PXEブートの手順を開始すると、TFTPサーバから必要なファイルがダウンロードされてOSのインストールが開始される。

参考↓

https://qiita.com/sakai00kou/items/bc6ae4d6b4cacc4b8af9

https://runningdog.mond.jp/blog/2024/04/28/pxe%E3%83%96%E3%83%BC%E3%83%881-%E5%9F%BA%E6%9C%AC/

以上。


### 参照
LPICレベル2 赤本
https://www.ether-zone.com/learning-lpic-2-3/#toc2
https://qiita.com/taichitk/items/b3b69705be0e270e9f6e
https://www.tomoyan.net/linux/dracut