官术网_书友最值得收藏!

1.5 系統啟動流程

1.5.1 系統引導概述

為了更好地了解Linux系統的運行原理,非常有必要了解系統啟動的流程。實際上,這也是學習Linux應知應會的內容,在很多Linux系統工程師的職位面試中都會被問及。

來想象一下臺式機的啟動過程,相信大家都有這樣的經驗和體會。在按開機電源后,會聽到機箱內發出“滴”的一聲,接著屏幕上開始打印出一些字符,然后開始顯示出圖形界面,最后屏幕上會顯示需要輸入用戶名、密碼的登錄界面。其實,不管是Linux還是Windows,從用戶感官上的體驗而言,順序都是大同小異的。本節將詳細描述Linux環境下的啟動流程,起點是從按下計算機的電源鍵開始。

首先,計算機會加載BIOS,這是計算機上最接近硬件的軟件,各家主板制造商都會開發適合自己主板的BIOS,而BIOS中一項很重要的功能就是對自身的硬件做一次健康檢查,只有硬件沒有問題,才能運行軟件,記住,操作系統也是一種軟件。這種通電后開始的自檢過程被稱為“加電自檢”,英文中稱為Power On Self Test,簡稱POST。如果所有的硬件自檢通過,一般都會發出一次“滴”的短聲提示,說明硬件一切正常。

機器自檢通過后,下面就要引導系統了。這個動作是BIOS設定的,BIOS默認會從硬盤上的第0柱面、第0磁道、第一個扇區中讀取被稱為MBR的東西,即主引導記錄。一個扇區的大小是512字節,存放的內容是一段引導程序和分區信息,其中引導程序部分占用446字節,另外64字節是磁盤分區表DPT,最后兩字節是MBR的結束位。這512字節的空間內容是由專門的分區程序產生的,比如說Windows下的fdisk.exe,或者Linux下的fdisk命令,所以它不依賴于任何操作系統,而MBR中的引導程序也是可以修改的,所以可以利用這個特性實現多操作系統共存。由于RedHat、CentOS默認會使用Grub作為其引導操作系統的程序,而Grub本身又比較大,所以常見的方式是在MBR中寫入Grub的地址,這樣系統實際會載入Grub作為操作系統的引導程序。

經過了上面的步驟,第三步就是順理成章地運行Grub了。Grub最重要的功能就是根據其配置文件加載kernel鏡像,并運行內核加載后的第一個程序/sbin/init,這個程序會根據/etc/inittab來進行初始化的工作。其實這里最重要的就是根據文件中設定的值來確定系統將會運行的runlevel,默認的runlevel定義在“id:3:initdefault:”中,其中的數字3說明目前的運行級別定義為3(這里提到了runlevel的概念,將在后面詳細講解)。

第四步,Linux將根據/etc/inittab中定義的系統初始化配置si::sysinit:/etc/rc.d/rc.sysinit執行/etc/rc.sysinit腳本,該腳本將會設置系統變量、網絡配置,并啟動swap、設定/proc、加載用戶自定義模塊、加載內核設置等。

第五步是根據第三步讀到的runlevel值來啟動對應的服務,如果值為3,就會運行/etc/rc3.d/下的所有腳本,如果值為5,就會運行/etc/rc5.d/下的所有腳本。

第六步將運行/etc/rc.local,第七步會生成終端或X Window來等待用戶登錄。

1.5.2 系統運行級別

前一節多次提到了runlevel這個詞,但是runlevel究竟是什么呢?我們說Linux默認有7個運行級,從運行級0到運行級6,每一個運行級所對應的含義如下:

運行級0:關機。

運行級1:單用戶模式,系統出現問題時可使用這種模式進入系統維護,典型的使用場景是在忘記root密碼時可進入此模式修改root密碼。

運行級2:多用戶模式,但是沒有網絡連接。

運行級3:完全多用戶模式,這也是Linux服務器最常見的運行級。

運行級4:保留未使用。

運行級5:窗口模式,支持多用戶,支持網絡。

運行級6:重啟。

任何時候Linux只能在一種runlevel下運行。那么不同的runlevel之間到底有什么區別呢?上一節中提到,系統在啟動的過程中會根據/etc/inittab中的設定讀取runlevel的數值X,并相應地讀取和運行/etc/rcX.d(X代表0~6)下所有的腳本。看一下/etc/rc3.d中的內容:

[root@localhost ~]# ll /etc/rc3.d/
total 288
......(略去內容)......
lrwxrwxrwx 1 root root 15 Oct  7 20:52 K15httpd-> ../init.d/httpd
lrwxrwxrwx 1 root root 13 Oct  7 20:55 K20nfs-> ../init.d/nfs
......(略去內容)......
lrwxrwxrwx 1 root root 18 Oct  7 20:50 S08iptables-> ../init.d/iptables
lrwxrwxrwx 1 root root 17 Oct  7 20:52 S10network-> ../init.d/network
......(略去內容)......

注意看每行中第9列的內容,分別是以K或S開頭、后跟兩位數字、再接服務名的文件,其實它們鏈接的是上層init.d目錄中的服務腳本。系統在啟動過程中,會首先運行以K開頭的腳本,全部運行完畢后再運行以S開頭的腳本,在運行所有K開頭的腳本時,又會嚴格按照K后面的數字大小依次來運行,也就是數字小的先運行,數字大的后運行。同樣,在運行S開頭的腳本時,也是按照這個原則進行的,即先運行數字小的腳本,再運行數字大的腳本。K和S的意思分別是停止(kill)和啟動(start),只要定義好不同運行級需要啟動和停止的服務,就可以讓系統在不同的運行級下啟動和關閉不一樣的服務。再來對比一下/etc/rc1.d下的關于network項內容:

[root@localhost ~]# ll /etc/rc1.d/
total 288
......(略去內容)......
lrwxrwxrwx 1 root root 17 Oct  7 20:52 K90network-> ../init.d/network
......(略去內容)......

在運行級為1的時候,network是在開機啟動的過程中被關閉的(K90network),而在運行級為3的時候,network則是被開啟的(S10network)。

1.5.3 服務啟動腳本

上節在介紹Linux運行級時,談到在Linux啟動過程中會使用K或S開頭的腳本關閉或啟動相關服務,那么這是怎么做到的呢?本節將通過一個腳本幫助大家理解。當然因為這里還沒有講到Shell編程的內容,所以只做非常簡單的講解。

#!/bin/bash
#一個bash腳本開始的標記,必須是用“#!/bin/bash”開頭,含義是提示系統在運行該腳本時使用
/bin/bash作為執行該文件的解釋器
#       /etc/rc.d/init.d/atd
#說明自己的絕對路徑
# Starts the at daemon
#
# chkconfig: 345 95 5
#345是說在運行級是345的時候,默認開啟atd,也就是Start
#95是說明當默認設置為on的時候,運行優先級定為95
#5是說明當默認設置為off的時候,停止優先級定為5
# description: Runs commands scheduled by the at command at the time
#    specified when at was run,and runs batch commands when the load
#    average is low enough.
# processname: atd

# Source function library. . /etc/init.d/functions #使用“.”命令包含文件,可以使用/etc/init.d/functions中定義的函數 # pull in sysconfig settings [-f /etc/sysconfig/atd ] && . /etc/sysconfig/atd test-x /usr/sbin/atd || exit 0 RETVAL=0
# # See how we were called. # prog="atd" start() { # Check if atd is already running if [ !-f /var/lock/subsys/atd ]; then echo-n $"Starting $prog: " daemon /usr/sbin/atd $OPTS && success || failure RETVAL=$? [ $RETVAL-eq 0 ] && touch /var/lock/subsys/atd echo fi return $RETVAL } #定義start函數 stop() { echo-n $"Stopping $prog: " killproc /usr/sbin/atd RETVAL=$? [ $RETVAL-eq 0 ] && rm-f /var/lock/subsys/atd echo return $RETVAL } #定義stop函數
restart() { stop start } #定義restart函數,實際調用時,先執行stop函數后執行start函數 reload() { restart } #定義reload函數,實際調用時,就是執行restart函數 status_at() { status /usr/sbin/atd } #定義status_at函數,實際調用時,是調用/etc/init.d/functions中定義的函數status, 參數為/usr/sbin/atd,也就是查詢atd的運行狀態 case "$1" in start) start ;; stop) stop ;; reload|restart) restart ;; condrestart) if [-f /var/lock/subsys/atd ]; then restart fi ;; status) status_at ;; *) echo $"Usage: $0 {start|stop|restart|condrestart|status}" exit 1 esac
exit $? exit $RETVAL

上面的腳本實際上是/etc/init.d/atd中的內容,我在腳本中做了一些注釋來簡單講解腳本的處理過程。當atd設置為啟動時,將會在對應的/etc/rcX.d(X代表0~6)目錄下顯示:S95atd-> ../init.d/atd,系統根據第一個字母S判定atd需要啟動,然后會調用命令/etc/init.d/atd start;當atd設置為關閉時,將會在對應的/etc/rcX.d目錄下顯示:K05atd-> ../init.d/atd,系統根據第一個字母K判定atd需要關閉,然后調用命令/etc/init.d/atd stop,這樣就實現了對atd的啟停控制,其他服務也是同樣的原理。

1.5.4 Grub介紹

在之前的系統引導概述中,相信大家已經看到Grub這個詞了,它的全稱為Grand Unified Bootloader,也是GNU贊助的項目之一,事實上Grub可以引導多個操作系統。早先Linux的引導程序是lilo,含義為Linux Loader,這是ext2文件系統中特有的引導程序,現在基本上已經不再使用了。

在之前的系統啟動流程中提到,計算機在啟動時,BIOS默認會從硬盤上的第0柱面、第0磁道、第一個扇區中讀取512字節的數據來引導系統啟動,但是Grub這個程序遠遠大于512字節,這一個扇區又如何能夠載下Grub所有的內容呢?為了解決這個問題,實際上Grub的啟動是分成兩段完成的。第一段以stage1作為主引導程序,它的主要任務是定位和裝載第二段引導程序,并轉交控制權,即stage2。Grub目錄中的內容如下:

[root@localhost grub]# cd /boot/grub/
[root@localhost grub]# ls-l
total 257
-rw-r--r-- 1 root root     63 Oct  7 21:02 device.map
-rw-r--r-- 1 root root   7584 Oct  7 21:02 e2fs_stage1_5
-rw-r--r-- 1 root root   7456 Oct  7 21:02 fat_stage1_5
-rw-r--r-- 1 root root   6720 Oct  7 21:02 ffs_stage1_5
-rw------- 1 root root    573 Oct  7 21:02 grub.conf
-rw-r--r-- 1 root root   6720 Oct  7 21:02 iso9660_stage1_5
-rw-r--r-- 1 root root   8192 Oct  7 21:02 jfs_stage1_5
lrwxrwxrwx 1 root root     11 Oct  7 21:02 menu.lst-> ./grub.conf
-rw-r--r-- 1 root root   6880 Oct  7 21:02 minix_stage1_5
-rw-r--r-- 1 root root   9248 Oct  7 21:02 reiserfs_stage1_5
-rw-r--r-- 1 root root  55808 Mar 13  2009 splash.xpm.gz
-rw-r--r-- 1 root root    512 Oct  7 21:02 stage1
-rw-r--r-- 1 root root 104988 Oct  7 21:02 stage2
-rw-r--r-- 1 root root   7072 Oct  7 21:02 ufs2_stage1_5
-rw-r--r-- 1 root root   6272 Oct  7 21:02 vstafs_stage1_5
-rw-r--r-- 1 root root   8904 Oct  7 21:02 xfs_stage1_5

注意一下,有一個stage1的文件,大小為512字節,正好是一個扇區的大小。其實這不是一個巧合,stage1確實是MBR的一個副本。還可以看到有很多文件是以stage1_5結尾的,事實上這些文件是各種文件系統的驅動文件,當stage1從不同的文件系統中讀取stage2時將用到這些驅動文件。

對Grub的配置可以通過修改Grub的配置文件完成,一般配置文件為/boot/grub/grub. conf。修改后的配置將直接影響下次引導時的行為。下面是系統安裝過程中自動生成的配置:

# grub.conf generated by anaconda
#
# Note that you do not have to rerun grub after making changes to this file
# NOTICE:  You have a /boot partition.  This means that
#          all kernel and initrd paths are relative to /boot/,eg.
#          root (hd0,0)
#          kernel /vmlinuz-version ro root=/dev/sda3
#          initrd /initrd-version.img
#boot=/dev/sda
default=0
timeout=5
splashimage=(hd0,0)/grub/splash.xpm.gz
hiddenmenu
title CentOS (2.6.18-194.el5)
        root (hd0,0)
        kernel /vmlinuz-2.6.18-194.el5 ro root=LABEL=/rhgb quiet
        initrd /initrd-2.6.18-194.el5.img

其中,default=0的含義是默認從第一個title處啟動。這里的配置文件中只有一個title項,但是如果還有第二個title項,則可以配置默認從第二個title處引導系統,只要把default改為1就可以了(注意這里的計數是從0開始的)。

timeout=5的含義是顯示這個title項時,同時有5秒倒計時,5秒內可以按回車鍵提前從默認的啟動項中啟動,也可以按上下鍵立即停止倒計時,選定一個title,然后按回車鍵確認從選定的title中啟動。也可以選定某一個title后,按e鍵進入編輯模式,這樣可以即時對Grub進行配置,但是這時的配置并不會寫入配置文件中,而只是當時生效。

splashimage是指定啟動時的背景圖像。如果系統使用的是sata磁盤,則命名規則為:第一塊磁盤是sda,第二塊磁盤是sdb,以此類推。對磁盤進行分區后的分區命名規則是,第一個磁盤的第一個分區是sda1,第一個磁盤的第二個分區是sda2,第二個磁盤的第一個分區是sdb1,第二個磁盤的第二個分區是sdb2。而Grub使用hd0代表第一塊磁盤,而這里(hd0,0)的含義是第一塊磁盤的第一個分區。所以(hd0,0)/grub/splash.xpm.gz的絕對路徑就是/boot/grub/splash.xpm.gz,這是一個壓縮文件,Grub在啟動時會自動對該文件做解壓縮。

hiddenmenu是設置啟動時是否顯示菜單。

title是系統引導時顯示的名字,這只是一種識別性的文字,可以任意修改。文件的最后3行是相互關聯的,第一行root(hd0,0)參數指定了內核放置的分區;第二行kernel/vmlinuz-2.6.18-194.el5 ro root=LABEL=/rhgb quiet指定了內核的路徑,表示內核是(hd0,0)分區中的vmlinuz-2.6.18-194.el5文件,ro root=LABEL=/rhgb quiet是啟動內核時向內核傳入的參數;最后一行initrd/initrd-2.6.18-194.el5.img指定了initrd文件的路徑是(hd0,0)中的initrd-2.6.18-194.el5.img文件。

這里第一次提到initrd文件,其英文含義是boot loader initialized RAM disk,也就是boot loader用于初始化的內存磁盤,是系統啟動時的臨時文件系統,kernel通過讀取initrd來獲得各種可執行文件和設備驅動,并掛載真實的文件系統,然后卸載這個臨時文件系統。在桌面或者Linux服務器中,initrd文件只是一個臨時的文件系統,其生命周期很短,只會用作掛載真實文件系統的一個接力,在很多嵌入式系統中,由于不需要外接大存儲設備,所以initrd會作為永久的文件系統直接使用。

主站蜘蛛池模板: 陕西省| 北宁市| 海伦市| 德庆县| 齐齐哈尔市| 宝清县| 铅山县| 景洪市| 乌拉特前旗| 赫章县| 深泽县| 吴忠市| 乌兰浩特市| 正安县| 永宁县| 惠州市| 弥渡县| 连城县| 钟祥市| 泰宁县| 宜城市| 自治县| 福贡县| 皋兰县| 沁水县| 蒙城县| 武宣县| 沙湾县| 酒泉市| 紫金县| 新建县| 宜兴市| 彰化县| 皋兰县| 宾阳县| 阿合奇县| 万全县| 新河县| 新营市| 汉阴县| 黄平县|