# 開機程序 (Booting Process)
###### tags: `Hardware` `Embedded System` `Bootstrap` `Bootloader` `MCU` `SoC` `x86` `ARM` `Linux` `Kernel` `GRUB` `Operating System` `Computer Architecture`
筆記彙整現今在不同硬體 (PC, MCU, SoC)、指令集 (x86, ARM)、作業系統類型 (Bare Metal, RTOS, GPOS) 上的多樣開機程序和技術。
> Booting 有如飛機行前的一系列自檢程序,以確保所有機組一切正常運作:機外檢查、冷艙啟動 APU、警告測試、啟動引擎、襟翼調整、陀螺儀校正、制定飛行計畫、貨物簽核、載重計算、...
> 
> (Source: [Preflight Checklists for New Pilots - Kingsky FA](https://www.kingskyfa.com/post/preflight-checklists-for-new-pilots/))
## Boot Process Overview



Refs:
- [第二十章、開機流程、模組管理與 Loader - for CentOS 5.x - 鳥哥私房菜](https://linux.vbird.org/linux_basic/centos5/0510osloader-centos5.php)
- [開機流程與GRUB](http://teaching.idv.tw/Course/joelinux/article/grub.html)
- [Boots And Shutdowns - Linux System Administrators Guide](https://tldp.org/LDP/sag/html/boots-and-shutdowns.html)
### [Step 1] Booting from ROM (BIOS, UEFI, BootROM)
*初次探索:CPU 第一次嘗試認識系統環境*
:::warning
**目標: 最小限度檢測所需基礎硬體和探索,並成功載入 UEFI 畫面。**
:::
當電路上的 reset 訊號一觸發,flash、EEPROM 上的 Code 即刻被搬運到 Processor 中執行,始測試基本 I/O 裝置 (e.g., 螢幕、鍵盤、硬碟) 和主記憶體 RAM (嵌入式可能需要 SRAM -> DRAM),並從 flash、CMOS 載入系統資訊,從 RTC (Real-Time Clock) 晶片載入時間等。
成功執行啟動任務和裝置測試檢查後,將執行權轉移給 1st Bootloader。
> BIOS 起始位址和系統環境配置可由硬體撥桿或韌體設定之:
> 
> (Source: [Embedded Linux Boot Process - Damon Chen](https://damon-chen.medium.com/embedded-linux-booting-process-e40f1fa068e8))

> 此階段僅負責正常啟動和簡單測試工作,不涉及其他進階功能和複雜系統週邊環境 setup。
:::info
**網路開機**
若 BIOS 支援網路開機並啟用,則此階段需要額外網路客戶端初始化、測試及環境設置程序,例如初始化支援 PXE (Preboot Execution Environment) 的 NIC (Network Interface Controller) 網卡後,開啟基於 UDP/IP、DHCP、TFTP 或 BootP 協定的客戶端 socket,並從一個指定的開機伺服器下載開機載入程式。
> 截至 2015, PXE 以成為 UEFI 的標準。
[Preboot Execution Environment - Wiki](https://en.wikipedia.org/wiki/Preboot_Execution_Environment)
:::
:::info
**UEFI vs. ACPI vs. 作業系統**
- UEFI 為業界開放標準,各家廠商實作上存在差異。
- 在 UEFI 系統中 ACPI 可由 driver 受 OS 進行電源組態設置和管理,例如:
- **系統電源管理:** 整機電源配置和調控
- **設備電源狀態:** USB 供電、PCIE 電壓、NIC 等 chipset 開關
- **處理器電源管理:** 調控 CPU 狀態 (核心頻率降載 / 關閉)
- **系統事件:** 設備監聽 & 喚醒、OS 監聽註冊之事件 hooks 再轉發至各應用層 dispatch、...
- **其他:** 溫度管理、硬體 controller 管理、接口熱插拔、電池管理、...
例:
- ACPI 睡眠模式
```sh
ACPI: EC: interrupt blocked
ACPI: PM: Preparing to enter system sleep state S3
ACPI: EC: event blocked
ACPI: EC: EC stopped
ACPI: PM: Saving platform NVS memory
# disable non-boot CPUs
...
```
- ACPI 從睡眠中回復
```sh
ACPI: PM: Low-level resume complete
ACPI: EC: EC started
ACPI: PM: Restoring platform NVS memory
# enable non-boot CPUs
...
ACPI: PM: Waking up from system sleep state S3
ACPI: button: The lid device is not complant to SW_LID.
ACPI: EC: interrupt unblocked
# enable PCH chipsets on pcieports
...
ACPI: EC: event unblocked
# wake up devices
...
```
更多:
- [ACPI - Wiki](https://zh.wikipedia.org/zh-tw/%E9%AB%98%E7%BA%A7%E9%85%8D%E7%BD%AE%E4%B8%8E%E7%94%B5%E6%BA%90%E6%8E%A5%E5%8F%A3)
- [ACPI 和 APIC 有什麽關係?](https://stenlyho.blogspot.com/2009/02/acpiapic.html)
- [一合盖就发热,windows笔记本,为什么连个待机都做不好? - 差评君](https://www.youtube.com/watch?v=0w3WLKfc6Cc): 應用層註冊之 event listener 的錯誤處理導致電腦耗電發熱
:::
#### 主要任務
- 關閉 / 重設 [看門狗計時器](https://zh.wikipedia.org/zh-tw/%E7%9C%8B%E9%96%80%E7%8B%97%E8%A8%88%E6%99%82%E5%99%A8)。

- 初始化 exception/interrupt handler table、時鐘、通訊、基礎驅動、POST (Power-On Self Test)。
- 初始化存儲、DMA、測試、驗證、解壓、更新 ... 等基礎設備檢驗服務。

:::info
**多核處理器啟動 & 測試**
1. 由 BIOS 或硬體指派某一核心為 Bootstrap Processor (BSP),其他核心則為 Application Processor (AP)。
2. BIOS 喚醒 BSP 執行 booting 程序。
3. BSP 從 BIOS 讀取多核心參數。
4. BSP 再透過 Interprocessor Interrupt (IPI) 喚醒其他 AP 執行 booting 程序,將欲執行的 reset code (bootloader) 的位址 reset vector (reset address) 寫入 AP 的 LAPIC (local advanced programming interrupt controller) 的 register。
1. BSP 發送 IPI: `INIT`。
2. BSP 發送 IPI: `STARTUP`。
3. AP 初始化: 設定 exception vectors、啟用 LAPIC interrupt、認識系統周遭環境 (已被 BSP 初始化)、...
6. BSP polling 直到 AP 的 state 改變。
[CSE 506 Lab 4 - Multiprocessor Support - Cherie Hsieh](https://yushuanhsieh.github.io/post/2021-04-23-life/)
例:
- 關閉 4 核處理器之 non-boot CPUs 以進入睡眠模式
```sh
Disabling non-boot CPUs ...
smpboot: CPU 1 is now offline
smpboot: CPU 2 is now offline
smpboot: CPU 3 is now offline
```
- 啟用 4 核處理器之 non-boot CPUs 以從睡眠模式回復
```sh
Enabling non-boot CPUs ...
x86: Booting SMP configuration:
smpboot: Booting Node 0 Processor 1 APIC 0x2
CPU1 is up
smpboot: Booting Node 0 Processor 2 APIC 0x4
CPU2 is up
smpboot: Booting Node 0 Processor 3 APIC 0x6
CPU3 is up
```
更多:
- [ACPI 和 APIC 有什麽關係?](https://stenlyho.blogspot.com/2009/02/acpiapic.html)
:::
#### 其他架構
- **MCU (e.g., ARM Cortex-M)**:
在 MCU 架構下,處理器大多沒有 MMU、單核或低頻的 SMP、目標作業系統為 RTOS 等。在此階段 MCU 特別之處在於處理器可直接讀取 ROM 上的 code 執行,而無須 RAM 參與。
MCU 同樣在之後 1st bootloader 可執行更完整的客製化檢測及初始化服務。
更多:
- [整合RTOS運行防護 TrustZone強化物聯網安全 - Joseph Yiu](https://www.2cm.com.tw/2cm/zh-tw/tech/C99CAB2F8A9944D080ECFDC7348AA3F1)

- :+1: [燒錄單體 MCU,從 Bootloader 燒錄器開始,製作完整的 Arduino 控制器。 - 阿吉米德](https://www.youtube.com/watch?v=TI4SC3Zi6pI)
- **SoC (System on a Chip)**:
作業系統 x 檔案系統 x 驅動應用 打包放在 Nand flash 中,首先先將 SPL (Secondary Program Loader) 載入侷促的 SRAM 空間中並跳轉執行,以開始初始化 DRAM 及後續必要環境之工作,再將 uboot 搬進 DRAM 並跳轉執行,再邊解壓邊將真正作業系統及檔案系統 ... 載入。
### [Step 2-1] Stage I Bootloader (on MBR, GPT Sector)
*二次探索: 為載入「真。kernel」前做準備*
:::warning
**目標: 在基礎系統「可運作」情況下 (CPU、RAM、IO、硬碟 or 網路),將第一部份 bootloader 嘗試從硬碟載入 RAM。**
:::
BIOS 程式將第一顆硬碟的第一個實體 512 位元組資料磁區載入主要記憶體後便開始執行。
Stage I 通常會置放於 MBR (主開機紀錄),即 446 Bytes 的位置,但也可以直接置於獨立 `/boot` 的第一個sector (每 OS 各一個),稱為 Boot Sector,此 Sector 因容量過小,只有 512 bytes,所以大部分程式碼在 Stage II 執行。
> **註:** 若啟用網路開機,則略過執行本地硬碟的 Bootloader,直接跳轉到 OS init 程序。
[Master Boot Record (MBR) - Wiki](https://zh.wikipedia.org/zh-tw/%E4%B8%BB%E5%BC%95%E5%AF%BC%E8%AE%B0%E5%BD%95) vs. [GUID Partition Table (GPT) - Wiki](https://zh.wikipedia.org/zh-tw/GUID%E7%A3%81%E7%A2%9F%E5%88%86%E5%89%B2%E8%A1%A8)
### [Step 2-2] Stage II Bootloader (GRUB, LILO)
> 最有名的 Stage II 大概算是 GRUB 及 LILO 了,它們提供選單,以讓使用者在開機時可以選擇用哪個 OS 開機。從此,Bootloader 就交給 OS 的 kernel 進行第三階段工作。
#### GRUB vs. LILO
LILO沒有CLI介面可下指令,所有開機參數都寫在設定檔(/etc/lilo.conf)及MBR裡,萬一參數有錯,則系統將無法順利開機,得使用具開機修護功能的floppy 來補救。
GRUB (GRand Unified Bootloader)是管理開機的Boot Loader管理程式,與早期LILO角色相當,但因它具較多功能,早在RH9時代就已取代 LILO成為預設Boot Manager。
- `/boot/grub/grub.conf`
```sh=
password --md5 $1$H2GMv1$7l/WWRBL/Vb1AkFeQx8ti1 # 進入GRUB畫面,要 md5 hash 密碼
splashimage=(hd0,0)/grub/splash.xpm.gz # GRUB Splash 顯示的 splash.xpm.gz 圖檔
hiddenmenu # stanza的內容將不會被顯示在開機時
title CentOS (2.6.18-92.el5) # stanza 0的開始,標籤名稱
root (hd0,0) # 下面所列的檔案都以hd0,0為參考點
kernel /vmlinuz-2.6.18-92.el5 ro root=LABEL=/ # kernel檔及root filesystem
initrd /initrd-2.6.18-92.el5.img # 要載入執行的RAM DISK檔
title Windows XP # stanza 1的標籤
rootnoverify(hd0,1) # root是 hd0,1,不要 mount在GRUB
chainloader +1 # 由hd0,1第一sector來開機
```
[Ubuntu GRUB Linux Bootloader and Configuration - Sagar](https://adamtheautomator.com/ubuntu-grub/)
### [Step 3] Kernel 開機載入程式
*最後一哩:載入「真。kernel」腳本,並繼續進階裝置自檢、探索、初始化*
:::warning
**目標: 這時系統已經對基礎設施的檢查及初始化有了一定程度的掌握,再來在成為「真。kernel」可使用前,仍有許多前置作業待完成 (e.g., PCIe buses、drivers、filesystems、RAID、NUMA systems、...),完全沒問題才會交給 init。**
:::
當一切必要基礎 driver 設備都準備就緒後,作業系統的開機載入程式就會載入 kernel image 並掛載 RAM 式檔案系統 (initramfs) 於主記憶體中。
核心可以直接存取 initramfs 的內容。initramfs 包含一個名為 init 的小執行檔,它可以處理實體根目錄檔案系統的裝載。其可用於初始化往後大量存取媒介 (e.g., SCSI 或 RAID) 的特殊硬體驅動程式,這些驅動以獨立於 kernel 存在,因此需透過 initramfs 外掛掛載以初始化。
> 如果系統沒有本地硬碟,則 initramfs 必須向核心提供 `/` 根目錄檔案系統。這可以藉由 iSCSI 或 SAN 這類網路區塊設備來完成,但也可以使用 NFS 做為 `/` 根目錄設備。
initramfs 上的 init 目標是載入完整 `/` 根目錄檔案系統,並且提供包含 udev 之大量儲存控制器的設備驅動程式。找到真正 `/` 根目錄檔案系統後,將會檢查是否有錯誤並進行 mount。若成功,將會清除 initramfs 並執行真正 `/` 根目錄檔案系統上的 init 程式。
[initramfs 原理探讨:为什么需要 initramfs -《深度探索 Linux 操作系统 : 系统构建和原理解析》 学习笔记 - vita](https://yifengyou.github.io/vita/docs/%E6%9E%84%E5%BB%BAinitramfs/initramfs%E5%8E%9F%E7%90%86%E6%8E%A2%E8%AE%A8.html)
:::warning
**Devicetree 裝置模組化**
階級式可擴充裝置模組化的誕生,使得同一 kernel source code 可編譯並支援各式不同硬體平台;port 到別的硬體平台也相對容易,更無需改動既有 kernel 架構。
Device tree 以 hierarchical tree 形式「描述」各硬體關係和互動模式,並帶有 kernel 如何驅動的資訊。
例:「如何初始化 CPU 之控制 I²C bus 的子系統?」
在 Device tree 中新增 I²C controller 的節點來描述此裝置「driver 初始化方式」、「函式呼叫接口 & 參數」、「register 範圍」、「其他裝置互動 & 關係」等。
如此,如有其他裝置需要「使用」I²C bus,只需在此 I²C controller 節點下新增子節點,標註「對應 driver 存取」、「bus 地址」、「初始參數配置」等。
並在 booting 時根據 ++依平台 & 架構編譯的 `.dtb` 規則++,在 boot time 時「動態」識別、檢查及正確載入對應的 device drivers。
> Device tree source 參考: [/arch/arm64/boot/dts/](https://elixir.bootlin.com/linux/v6.15.3/source/arch/arm64/boot/dts)。

[What is Linux device tree? - VOCAL Technologies](https://vocal.com/resources/development/what-is-linux-device-tree/)
:::info
**x86 使用另一套業界裝置模組化擴充標準 ---- ACPI 的 Differentiated System Description Table。**
[Why do embedded systems need device tree while pcs don't? - StackExchange](https://unix.stackexchange.com/questions/399619/why-do-embedded-systems-need-device-tree-while-pcs-dont)
:::
### [Step 4] Kernel Init Process
*起飛爬行: 執行硬碟上的第一支可運作的程式*
:::warning
**目標: 雖然系統已順利 bootstrapped ---- 低階系統功能已全數可正常工作,但後續高階系統服務的初始化工作 (e.g., 網卡設定、防火牆初始化、使用者登入管理 daemon、GUI display manager、D-Bus & Polkit 初始化、...) 仍需交給 root 權限下的 init 程序來完成。**
> `/etc/*` 下的配置文件和腳本大概都是在此階段 init 程序完成執行。
:::
init 會透過提供數個不同層級所提供的不同功能來負責系統實際開機作業。
最終將執行權交給作業系統第一支 process,再由它創造可執行 user program 之作業環境。
[Linux 開機程序 - Novell Docs](https://www.novell.com/zh-tw/documentation/sles11/book_sle_admin/data/sec_boot_proc.html)
### Systemd
[Understanding and administering systemd - Fedora Docs](https://docs.fedoraproject.org/en-US/quick-docs/systemd-understanding-and-administering/)
- **Overview:**

- **System Manager:**
- **Bootup:**
> When `systemd` starts up the system, it will activate all units that are dependencies of `default.target` (as well as recursively all dependencies of these dependencies). Usually, `default.target` is simply an alias of `graphical.target` or `multi-user.target`.
>

- **Shutdown:**

- **User Manager:**
- **Startup:**
> The system manager starts the `user@uid.service` unit per user, which launches a separate unprivileged instance of `systemd` for each user — the user manager.
>

Refs:
- [systemd.service — Service unit configuration - freedesktop.org](https://www.freedesktop.org/software/systemd/man/latest/systemd.service.html)
- [bootup — System bootup process - freedesktop.org](https://www.freedesktop.org/software/systemd/man/latest/bootup.html)
- [System and Service Manager - Systemd](https://systemd.io)
#### Basic Usage
```sh=
# system/user-wide targets/services
systemctl (--user) list-units --type=target --all # or --type=service --all
```
#### Install Systemd Service Targets
```sh=
systemctl (--user) enable <service-name>
systemctl (--user) disable <service-name> # uninstall
systemctl (--user) status <service-name>
# try starting once
systemctl (--user) start <service-name>
```
```sh=
# if service has changed, systemd should reconfigure for it
systemctl (--user) daemon-reload
# reload the service
systemctl (--user) enable <service-name>
```
```sh=
# debug log
journalctl (--user) -u <service-name>
```
```sh=
# run user services even when not logged in (default off)
loginctl enable-linger <user-name>
```
#### Service Configuration
For example:
- `/etc/systemd/system/sshd.service`
-> `/lib/systemd/system/ssh.service`
```ini=
[Unit]
Description=OpenBSD Secure Shell server
Documentation=man:sshd(8) man:sshd_config(5)
After=network.target auditd.service
ConditionPathExists=!/etc/ssh/sshd_not_to_be_run
[Service]
EnvironmentFile=-/etc/default/ssh
ExecStartPre=/usr/sbin/sshd -t
ExecStart=/usr/sbin/sshd -D $SSHD_OPTS
ExecReload=/usr/sbin/sshd -t
ExecReload=/bin/kill -HUP $MAINPID
KillMode=process
Restart=on-failure
RestartPreventExitStatus=255
Type=notify
RuntimeDirectory=sshd
RuntimeDirectoryMode=0755
[Install]
WantedBy=multi-user.target
Alias=sshd.service
```
- `/etc/systemd/system/graphical.target.wants/accounts-daemon.service`
-> `/lib/systemd/system/accounts-daemon.service`
```ini=
[Unit]
Description=Accounts Service
# In order to avoid races with identity-providing services like SSSD or
# winbind, we need to ensure that Accounts Service starts after
# nss-user-lookup.target
After=nss-user-lookup.target
Wants=nss-user-lookup.target
[Service]
Type=dbus
BusName=org.freedesktop.Accounts
ExecStart=/usr/libexec/accounts-daemon
Environment=GVFS_DISABLE_FUSE=1
Environment=GIO_USE_VFS=local
Environment=GVFS_REMOTE_VOLUME_MONITOR_IGNORE=1
...
# In addition to the below paths,
# - /var/lib/AccountsService/users/ and
# - /var/lib/AccountsService/icons/
# are read/written by the daemon. See StateDirectory= above.
#
# The paths in /etc are not directly modified by AccountsService, but by
# usermod, which it spawns.
#
# The paths in /var/log and /var/mail are touched by useradd/userdel when adding
# or deleting users.
ReadWritePaths=\
-/etc/gdm3/daemon.conf \
/etc/ \
-/proc/self/loginuid \
-/var/log/lastlog \
-/var/log/tallylog \
-/var/mail/
ReadOnlyPaths=\
/usr/share/accountsservice/interfaces/ \
/usr/share/dbus-1/interfaces/ \
/var/log/wtmp \
/run/systemd/seats/
[Install]
# We pull this in by graphical.target instead of waiting for the bus
# activation, to speed things up a little: gdm uses this anyway so it is nice
# if it is already around when gdm wants to use it and doesn't have to wait for
# it.
WantedBy=graphical.target
```
:::info
**[Service] type=forking 啟動背景 Daemon**
- [Why fork() twice while daemonizing? - Stack Overflow](https://stackoverflow.com/questions/31485204/why-fork-twice-while-daemonizing)
- [Linux Systemd type=simple 和 type=forking 的区别 - CSDN](https://blog.csdn.net/l641208111/article/details/130064689)
:::
:::info
**使用者程序登出後持續運行**
```sh!
sudo loginctl enable-linger <user>
```
[Oracle Linux: Configure Systemd to Enable User Processes to Continue to Run After Logout - Oracle Docs](https://docs.oracle.com/en/operating-systems/oracle-linux/8/obe-systemd-linger/)
:::