19.1.2 BIOS, boot loader 與 kernel 載入
我們在第二章曾經談過簡單的開機流程與 MBR 的功能,以及大容量磁碟需要使用的 GPT 分割表格式等。
詳細的資料請再次回到第二章好好的閱讀一下,我們這裡為了講解方便起見,將後續會用到的專有名詞先做個綜合解釋:
BIOS:不論傳統 BIOS 還是 UEFI BIOS 都會被簡稱為 BIOS;
MBR:雖然分割表有傳統 MBR 以及新式 GPT,不過 GPT 也有保留一塊相容 MBR 的區塊,因此,底下的說明在安裝 boot loader 的部份,
鳥哥還是簡稱為 MBR 喔!總之,MBR 就代表該磁碟的最前面可安裝 boot loader 的那個區塊就對了!
BIOS, 開機自我測試與 MBR/GPT
我們在第零章的計算機概論就曾談過電腦主機架構,
在個人電腦架構下,你想要啟動整部系統首先就得要讓系統去載入 BIOS (Basic Input Output System),並透過 BIOS
程式去載入 CMOS 的資訊,並且藉由 CMOS 內的設定值取得主機的各項硬體設定,
例如 CPU 與周邊設備的溝通時脈啊、開機裝置的搜尋順序啊、硬碟的大小與類型啊、
系統時間啊、各周邊匯流排的是否啟動 Plug and Play (PnP, 隨插即用裝置) 啊、
各周邊設備的 I/O 位址啊、以及與 CPU 溝通的 IRQ 岔斷等等的資訊。
在取得這些資訊後,BIOS 還會進行開機自我測試 (Power-on Self Test, POST) (註1)。
然後開始執行硬體偵測的初始化,並設定 PnP 裝置,之後再定義出可開機的裝置順序,接下來就會開始進行開機裝置的資料讀取了。
由於我們的系統軟體大多放置到硬碟中嘛!所以 BIOS 會指定開機的裝置好讓我們可以讀取磁碟中的作業系統核心檔案。
但由於不同的作業系統他的檔案系統格式不相同,因此我們必須要以一個開機管理程式來處理核心檔案載入 (load) 的問題,
因此這個開機管理程式就被稱為 Boot Loader 了。那這個 Boot Loader
程式安裝在哪裡呢?就在開機裝置的第一個磁區 (sector) 內,也就是我們一直談到的 MBR
(Master Boot Record, 主要開機記錄區)。
那你會不會覺得很奇怪啊?既然核心檔案需要 loader 來讀取,那每個作業系統的 loader 都不相同,
這樣的話 BIOS 又是如何讀取 MBR 內的 loader 呢?很有趣的問題吧!其實 BIOS 是透過硬體的 INT 13 中斷功能來讀取 MBR
的,也就是說,只要 BIOS 能夠偵測的到你的磁碟 (不論該磁碟是 SATA 還是 SAS 介面),那他就有辦法透過 INT 13
這條通道來讀取該磁碟的第一個磁區內的 MBR 軟體啦!(註2)這樣 boot loader 也就能夠被執行囉!
Tips
我們知道每顆硬碟的最前面區塊含有 MBR 或 GPT 分割表的提供 loader 的區塊,那麼如果我的主機上面有兩顆硬碟的話,
系統會去哪顆硬碟的最前面區塊讀取 boot loader 呢?這個就得要看 BIOS 的設定了。
基本上,我們常常講的『系統的 MBR』其實指的是 第一個開機裝置的 MBR 才對!
所以,改天如果你要將開機管理程式安裝到某顆硬碟的 MBR 時,
要特別注意當時系統的『第一個開機裝置』是哪個,否則會安裝到錯誤的硬碟上面的 MBR 喔!重要重要!
Boot Loader 的功能
剛剛說到 Loader 的最主要功能是要認識作業系統的檔案格式並據以載入核心到主記憶體中去執行。
由於不同作業系統的檔案格式不一致,因此每種作業系統都有自己的 boot loader 啦!用自己的 loader
才有辦法載入核心檔案嘛!那問題就來啦,你應該有聽說過多重作業系統吧?也就是在一部主機上面安裝多種不同的作業系統。
既然你 (1)必須要使用自己的 loader 才能夠載入屬於自己的作業系統核心,而
(2)系統的 MBR 只有一個,那你怎麼會有辦法同時在一部主機上面安裝 Windows 與 Linux 呢?
這就得要回到第七章的磁碟檔案系統去回憶一下檔案系統功能了。
其實每個檔案系統 (filesystem, 或者是 partition) 都會保留一塊開機磁區 (boot sector) 提供作業系統安裝 boot loader ,
而通常作業系統預設都會安裝一份 loader 到他根目錄所在的檔案系統的 boot sector 上。如果我們在一部主機上面安裝 Windows
與 Linux 後,該 boot sector, boot loader 與 MBR 的相關性會有點像下圖:
圖19.1.1、boot loader 安裝在 MBR, boot sector 與作業系統的關係
如上圖所示,每個作業系統預設是會安裝一套 boot loader 到他自己的檔案系統中 (就是每個 filesystem
左下角的方框),而在 Linux 系統安裝時,你可以選擇將 boot loader 安裝到 MBR 去,也可以選擇不安裝。
如果選擇安裝到 MBR 的話,那理論上你在 MBR 與 boot sector 都會保有一份 boot loader 程式的。
至於 Windows 安裝時,他預設會主動的將 MBR 與 boot sector 都裝上一份 boot loader!所以啦,
你會發現安裝多重作業系統時,你的 MBR 常常會被不同的作業系統的 boot loader 所覆蓋啦! ^_^
我們剛剛提到的兩個問題還是沒有解決啊!雖然各個作業系統都可以安裝一份 boot loader 到他們的 boot sector 中,
這樣作業系統可以透過自己的 boot loader 來載入核心了。問題是系統的 MBR 只有一個哩!
你要怎麼執行 boot sector 裡面的 loader 啊?這個我們得要回憶一下第二章約略提過的 boot loader 的功能了。boot loader
主要的功能如下:
提供選單:使用者可以選擇不同的開機項目,這也是多重開機的重要功能!
載入核心檔案:直接指向可開機的程式區段來開始作業系統;
轉交其他 loader:將開機管理功能轉交給其他 loader 負責。
由於具有選單功能,因此我們可以選擇不同的核心來開機。而由於具有控制權轉交的功能,因此我們可以載入其他 boot sector
內的 loader 啦!不過 Windows 的 loader 預設不具有控制權轉交的功能,因此你不能使用 Windows 的 loader
來載入 Linux 的 loader 喔!這也是為啥第二章談到 MBR 與多重開機時,會特別強調先裝 Windows 再裝 Linux 的緣故。
我們將上述的三個功能以底下的圖示來解釋你就看的懂了!(與第二章的圖示也非常類似啦!)
圖19.1.2、開機管理程式的選單功能與控制權轉交功能示意圖
如上圖所示,我的 MBR 使用 Linux 的 grub2 這個開機管理程式,並且裡面假設已經有了三個選單,
第一個選單可以直接指向 Linux 的核心檔案並且直接載入核心來開機;第二個選單可以將開機管理程式控制權交給 Windows
來管理,此時 Windows 的 loader 會接管開機流程,這個時候他就能夠啟動 windows 了。第三個選單則是使用 Linux
在 boot sector 內的開機管理程式,此時就會跳出另一個 grub2 的選單啦!瞭解了嗎?
選單一:MBR(grub2) --> kernel file --> booting
選單二:MBR(grub2) --> boot sector(Windows loader) --> Windows kernel --> booting
選單三:MBR(grub2) --> boot sector(grub2) --> kernel file --> booting
而最終 boot loader 的功能就是『載入 kernel 檔案』啦!
載入核心偵測硬體與 initramfs 的功能
當我們藉由 boot loader 的管理而開始讀取核心檔案後,接下來, Linux 就會將核心解壓縮到主記憶體當中,
並且利用核心的功能,開始測試與驅動各個周邊裝置,包括儲存裝置、CPU、網路卡、音效卡等等。
此時 Linux 核心會以自己的功能重新偵測一次硬體,而不一定會使用 BIOS
偵測到的硬體資訊喔!也就是說,核心此時才開始接管 BIOS 後的工作了。
那麼核心檔案在哪裡啊?一般來說,他會被放置到 /boot 裡面,並且取名為 /boot/vmlinuz 才對!
[root@study ~]# ls --format=single-column -F /boot
config-3.10.0-229.el7.x86_64 <==此版本核心被編譯時選擇的功能與模組設定檔
grub/ <==舊版 grub1 ,不需要理會這目錄了!
grub2/ <==就是開機管理程式 grub2 相關資料目錄
initramfs-0-rescue-309eb890d3d95ec7a.img <==底下幾個為虛擬檔案系統檔!這一個是用來救援的!
initramfs-3.10.0-229.el7.x86_64.img <==正常開機會用到的虛擬檔案系統
initramfs-3.10.0-229.el7.x86_64kdump.img <==核心出問題時會用到的虛擬檔案系統
System.map-3.10.0-229.el7.x86_64 <==核心功能放置到記憶體位址的對應表
vmlinuz-0-rescue-309eb890d09543d95ec7a* <==救援用的核心檔案
vmlinuz-3.10.0-229.el7.x86_64* <==就是核心檔案啦!最重要者!
從上表中的特殊字體,我們也可以知道 CentOs 7.x 的 Linux 核心為 3.10.0-229.el7.x86_64 這個版本!為了硬體開發商與其他核心功能開發者的便利,
因此 Linux 核心是可以透過動態載入核心模組的 (就請想成驅動程式即可),這些核心模組就放置在 /lib/modules/ 目錄內。
由於模組放置到磁碟根目錄內 (要記得 /lib 不可以與 / 分別放在不同的 partition !),
因此在開機的過程中核心必須要掛載根目錄,這樣才能夠讀取核心模組提供載入驅動程式的功能。
而且為了擔心影響到磁碟內的檔案系統,因此開機過程中根目錄是以唯讀的方式來掛載的喔。
一般來說,非必要的功能且可以編譯成為模組的核心功能,目前的 Linux distributions 都會將他編譯成為模組。
因此 USB, SATA, SCSI... 等磁碟裝置的驅動程式通常都是以模組的方式來存在的。
現在來思考一種情況,假設你的 linux 是安裝在 SATA 磁碟上面的,你可以透過 BIOS 的 INT 13 取得 boot loader 與
kernel 檔案來開機,然後 kernel 會開始接管系統並且偵測硬體及嘗試掛載根目錄來取得額外的驅動程式。
問題是,核心根本不認識 SATA 磁碟,所以需要載入 SATA 磁碟的驅動程式,
否則根本就無法掛載根目錄。但是 SATA 的驅動程式在 /lib/modules 內,你根本無法掛載根目錄又怎麼讀取到
/lib/modules/ 內的驅動程式?是吧!非常的兩難吧!在這個情況之下,你的 Linux 是無法順利開機的!
那怎辦?沒關係,我們可以透過虛擬檔案系統來處理這個問題。
虛擬檔案系統 (Initial RAM Disk 或 Initial RAM Filesystem) 一般使用的檔名為 /boot/initrd 或 /boot/initramfs
,這個檔案的特色是,他也能夠透過 boot loader 來載入到記憶體中,然後這個檔案會被解壓縮並且在記憶體當中模擬成一個根目錄,
且此模擬在記憶體當中的檔案系統能夠提供一支可執行的程式,透過該程式來載入開機過程中所最需要的核心模組,
通常這些模組就是 USB, RAID, LVM, SCSI 等檔案系統與磁碟介面的驅動程式啦!等載入完成後,
會幫助核心重新呼叫 systemd 來開始後續的正常開機流程。
圖19.1.3、BIOS 與 boot loader 及核心載入流程示意圖
如上圖所示,boot loader 可以載入 kernel 與 initramfs ,然後在記憶體中讓 initramfs 解壓縮成為根目錄,
kernel 就能夠藉此載入適當的驅動程式,最終釋放虛擬檔案系統,並掛載實際的根目錄檔案系統,就能夠開始後續的正常開機流程。
更詳細的 initramfs 說明,你可以自行使用 man initrd 去查閱看看。
底下讓我們來瞭解一下 CentOS 7.x 的 initramfs 檔案內容有什麼吧! ^_^
# 1. 先來直接看一下 initramfs 裡面的內容有些啥資料?
[root@study ~]# lsinitrd /boot/initramfs-3.10.0-229.el7.x86_64.img
# 首先會呼叫出 initramfs 最前面檔頭的許多資料介紹,這部份會佔用一些容量!
Image: /boot/initramfs-3.10.0-229.el7.x86_64.img: 18M
========================================================================
Early CPIO image
========================================================================
drwxr-xr-x 3 root root 0 May 4 17:56 .
-rw-r--r-- 1 root root 2 May 4 17:56 early_cpio
drwxr-xr-x 3 root root 0 May 4 17:56 kernel
drwxr-xr-x 3 root root 0 May 4 17:56 kernel/x86
drwxr-xr-x 2 root root 0 May 4 17:56 kernel/x86/microcode
-rw-r--r-- 1 root root 10240 May 4 17:56 kernel/x86/microcode/GenuineIntel.bin
========================================================================
Version: dracut-033-240.el7
Arguments: -f
dracut modules: # 開始一堆模組的載入行為
bash
nss-softokn
.....(中間省略).....
========================================================================
drwxr-xr-x 12 root root 0 May 4 17:56 .
crw-r--r-- 1 root root 5, 1 May 4 17:56 dev/console
crw-r--r-- 1 root root 1, 11 May 4 17:56 dev/kmsg
crw-r--r-- 1 root root 1, 3 May 4 17:56 dev/null
.....(中間省略).....
lrwxrwxrwx 1 root root 23 May 4 17:56 init -> usr/lib/systemd/systemd
.....(中間省略).....
drwxr-xr-x 2 root root 0 May 4 17:56 var/lib/lldpad
lrwxrwxrwx 1 root root 11 May 4 17:56 var/lock -> ../run/lock
lrwxrwxrwx 1 root root 10 May 4 17:56 var/log -> ../run/log
lrwxrwxrwx 1 root root 6 May 4 17:56 var/run -> ../run
========================================================================
# 最後則會列出這個 initramfs 裡頭的所有檔案!也就是說,這個 initramfs 檔案大概存著兩部份,
# 先是檔頭宣告的許多檔案部份,再來才是真的會被核心取用的全部附加的檔案資料!
從上面我們大概知道了這個 initramfs 裡頭含有兩大區塊,一個是事先宣告的一些資料,包括 kernel/x86/microcode/GenuineIntel.bin 這些東西。
在這些資料後面,才是真的我們的核心會去讀取的重要檔案~如果看一下檔案的內容,你會發現到 init 那隻程式已經被 systemd 所取代囉!這樣理解否?
好~如果你想要進一步將這個檔案解開的話,那得要先將前面的 kernel/x86/microcode/GenuineIntel.bin 之前的檔案先去除掉,這樣才能夠順利的解開。
因此,得要這樣進行:
# 1. 先取得 initramfs 前面應該要去除的容量有多少才對!使用下列方式取得
[root@study ~]# mkdir /dev/shm/initramfs
[root@study ~]# cd /dev/shm/initramfs
[root@study initramfs]# cpio -i -d --no-absolute-filenames \
> -I /boot/initramfs-3.10.0-229.el7.x86_64.img
22 blocks
# 這個重點就是在前面的字元佔了幾個 block 容量,每個 block 容量為 512bytes,
# 因此,前面的部份就總共佔了: 22 * 512 = 11264 個 bytes 的意思!
# 每一個 initramfs 檔案的前置字元容量都不相同,所以需要先找出來去除才行!
# 2. 將 /boot/initramfs-XX 底下的檔案進行去除前面不需要的檔頭資料部份。
[root@study initramfs]# dd if=/boot/initramfs-3.10.0-229.el7.x86_64.img of=initramfs.gz \
> bs=11264 skip=1
[root@study initramfs]# ll initramfs.gz; file initramfs.gz
-rw-r--r--. 1 root root 18558166 Aug 24 19:38 initramfs.gz
initramfs.gz: gzip compressed data, from Unix, last modified: Mon May 4 17:56:47 2015,
max compression
# 3. 從上面看到檔案是 gzip 壓縮檔,所以將它解壓縮後,再查閱一下檔案的類型!
[root@study initramfs]# gzip -d initramfs.gz
[root@study initramfs]# file initramfs
initramfs: ASCII cpio archive (SVR4 with no CRC)
# 4. 解開後又產生一個 cpio 檔案,得要將它用 cpio 的方法解開!加上不要絕對路徑的參數較保險!
[root@study initramfs]# cpio -i -d -H newc --no-absolute-filenames < initramfs
[root@study initramfs]# ll
lrwxrwxrwx. 1 root root 7 Aug 24 19:40 bin -> usr/bin
drwxr-xr-x. 2 root root 42 Aug 24 19:40 dev
drwxr-xr-x. 12 root root 4096 Aug 24 19:40 etc
lrwxrwxrwx. 1 root root 23 Aug 24 19:40 init -> usr/lib/systemd/systemd
-rw-r--r--. 1 root root 42263552 Aug 24 19:38 initramfs
lrwxrwxrwx. 1 root root 7 Aug 24 19:40 lib -> usr/lib
lrwxrwxrwx. 1 root root 9 Aug 24 19:40 lib64 -> usr/lib64
drwxr-xr-x. 2 root root 6 Aug 24 19:40 proc
drwxr-xr-x. 2 root root 6 Aug 24 19:40 root
drwxr-xr-x. 2 root root 6 Aug 24 19:40 run
lrwxrwxrwx. 1 root root 8 Aug 24 19:40 sbin -> usr/sbin
-rwxr-xr-x. 1 root root 3041 Aug 24 19:40 shutdown
drwxr-xr-x. 2 root root 6 Aug 24 19:40 sys
drwxr-xr-x. 2 root root 6 Aug 24 19:40 sysroot
drwxr-xr-x. 2 root root 6 Aug 24 19:40 tmp
drwxr-xr-x. 7 root root 61 Aug 24 19:40 usr
drwxr-xr-x. 3 root root 47 Aug 24 19:40 var
# 看吧!上面幾乎就像是一個小型的檔案系統根目錄耶!這樣就能讓 kernel 去掛載了!
# 4. 接下來瞧一瞧到底這個小型的檔案系統中,systemd 是要以哪個 target 來執行開機呢?
[root@study initramfs]# ll usr/lib/systemd/system/default.target
lrwxrwxrwx. 1 root root 13 Aug 24 19:40 usr/lib/systemd/system/default.target -> initrd.target
# 5. 最終,讓我們瞧一瞧系統內預設的 initrd.target 相依的所有服務資料吧!
[root@study initramfs]# systemctl list-dependencies initrd.target
initrd.target
├─dracut-cmdline.service
.....(中間省略).....
├─basic.target
│ ├─alsa-restore.service
.....(中間省略).....
│ ├─slices.target
│ │ ├─-.slice
│ │ └─system.slice
│ ├─sockets.target
│ │ ├─dbus.socket
.....(中間省略).....
│ │ └─systemd-udevd-kernel.socket
│ ├─sysinit.target
│ │ ├─dev-hugepages.mount
.....(中間省略).....
│ │ ├─local-fs.target
│ │ │ ├─-.mount
│ │ │ ├─boot.mount
.....(中間省略).....
│ │ └─swap.target
│ │ ├─dev-centos-swap.swap
.....(中間省略).....
│ │ └─dev-mapper-centos\x2dswap.swap
│ └─timers.target
│ └─systemd-tmpfiles-clean.timer
├─initrd-fs.target
└─initrd-root-fs.target
# 依舊透過 systemd 的方式,一個一個的將所有的偵測與服務載入系統中!
透過上面解開 initramfs 的結果,你會知道其實 initramfs 就是一個小型的根目錄,這個小型根目錄裡面也是透過 systemd 來進行管理,同時觀察 default.target
的連結,會發現其實這個小型系統就是透過 initrd.target 來開機,而 initrd.target 也是需要讀入一堆例如 basic.target, sysinit.target 等等的硬體偵測、核心功能啟用的流程,
然後開始讓系統順利運作。最終才又卸載 initramfs 的小型檔案系統,實際掛載系統的根目錄!
此外,initramfs 並沒有包山包海,它僅是帶入開機過程會用到的核心模組而已。所以如果你在 initramfs 裡面去找 modules 這個關鍵字的話,
就可以發現主要的核心模組大概就是 SCSI、virtio、RAID 等等跟磁碟相關性比較高的模組就是了!現在由於磁碟大部分都是使用 SATA 這玩意兒,
並沒有 IDE 的格式囉!所以,沒有 initramfs 的話,你的 Linux 幾乎就是不能順利開機的啦!除非你將 SATA 的模組直接編譯到核心去了! ^_^
在核心完整的載入後,您的主機應該就開始正確的運作了,接下來,就是要開始執行系統的第一支程式: systemd !