本文目錄: 14.1 按下電源和bios階段 14.2 MBR和各種bootloader階段 14.2.1 boot loader 14.2.2 分區表 14.2.3 採用VBR/EBR方式引導操作系統 14.3 grub階段 14.3.1 使用grub2時的啟動過程 14.3.2 使用傳統grub ...
本文目錄:
電腦啟動分為內核載入前、載入時和載入後3個大階段,這3個大階段又可以分為很多小階段,本文將非常細化分析每一個重要的小階段。
內核載入前的階段和操作系統無關,Linux或Windows在這部分的順序是一樣的。由於使用anaconda安裝Linux時,預設的圖形界面是不支持GPT分區的,即使是目前最新的CentOS 7.3也仍然不支持,所以在本文中主要介紹傳統BIOS平臺(MBR方式)的啟動方式(其實是本人愚笨,看不懂uefi啟動方式)。
在內核載入時和載入後階段,由於CentOS 7採用的是systemd,和CentOS 5或CentOS 6的sysV風格的init大不相同,所以本文也只介紹sysV風格的init。
14.1 按下電源和bios階段
按下電源,電腦開始通電,最重要的是要接通cpu的電路,然後通過cpu的針腳讓cpu運行起來,只有cpu運行起來才能執行相關代碼跳到bios。
bios是按下開機鍵後第一個運行的程式,它會讀取CMOS中的信息,以瞭解部分硬體的信息,比如硬體自檢(post)、硬體上的時間、硬碟大小和型號等。其實,手動進入bios界面看到的信息,都是在這一階段獲取到的,如下圖。對本文來說,最重要的還是獲取到了啟動設備以及它們的啟動順序(順序從上到下)信息。
當硬體檢測和信息獲取完畢,開始初始化硬體,最後從排在第一位的啟動設備中讀取MBR,如果第一個啟動設備中沒有找到合理的MBR,則繼續從第二個啟動設備中查找,直到找到正確的MBR。
14.2 MBR和各種bootloader階段
這小節將介紹各種BR(boot record)和各種boot loader,但只是簡單介紹其基本作用。
MBR是主引導記錄,位於磁碟的第一個扇區,和分區無關,和操作系統無關,bios一定會讀取MBR中的記錄。
在MBR中存儲了bootloader/分區表/BRID。bootloader占用446個位元組,用於引導載入;分區表占用64個位元組,每個主分區或擴展分區占用16個位元組,如果16個位元組中的第一個位元組為0x80,則表示該分區為激活的分區(活動分區),且只允許有一個激活的分區;最後2個位元組是BRID(boot record ID),它固定為0x55AA,用於標識該存儲設備的MBR是否是合理有效的MBR,如果bios讀取MBR發現最後兩個位元組不是0x55AA,就會讀取下一個啟動設備。
14.2.1 boot loader
MBR中的bootloader只占用446位元組,所以可存儲的代碼有限,能載入引導的東西也有限,所以在磁碟的不同位置上設計了多種boot loader。下麵將說明各種情況。
在創建文件系統時,是否還記得有些分區的第一個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為stage1、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 # ext2、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一樣載入操作系統內核,傳遞內核啟動參數,並將控制權交給操作系統內核。
所以,stage1、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並載入。
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提供給我們定義自己想要在開機時(嚴格地說是進入運行級別)就執行的命令的文件。
當初始化完運行級別環境後,將要準備登錄系統了。
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