第14章 Linux開機詳細流程

計算機啓動分爲內核加載前、加載時和加載後3個大階段,這3個大階段又能夠分爲不少小階段,本文將很是細化分析每個重要的小階段。html

內核加載前的階段和操做系統無關,Linux或Windows在這部分的順序是同樣的。因爲使用anaconda安裝Linux時,默認的圖形界面是不支持GPT分區的,即便是目前最新的CentOS 7.3也仍然不支持,因此在本文中主要介紹傳統BIOS平臺(MBR方式)的啓動方式(實際上是本人愚笨,看不懂uefi啓動方式)。linux

在內核加載時和加載後階段,因爲CentOS 7採用的是systemd,和CentOS 5或CentOS 6的sysV風格的init大不相同,因此本文也只介紹sysV風格的init。ios

14.1 按下電源和bios階段

按下電源,計算機開始通電,最重要的是要接通cpu的電路,而後經過cpu的針腳讓cpu運行起來,只有cpu運行起來才能執行相關代碼跳到bios。nginx

bios是按下開機鍵後第一個運行的程序,它會讀取CMOS中的信息,以瞭解部分硬件的信息,好比硬件自檢(post)、硬件上的時間、硬盤大小和型號等。其實,手動進入bios界面看到的信息,都是在這一階段獲取到的,以下圖。對本文來講,最重要的仍是獲取到了啓動設備以及它們的啓動順序(順序從上到下)信息。shell

當硬件檢測和信息獲取完畢,開始初始化硬件,最後從排在第一位的啓動設備中讀取MBR,若是第一個啓動設備中沒有找到合理的MBR,則繼續從第二個啓動設備中查找,直到找到正確的MBR。編程

14.2 MBR和各類bootloader階段

這小節將介紹各類BR(boot record)和各類boot loader,但只是簡單介紹其基本做用。windows

MBR是主引導記錄,位於磁盤的第一個扇區,和分區無關,和操做系統無關,bios必定會讀取MBR中的記錄。安全

在MBR中存儲了bootloader/分區表/BRID。bootloader佔用446個字節,用於引導加載;分區表佔用64個字節,每一個主分區或擴展分區佔用16個字節,若是16個字節中的第一個字節爲0x80,則表示該分區爲激活的分區(活動分區),且只容許有一個激活的分區;最後2個字節是BRID(boot record ID),它固定爲0x55AA,用於標識該存儲設備的MBR是不是合理有效的MBR,若是bios讀取MBR發現最後兩個字節不是0x55AA,就會讀取下一個啓動設備。bash

14.2.1 boot loader

MBR中的bootloader只佔用446字節,因此可存儲的代碼有限,能加載引導的東西也有限,因此在磁盤的不一樣位置上設計了多種boot loader。下面將說明各類狀況。app

在建立文件系統時,是否還記得有些分區的第一個block是boot sector?這個啓動扇區中也放了boot loader,大小也頗有限。若是是主分區上的boot sector,則該段boot loader所在扇區稱爲VBR(volumn boot record),若是是邏輯分區上的boot sector,則該段boot loader所在扇區稱爲EBR(Extended boot sector)。但很不幸,這兩種方式的boot loader都不多被使用上了,由於它們很不方便,加上後面出現了啓動管理器(LILO和GRUB),它們就被遺忘了。但即便如此,在分區中仍是存在boot sector。

14.2.2 分區表

硬盤分區的好處之一就是能夠在不一樣的分區中安裝不一樣的操做系統,但boot loader必需要知道每一個操做系統具體是在哪一個分區。

分區表的長度只有64個字節,裏面又分紅四項,每項16個字節。因此,一個硬盤最多隻能分四個主分區。

每一個主分區表項的16個字節,都由6個部分組成:

(1).第1個字節:只能爲0或者0x80。0x80表示該主分區是激活分區,0表示非激活分區。單磁盤只能有一個主分區是激活的。

(2).第2-4個字節:主分區第一個扇區的物理位置(柱面、磁頭、扇區號等等)。

(3).第5個字節:主分區類型。

(4).第6-8個字節:主分區最後一個扇區的物理位置。

(5).第9-12字節:該主分區第一個扇區的邏輯地址。

(6).第13-16字節:主分區的扇區總數。

最後的四個字節"主分區的扇區總數",決定了這個主分區的長度。也就是說,一個主分區的扇區總數最多不超過2的32次方。若是每一個扇區爲512個字節,就意味着單個分區最大不超過2TB。

14.2.3 採用VBR/EBR方式引導操做系統

暫且先不討論grub如何管理啓動操做系統的,以VBR和EBR引導操做系統爲例。

當bios讀取到MBR中的boot loader後,會繼續讀取分區表。分兩種狀況:

(1)若是查找分區表時發現某個主分區表的第一個字節是0x80,也就是激活的分區,那麼說明操做系統裝在了該主分區,而後執行已載入的MBR中的boot loader代碼,加載該激活主分區的VBR中的boot loader,至此,控制權就交給了VBR的boot loader了;

(2)若是操做系統不是裝在主分區,那麼確定是裝在邏輯分區中,因此查找完主分區表後會繼續查找擴展分區表,直到找到EBR所在的分區,而後MBR中的boot loader將控制權交給該EBR的boot loader。

也就是說,若是一塊硬盤上裝了多個操做系統,那麼boot loader會分佈在多個地方,多是VBR,也多是EBR,但MBR是必定有的,這是被bios給"綁定"了的。在裝LINUX操做系統時,其中有一個步驟就是詢問你MBR裝在哪裏的,但這個MBR並不是必定真的是MBR,多是MBR,也多是VBR,還多是EBR,而且想要單磁盤多系統共存,則MBR必定不能被覆蓋(此處不考慮grub)。

以下圖,是我測試單磁盤裝3個操做系統時的分區結構。其中/dev/sda{1,2,3}是第一個CentOS 6系統,/dev/sda{5,6,7}是第二個CentOS 7系統,/dev/sda{8,9,10}是第三個CentOS 6系統,每個操做系統的分區序號從前向後都是/boot分區、根分區、swap分區。

再看下圖,是裝第三個操做系統時的詢問boot loader安裝位置的步驟。

裝第一個操做系統時,boot loader能夠裝在/dev/sda上,也能夠選擇裝在/dev/sda1上,這時裝的是MBR和VBR,任選一個都會將另外一個也裝上,從第二個操做系統開始,裝的是EBR而非MBR,且應該指定boot loader位置(如/dev/sda5和/dev/sda8),不然默認選項是裝在/dev/sda上,但這會覆蓋原有的MBR。

另外,在指定boot loader安裝路徑的下方,還有一個方框是操做系統列表,這就是操做系統菜單,其中能夠指定默認的操做系統,這裏的默認指的是MBR默認跳轉到哪一個VBR或EBR上。

因此,MBR/VBR和EBR之間的跳轉關係以下圖。

使用這種方式的菜單管理操做系統啓動,無需什麼stage1,stage1.5和stage2的概念,只要跳轉到了分區上的VBR或EBR,那麼直接就能夠加載引導該分區上的操做系統。

可是,這種管理操做系統啓動的菜單已經沒有意義了,如今都是使用grub來管理,因此裝第二個操做系統或第n個操做系統時不手動指定boot loader安裝位置,覆蓋掉MBR也無所謂,想要實現單磁盤多系統共存所須要作的,僅僅只是修改grub的配置文件而已。

使用grub管理引導菜單時,VBR/EBR就毫無用處了,具體的見下文。

14.3 grub階段

使用grub管理啓動,則MBR中的boot loader是由grub程序安裝的,此外還會安裝其餘的boot loader。CentOS 6使用的是傳統的grub,而CentOS 7使用的是grub2。

若是使用的是傳統的grub,則安裝的boot loader爲stage一、stage1_5和stage2,若是使用的是grub2,則安裝的是boot.img和core.img。傳統grub和grub2的區別仍是挺大的,因此下面分開解釋,若是對於grub有不理解之處,見個人另外一篇文章grub2詳解

14.3.1 使用grub2時的啓動過程

grub2程序安裝grub後,會在/boot/grub2/i386-pc/目錄下生成boot.img和core.img文件,另外還有一些模塊文件,其中包括文件系統類的模塊。

[root@xuexi ~]# find /boot/grub2/i386-pc/ -name '*.img' -o -name "*fs.mod" -o -name "*ext[0-9].mod"  
/boot/grub2/i386-pc/affs.mod
/boot/grub2/i386-pc/afs.mod
/boot/grub2/i386-pc/bfs.mod
/boot/grub2/i386-pc/btrfs.mod
/boot/grub2/i386-pc/cbfs.mod
/boot/grub2/i386-pc/ext2.mod # ext二、ext3和ext4都使用該模塊 /boot/grub2/i386-pc/hfs.mod
/boot/grub2/i386-pc/jfs.mod
/boot/grub2/i386-pc/ntfs.mod
/boot/grub2/i386-pc/procfs.mod
/boot/grub2/i386-pc/reiserfs.mod
/boot/grub2/i386-pc/romfs.mod
/boot/grub2/i386-pc/sfs.mod
/boot/grub2/i386-pc/xfs.mod
/boot/grub2/i386-pc/zfs.mod
/boot/grub2/i386-pc/core.img /boot/grub2/i386-pc/boot.img

其中boot.img就是安裝在MBR中的boot loader。固然,它們的內容是不同的,安裝boot loader時,grub2-install會將boot.img轉換爲合適的代碼寫入MBR中的boot loader部分。

core.img是第二段Boot loader段,grub2-install會將core.img轉換爲合適的代碼寫入到緊跟在MBR後面的空間,這段空間是MBR以後、第一個分區以前的空閒空間,被稱爲MBR gap,這段空間最小31KB,但通常都會是1MB左右。

實際上,core.img是多個img文件的結合體。它們的關係以下圖:

這張圖解釋了開機過程當中grub2階段的全部過程,boot.img段的boot loader只有一個做用,就是跳轉到core.img對應的boot loader的第一個扇區,對於從硬盤啓動的系統來講,該扇區是diskboot.img的內容,diskboot.img的做用是加載core.img中剩餘的內容。

因爲diskboot.img所在的位置是以硬編碼的方式寫入到boot.img中的,因此boot.img總能找到core.img中diskboot.img的位置並跳轉到它身上,隨後控制權交給diskboot.img。隨後diskboot.img加載壓縮後的kernel.img(注意,是grub的kernel不是操做系統的kernel)以初始化grub運行時的各類環境,控制權交給kernel.img。

但直到目前爲止,core.img都還不識別/boot所在分區的文件系統,因此kernel.img初始化grub環境的過程就包括了加載模塊,嚴格地說不是加載,由於在安裝grub時,文件系統類的模塊已經嵌入到了core.img中,例如ext類的文件系統模塊ext2.mod。

加載了模塊後,kernel.img就能識別/boot分區的文件系統,也就能找到grub的配置文件/boot/grub2/grub.cfg,有了grub.cfg就能顯示啓動菜單,咱們就能自由的選擇要啓動的操做系統。

當選擇某個菜單項後,kernel.img會根據grub.cfg中的配置加載對應的操做系統內核(/boot目錄下vmlinuz開頭的文件),並向操做系統內核傳遞啓動時參數,包括根文件系統所在的分區,init ramdisk(即initrd或initramfs)的路徑。例以下面是某個菜單項的配置:

menuentry 'CentOS 6' --unrestricted {
        search --no-floppy --fs-uuid --set=root f5d8939c-4a04-4f47-a1bc-1b8cbabc4d32
        linux16 /vmlinuz-2.6.32-504.el6.x86_64 root=UUID=edb1bf15-9590-4195-aa11-6dac45c7f6f3 ro quiet
        initrd16 /initramfs-2.6.32-504.el6.x86_64.img
}

加載完操做系統內核後grub2就將控制權交給操做系統內核。

總結下,從MBR開始後的過程是這樣的:

1.執行MBR中的boot loader(即boot.img)跳轉到diskboot.img。

2.執行diskboot.img,加載core.img剩餘的部分,並跳轉到kernel.img。

3.kernel.img讀取/boot/grub2/grub2.cfg,並顯示啓動管理菜單。

4.選中某菜單後,kernel.img加載該菜單項配置的操做系統內核/boot/vmlinux-XXX,並傳遞內核啓動參數,包括根文件系統所在分區和init ramdisk的路徑。

5.控制權交給操做系統內核。

14.3.2 使用傳統grub時的啓動過程

傳統grub對應的boot loader是stage1和stage2,從stage1跳轉到stage2大多數狀況下還會用到stage1_5對應的boot loader。

與grub2相比,stage1和boot.img的做用是相似的,都在MBR中。當該段boot loader執行後,它的目的是跳轉到stage1_5的第一個扇區上,而後由該扇區的代碼加載剩餘的內容,並跳轉到stage2的第一個扇區上。

stage1_5存在的理由是由於stage2功能較多,致使其文件體積較大(通常至少都有100多K),因此並無像core.img同樣嵌入到磁盤上,而是簡單地將其放在了boot分區上,但stage1並不識別boot分區的文件系統類型,因此藉助中間的輔助boot loader即stage1_5來跳轉。

stage1_5的目的之一是識別文件系統,但文件系統的類型有不少,因此對應的stage1_5也有不少種。

[root@xuexi ~]# ls -C /boot/grub/*stage1_5*
/boot/grub/e2fs_stage1_5     /boot/grub/jfs_stage1_5       /boot/grub/vstafs_stage1_5
/boot/grub/fat_stage1_5      /boot/grub/minix_stage1_5     /boot/grub/xfs_stage1_5
/boot/grub/ffs_stage1_5      /boot/grub/reiserfs_stage1_5
/boot/grub/iso9660_stage1_5  /boot/grub/ufs2_stage1_5

雖然有不少種stage1_5,但每一個boot分區也只能對應一種stage1_5。這個stage1_5對應的boot loader通常會被嵌入到MBR後、第一個分區前的中間那段空間(即MBR gap)。

當執行了stage1_5對應的boot loader後,stage1_5就能識別出boot所在的分區,並找到stage2文件的第一個扇區,而後跳轉過去。

當控制權交給了stage2,stage2就能加載grub的配置文件/boot/grub/grub.conf並顯示菜單並初始化grub的運行時環境,當選中操做系統後,stage2將和kernel.img同樣加載操做系統內核,傳遞內核啓動參數,並將控制權交給操做系統內核。

因此,stage一、stage1_5和stage2之間的關係以下圖:

雖然絕大多數都提供了stage1_5,但它不是必須的,它的做用僅僅只是識別boot分區的文件系統類型,對於一個會編程的人來講,能夠將固定boot分區的文件系統識別代碼嵌入到stage1中,這樣stage1自身就能識別boot分區,就不須要stage1_5了。

看看安裝grub時,grub到底作了些什麼工做。

grub> setup (hd0)
 Checking if "/boot/grub/stage1" exists... yes
 Checking if "/boot/grub/stage2" exists... yes
 Checking if "/boot/grub/e2fs_stage1_5" exists... yes
 Running "embed /boot/grub/e2fs_stage1_5 (hd0)"...  15 sectors are embedded.
succeeded
 Running "install /boot/grub/stage1 (hd0) (hd0)1+15 p (hd0,0)/boot/grub/stage2 /boot/grub/menu.lst"... succeeded
Done.

首先檢測各stage文件是否存在於/boot/grub目錄下,隨後嵌入stage1_5到磁盤上,該文件系統類型的stage1_5佔用了15個扇區,最後安裝stage1,並告知stage1 stage1_5的位置是第1到第15個扇區,之因此先嵌入stage1_5再嵌入stage1就是爲了讓stage1知道stage1_5的位置,最後還告知了stage1 stage2和配置文件menu.lst(它是grub.conf的軟連接)的路徑。

14.4 內核加載和內核初始化階段

提早說明,下文所述均爲sysV init系統啓動風格,systemd的啓動管理方式大不相同,因此不要將systemd管理的啓動方式與此作比較。

到目前爲止,內核已經被加載到內存掌握了控制權,且收到了boot loader最後傳遞的內核啓動參數以及init ramdisk的路徑。

全部的內核都是以bzImage方式壓縮過的,壓縮後CentOS 6的內核大小大約爲4M,CentOS 7的內核大小大約爲5M。內核要能正常運做下去,它須要進行解壓釋放。

解壓釋放以後,將建立pid爲0的idle進程,該進程很是重要,後續內核全部的進程都是經過fork它建立的,且不少cpu降溫工具就是強制執行idle進程來實現的。

而後建立pid=1和pid=2的內核進程。pid=1的進程也就是init進程,pid=2的進程是kthread內核線程,它的做用是在真正調用init程序以前完成內核環境初始化和設置工做,例如根據grub傳遞的內核啓動參數找到init ramdisk並加載。

所謂的救援模式就是剛加載完內核,init進程接收到控制權的那一階段,由於沒有進行任何操做系統初始化過程,因此能夠修復和操做系統相關的不少問題。另外,安裝鏡像中也有內核,能夠經過安裝鏡像進入救援模式,這種進入救援模式的方式幾乎可修復任何操做系統啓動相關的問題,即便是/boot目錄下內核鏡像缺失均可以重裝。(還有一種單用戶模式,它是運行級別爲1的環境,因此已經初始化完運行級別,見後文)

14.4.1 加載init ramdisk

在前面,已經建立了pid=1的init進程和pid=2的kthread進程,但注意,它們都是內核線程,全稱是kernel_init和kernel_kthread,而真正能被ps捕獲到的pid=1的init進程是由kernel_init調用init程序後造成的。

要加載/sbin/init程序,首先要找到根分區,根分區是有文件系統的,因此內核須要先識別文件系統並加載文件系統的驅動,但文件系統的驅動又是放在根分區的,這就出現了先有雞仍是先有蛋的矛盾。

解決的方法之一是像grub2識別boot分區的文件系統同樣,將根文件系統驅動模塊嵌入到內核中,但文件系統的種類太多,並且會升級,這樣就致使內核不斷的嵌入新的文件系統驅動模塊,內核不斷增大,這顯然是不合適的。

解決方法之二則像傳統grub藉助中間過渡引導段stage1_5同樣,將根文件系統的驅動模塊放入一箇中間過渡文件,在加載根文件系統以前先加載這個過渡文件,再由過渡文件跳轉到根文件系統。

方法二正是如今採用的,其採用的中間過渡文件稱爲init ramdisk,它是在安裝完操做系統時生成的,這樣它會收集到當前操做系統的根文件系統是什麼類型的文件系統,也就能只嵌入一個對應的文件系統驅動模塊使其變得足夠小。以下圖,它是安裝操做系統時安裝完全部軟件包後執行的一個收集過程。

在CentOS 5上採用的init ramdisk稱爲initrd,而CentOS 6和CentOS 7採用的則是initramfs,它們的目的是同樣的,但在實現上卻大有不一樣。但它們都存放在/boot目錄下。

[root@xuexi ~]# ll -h /boot/init*
-rw-------. 1 root root 19M Feb 25 11:53 /boot/initramfs-2.6.32-504.el6.x86_64.img

能夠看到,它們的大小有十多兆,由此也可知道init ramdisk的做用確定不只僅只是找到根文件系統,它還會作其餘工做。具體還作什麼工做,請繼續閱讀下文。

14.4.2 initrd

initrd實際上是一個鏡像文件系統,是在內存中劃分一片區域模擬磁盤分區,在該文件中包含了找到根文件系統的腳本和驅動。

既然是文件系統,那麼內核也必需要帶有對應文件系統的驅動,另外文件系統要使用就必須有根"/",這個根是內存中的"虛根"。因爲內核加載到這裏已經初始化一些運行環境了,因此內核的運行狀態等參數也要保存下來,保存的位置就是內存中虛根下的/proc和/sys,此外還有收集到的硬件設備信息以及設備的運行環境也要保存下來,保存的位置是/dev。到此爲止,pid=2的內核線程kernel_kthread就完成了基本工做,開始轉到kernel_init進程上了。

再以後就是kernel_init掛載真正的根文件系統並從虛根切換到實根,最後kernel_init將調用init程序,也就是真正的能被咱們看見的pid=1的init進程,而後將控制權交給init,因此從如今開始,將切換到用戶空間,後續剩餘的事情都將由用戶空間的程序完成。

如下是CentOS 5.8中initrd文件的解壓過程和解包後的目錄結構。

[root@localhost ~]# cp /boot/initrd-2.6.18-308.el5.img /tmp/initrd.gz
[root@localhost tmp]# gunzip initrd.gz
[root@localhost tmp]# cpio -id < initrd 

[root@localhost tmp]# ls
bin  dev  etc  init  initrd  lib  proc  sbin  sys  sysroot

14.4.3 initramfs

initramfs比initrd先進了一些,initrd必須是一個文件系統,是在內存中模擬出磁盤分區的,因此內核必需要帶有它的文件系統驅動,而initramfs則僅僅只是一個鏡像壓縮文件而非文件系統,因此它不須要帶文件系統驅動,在加載時,內核會將其解壓的內容裝入到一個tmpfs 中。

initramfs和initrd最大的區別在於init進程的區別對待。initramfs爲了儘早進入用戶空間,它將init程序集成到了initramfs鏡像文件中,這樣就能夠在initramfs裝入tmpfs時直接運行init進程,而不用去找根文件系統下的/sbin/init,由此掛載根文件系統的工做將由init來完成,而再也不是內核線程kernel_init完成。最後從虛根切換到實根。

那根分區下的/sbin/init是幹嗎的呢?能夠認爲是init ramdisk中init的一個備份,若是ramdisk中找不到init就會去找/sbin/init。另外,在正常運行的操做系統環境下,/sbin/init還常常用來完成其餘工做,如發送信號。

其實initramfs完成了不少工做,解開它的鏡像文件就能發現它的目錄結構和真實環境下的目錄結構相似。如下是CentOS 7上initramfs-3.10.0-327.el7.x86_64解包過程和解包後的目錄結構。

[root@xuexi ~]# cp /boot/initramfs-3.10.0-327.el7.x86_64.img /tmp/initramfs.gz
[root@xuexi ~]# cd /tmp; gunzip /tmp/initramfs.gz
[root@xuexi tmp]# cpio -id < initramfs

[root@xuexi tmp]# ls -l
total 8
lrwxrwxrwx  1 root root    7 Jun 29 23:28 bin -> usr/bin
drwxr-xr-x  2 root root   42 Jun 29 23:28 dev
drwxr-xr-x 11 root root 4096 Jun 29 23:28 etc
lrwxrwxrwx  1 root root   23 Jun 29 23:28 init -> usr/lib/systemd/systemd
lrwxrwxrwx  1 root root    7 Jun 29 23:28 lib -> usr/lib
lrwxrwxrwx  1 root root    9 Jun 29 23:28 lib64 -> usr/lib64
drwxr-xr-x  2 root root    6 Jun 29 23:28 proc
drwxr-xr-x  2 root root    6 Jun 29 23:28 root
drwxr-xr-x  2 root root    6 Jun 29 23:28 run
lrwxrwxrwx  1 root root    8 Jun 29 23:28 sbin -> usr/sbin
-rwxr-xr-x  1 root root 3041 Jun 29 23:28 shutdown
drwxr-xr-x  2 root root    6 Jun 29 23:28 sys
drwxr-xr-x  2 root root    6 Jun 29 23:28 sysroot
drwxr-xr-x  2 root root    6 Jun 29 23:28 tmp
drwxr-xr-x  7 root root   61 Jun 29 23:28 usr
drwxr-xr-x  2 root root   27 Jun 29 23:28 var

另外,還能夠在其sbin目錄下發現init程序。

[root@xuexi tmp]# ll sbin/init
lrwxrwxrwx 1 root root 22 Jun 29 23:28 sbin/init -> ../lib/systemd/systemd

14.5 操做系統初始化

下文解釋的是sysV風格的系統環境,與systemd初始化大不相同。

當init進程掌握控制權後,意味着已經進入了用戶空間,後續的事情也將以用戶空間爲主導來完成。

init的名稱是initialize的縮寫,是初始化的意思,因此它的做用也就是初始化的做用。在內核加載階段,也有初始化動做,初始化的環境是內核的環境,是由kernel_init、kernel_thread等內核線程完成的。而init掌握控制權後,已經能夠和用戶空間交互,意味着真正的開始進入操做系統,因此它初始化的是操做系統的環境。

操做系統初始化涉及了很多過程,大體以下:讀取運行級別;初始化系統類的環境;根據運行級別初始化用戶類的環境;執行rc.local文件完成用戶自定義開機要執行的命令;加載終端;

14.5.1 運行級別

在sysV風格的系統下,使用了運行級別的概念,不一樣運行級別初始化不一樣的系統類環境,你能夠認爲windows的安全模式也是使用運行級別的一種產物。

在Linux系統中定義了7個運行級別,使用0-6的數字表示。

0:halt,即關機

1:單用戶模式

2:不帶NFS的多用戶模式

3:完整多用戶模式

4:保留未使用的級別

5:X11,即圖形界面模式

6:reboot,即重啓

實際上,執行關機或重啓命令的本質就是向init進程傳遞0或6這兩個運行級別。

sysV的init程序讀取/etc/inittab文件來獲取默認的運行級別,並根據此文件所指定的配置執行默認運行級別對應的操做。注意,systemd管理的系統是沒有/etc/inittab文件的,即便有也僅僅只是出於提醒的目的,由於systemd沒有了運行級別的概念,說實話,systemd管的真的太多了。

CentOS 6.6上該文件內容以下:

[root@xuexi ~]# cat /etc/inittab
# inittab is only used by upstart for the default runlevel.
#
# ADDING OTHER CONFIGURATION HERE WILL HAVE NO EFFECT ON YOUR SYSTEM.
#
# System initialization is started by /etc/init/rcS.conf # # Individual runlevels are started by /etc/init/rc.conf # # Ctrl-Alt-Delete is handled by /etc/init/control-alt-delete.conf # # Terminal gettys are handled by /etc/init/tty.conf and /etc/init/serial.conf, # with configuration in /etc/sysconfig/init.
#
# For information on how to write upstart event handlers, or how
# upstart works, see init(5), init(8), and initctl(8).
#
# Default runlevel. The runlevels used are:
#   0 - halt (Do NOT set initdefault to this)
#   1 - Single user mode
#   2 - Multiuser, without NFS (The same as 3, if you do not have networking)
#   3 - Full multiuser mode
#   4 - unused
#   5 - X11
#   6 - reboot (Do NOT set initdefault to this)
#
id:3:initdefault:

該文件告訴咱們,系統初始化過程由/etc/init/rcS.conf完成,運行級別類的初始化過程由/etc/init.conf來完成,按下CTRL+ALT+DEL鍵要執行的過程由/etc/init/control-alt-delete.conf來完成,終端加載的過程由/etc/init/tty.conf和/etc/init/serial.conf讀取配置文件/etc/sysconfig/init來完成再文件最後,還有一行"id:3:initdefault",表示默認的運行級別爲3,即完整的多用戶模式。

確認了要進入的運行級別後,init將先讀取/etc/init/rcS.conf來完成系統環境類初始化動做,再讀取/etc/init/rc.conf來完成運行級別類動做。

14.5.2 系統環境初始化

先看看/etc/init/rcS.conf文件的內容。

[root@xuexi ~]# cat /etc/init/rcS.conf
# rcS - runlevel compatibility
#
# This task runs the old sysv-rc startup scripts.
#
# Do not edit this file directly. If you want to change the behaviour,
# please create a file rcS.override and put your changes there.
 
start on startup
 
stop on runlevel
 
task
 
# Note: there can be no previous runlevel here, if we have one it's bad
# information (we enter rc1 not rcS for maintenance).  Run /etc/rc.d/rc
# without information so that it defaults to previous=N runlevel=S.
console output
pre-start script
        for t in $(cat /proc/cmdline); do
                case $t in
                        emergency)
                                start rcS-emergency
                                break
                        ;;
                esac
        done
end script
exec /etc/rc.d/rc.sysinit
post-stop script
        if [ "$UPSTART_EVENTS" = "startup" ]; then
                [ -f /etc/inittab ] && runlevel=$(/bin/awk -F ':' '$3 == "initdefault" && $1 !~ "^#" { print $2 }' /etc/inittab)
                [ -z "$runlevel" ] && runlevel="3"
                for t in $(cat /proc/cmdline); do
                        case $t in
                                -s|single|S|s) runlevel="S" ;;
                                [1-9])       runlevel="$t" ;;
                        esac
                done
                exec telinit $runlevel
        fi
end script

其中"exec /etc/rc.d/rc.sysinit"這一行就表示要執行/etc/rc.d/rc.sysinit文件,該文件定義了系統初始化(system initialization)的內容,包括:

(1).確認主機名。

(2).掛載/proc和/sys等特殊文件系統,使得內核參數和狀態可與人進行交互。是否還記得在內核加載階段時的/proc和/sys?

(3).啓動udev,也就是啓動相似windows中的設備管理器。

(4)初始化硬件參數,如加載某些驅動,設置時鐘等。

(5).設置主機名。

(6).執行fsck檢測磁盤是否健康。

(7).掛載/etc/fstab中除/proc和NFS的文件系統。

(8).激活swap。

(9).將全部執行的操做寫入到/var/log/dmesg文件中。

14.5.3 運行級別環境初始化

執行完系統初始化後,接下來就是執行運行級別的初始化。先看看/etc/init/rc.conf的內容。

[root@xuexi ~]# cat /etc/init/rc.conf
# rc - System V runlevel compatibility
#
# This task runs the old sysv-rc runlevel scripts.  It
# is usually started by the telinit compatibility wrapper.
#
# Do not edit this file directly. If you want to change the behaviour,
# please create a file rc.override and put your changes there.
 
start on runlevel [0123456]
 
stop on runlevel [!$RUNLEVEL]
 
task
 
export RUNLEVEL
console output
exec /etc/rc.d/rc $RUNLEVEL

最後一行"exec /etc/rc.d/rc $RUNLEVEL"說明調用/etc/rc.d/rc這個腳原本初始化指定運行級別的環境。Linux採用了將各運行級別初始化內容分開管理的方式,將0-6這7個運行級別要執行的初始化腳本分別放入rc[0-6].d這7個目錄中。

[root@xuexi ~]# ls -l /etc/rc.d/
total 60
drwxr-xr-x. 2 root root  4096 Jun 11 02:42 init.d
-rwxr-xr-x. 1 root root  2617 Oct 16  2014 rc
drwxr-xr-x. 2 root root  4096 Jun 11 02:42 rc0.d
drwxr-xr-x. 2 root root  4096 Jun 11 02:42 rc1.d
drwxr-xr-x. 2 root root  4096 Jun 11 02:42 rc2.d
drwxr-xr-x. 2 root root  4096 Jun 11 02:42 rc3.d
drwxr-xr-x. 2 root root  4096 Jun 11 02:42 rc4.d
drwxr-xr-x. 2 root root  4096 Jun 11 02:42 rc5.d
drwxr-xr-x. 2 root root  4096 Jun 11 02:42 rc6.d
-rwxr-xr-x. 1 root root   220 Oct 16  2014 rc.local
-rwxr-xr-x. 1 root root 19914 Oct 16  2014 rc.sysinit

實際上/etc/init.d/下的腳本纔是真正的腳本,放入rcN.d目錄中的文件只不過是/etc/init.d/目錄下腳本的軟連接。注意,/etc/init.d是Linux耍的一個小把戲,它是/etc/rc.d/init.d的一個符號連接,在有些類unix系統中是沒有/etc/init.d的,都是直接使用/etc/rc.d/init.d。

以/etc/rc.d/rc3.d爲例。

[root@xuexi ~]# ll /etc/rc.d/rc3.d/ | head
total 0
lrwxrwxrwx. 1 root root 16 Feb 25 11:52 K01smartd -> ../init.d/smartd
lrwxrwxrwx. 1 root root 16 Feb 25 11:52 K10psacct -> ../init.d/psacct
lrwxrwxrwx. 1 root root 19 Feb 25 11:51 K10saslauthd -> ../init.d/saslauthd
lrwxrwxrwx  1 root root 22 Jun 10 08:59 K15htcacheclean -> ../init.d/htcacheclean
lrwxrwxrwx  1 root root 15 Jun 10 08:59 K15httpd -> ../init.d/httpd
lrwxrwxrwx  1 root root 15 Jun 11 02:42 K15nginx -> ../init.d/nginx
lrwxrwxrwx. 1 root root 18 Feb 25 11:52 K15svnserve -> ../init.d/svnserve
lrwxrwxrwx. 1 root root 20 Feb 25 11:51 K50netconsole -> ../init.d/netconsole
lrwxrwxrwx  1 root root 17 Jun 10 00:50 K73winbind -> ../init.d/winbind

可見,rcN.d中的文件都以K或S加一個數字開頭,其後纔是腳本名稱,且它們都是/etc/rc.d/init.d中文件的連接。S開頭表示進入該運行級別時要運行的程序,S字母后的數值表示啓動順序,數字越大,啓動的越晚;K開頭的表示退出該運行級別時要殺掉的程序,數值表示關閉的順序。

全部這些文件都是由/etc/rc.d/rc這個程序調用的,K開頭的則傳給rc一個stop參數,S開頭的則傳給rc一個start參數。

打開rc0.d和rc6.d這兩個目錄,你會發如今這兩個目錄中除了"S00killall"和"S01reboot",其他都是K開頭的文件。

在rc[2-5].d這幾個目錄中,都有一個S99local文件,且它們都是指向/etc/rc.d/rc.local的軟連接。S99表示最後啓動的一個程序,因此rc.local中的程序是2345這4個運行級別初始化過程當中最後運行的一個腳本。這是Linux提供給咱們定義本身想要在開機時(嚴格地說是進入運行級別)就執行的命令的文件。

當初始化完運行級別環境後,將要準備登陸系統了。

所謂的單用戶模式(runlevel=1),就是初始化完運行級別1對應的環境。由於已經初始化了操做系統和運行級別,因此單用戶模式所處的層次要比救援模式高的多,能修復的問題也就只有它後面還未初始化的過程:終端初始化和用戶登陸問題。

14.6 終端初始化和登陸系統

Linux是多任務多用戶的操做系統,它容許多人同時在線工做。但每一個人都必需要輸入用戶名和密碼才能驗證身份並最終登陸。但登錄時是以圖形界面的方式給用戶使用,仍是以純命令行模式給用戶使用呢?這是終端決定的,也就是說在登陸前須要先加載終端。至於什麼是終端,見個人另外一篇文章Linux終端類型

14.6.1 終端初始化

在Linux上,每次開機都必然會開啓全部支持的虛擬終端,以下圖。

這些虛擬終端是由getty命令(get tty)來完成的,getty命令有不少變種,有mingetty、agetty、rungettty等,在CentOS 5和CentOS 6都使用mingetty,在CentOS 7上使用agetty。getty命令的做用之一是調用登陸程序/bin/login。

例如,在CentOS 6下,捕獲tty終端狀況。

[root@xuexi ~]# ps -elf | grep tt[y]
4 S root       1412      1  0  80   0 -  1016 n_tty_ Jun21 tty2     00:00:00 /sbin/mingetty /dev/tty2
4 S root       1414      1  0  80   0 -  1016 n_tty_ Jun21 tty3     00:00:00 /sbin/mingetty /dev/tty3
4 S root       1417      1  0  80   0 -  1016 n_tty_ Jun21 tty4     00:00:00 /sbin/mingetty /dev/tty4
4 S root       1419      1  0  80   0 -  1016 n_tty_ Jun21 tty5     00:00:00 /sbin/mingetty /dev/tty5
4 S root       1421      1  0  80   0 -  1016 n_tty_ Jun21 tty6     00:00:00 /sbin/mingetty /dev/tty6
4 S root       1492   1410  0  80   0 - 27118 n_tty_ Jun21 tty1     00:00:00 -bash

在CentOS 7下,捕獲tty終端狀況。

[root@xuexi tmp]# ps -elf | grep tt[y]
4 S root       8258      1  0  80   0 - 27507 n_tty_ 04:17 tty2     00:00:00 /sbin/agetty --noclear tty2 linux
4 S root       8259      1  0  80   0 - 27507 n_tty_ 04:17 tty3     00:00:00 /sbin/agetty --noclear tty3 linux
4 S root       8260      1  0  80   0 - 27507 n_tty_ 04:17 tty4     00:00:00 /sbin/agetty --noclear tty4 linux
4 S root       8262    915  0  80   0 - 29109 n_tty_ 04:17 tty1     00:00:00 -bash
4 S root       8307   8305  0  80   0 - 29109 n_tty_ 04:17 tty5     00:00:00 -bash
4 S root       8348   8346  0  80   0 - 29136 n_tty_ 04:17 tty6     00:00:00 -bash

細心一點會發現,有的tty終端仍然以/sbin/mingetty進程或/sbin/agetty進程顯示,有些卻以bash進程顯示。這是由於getty進程在調用/bin/login後,若是輸入用戶名和密碼成功登陸了某個虛擬終端,那麼gettty程序會融合到bash(假設bash是默認的shell)進程,這樣getty進程就不會再顯示了。

雖然getty不顯示了,但並不表明它消失了,它仍以特殊的方式存在着。是否還記得/etc/inittab文件?此文件中提示了終端加載的過程由/etc/init/tty.conf讀取配置文件/etc/sysconfig/init來完成。

[root@xuexi ~]# grep tty -A 1 /etc/inittab
# Terminal gettys are handled by /etc/init/tty.conf and /etc/init/serial.conf,
# with configuration in /etc/sysconfig/init.

那麼就看看/etc/init/tty.conf文件。

[root@xuexi ~]# cat /etc/init/tty.conf
# tty - getty
#
# This service maintains a getty on the specified device.
#
# Do not edit this file directly. If you want to change the behaviour,
# please create a file tty.override and put your changes there.
 
stop on runlevel [S016]
 
respawn
instance $TTY
exec /sbin/mingetty $TTY
usage 'tty TTY=/dev/ttyX  - where X is console id'

此文件中的respawn表示進程由init進程監視,一發現被殺掉了init會當即重啓它。因此,只要getty進程一結束,init會當即監視到而重啓該進程。所以,用戶登陸成功後getty只是融合到了bash進程中,並不是退出,不然init會當即重啓它,而它會調用login程序讓你再次輸入用戶和密碼。

再看看/etc/sysconfig/init文件。

[root@xuexi ~]# cat /etc/sysconfig/init
# color => new RH6.0 bootup
# verbose => old-style bootup
# anything else => new style bootup without ANSI colors or positioning
BOOTUP=color
# column to start "[  OK  ]" label in
RES_COL=60
# terminal sequence to move to that column. You could change this
# to something like "tput hpa ${RES_COL}" if your terminal supports it
MOVE_TO_COL="echo -en \\033[${RES_COL}G"
# terminal sequence to set color to a 'success' color (currently: green)
SETCOLOR_SUCCESS="echo -en \\033[0;32m"
# terminal sequence to set color to a 'failure' color (currently: red)
SETCOLOR_FAILURE="echo -en \\033[0;31m"
# terminal sequence to set color to a 'warning' color (currently: yellow)
SETCOLOR_WARNING="echo -en \\033[0;33m"
# terminal sequence to reset to the default color.
SETCOLOR_NORMAL="echo -en \\033[0;39m"
# Set to anything other than 'no' to allow hotkey interactive startup...
PROMPT=yes
# Set to 'yes' to allow probing for devices with swap signatures
AUTOSWAP=no
# What ttys should gettys be started on? ACTIVE_CONSOLES=/dev/tty[1-6] # Set to '/sbin/sulogin' to prompt for password on single-user mode # Set to '/sbin/sushell' otherwise SINGLE=/sbin/sushell

其中ACTIVE_CONSOLES指令決定了要開啓哪些虛擬終端。SINGLE決定了在單用戶模式下要調用哪一個login程序和哪一個shell。

14.6.2 登陸過程

若是不在虛擬終端登陸,而是經過爲ssh分配的僞終端登陸,那麼到建立完getty進程那一步其實開機流程已經完成了。但無論在哪一種終端下登陸,登陸過程也能夠算做開機流程的一部分,因此也簡單說明下。

getty進程啓用虛擬終端後將調用login進程提示用戶輸入用戶名或密碼(或僞終端的鏈接程序如ssh提示輸入用戶名和密碼),當用戶輸入完成後,將驗證輸入的用戶名是否合法,密碼是否正確,用戶名是不是明確被禁止登錄的,PAM模塊對此用戶的限制是如何的等等,還要將登陸過程記錄到各個日誌文件中。若是登陸成功,將加載該用戶的bash,加載bash過程須要讀取各類配置文件,初始化各類環境等等。但無論怎麼說,只要登陸成功就表示開機流程所有完成了。

相關文章
相關標籤/搜索