和Window等其他作業系統一樣,linux的啟動也分為兩個階段:引導(boot)和啟動(startup)。引導階段開始於打開電源開關,接下來板載程序BIOS的開始POST上電自檢主過程,結束於內核初始化完成。啟動階段接管剩餘的工作,直到作業系統初始化完成進入可操作狀態,並能夠執行用戶的功能性任務。
我們不花過多篇幅講解引導階段的硬體板載程序加載運行的過程。事實上,由於在板載程序上很多行為基本上固定的,程式設計師很難介入,所以接下來主要講講主板的引導程序如何加載內核程序以及控制權交給linux作業系統之後的各個服務的啟動流程。
所謂引導程序,一個用於計算機尋找作業系統內核並加載其到內存的智能程序,通常位於硬碟的第一個扇區,並由BIOS載入內存執行。為什麼需要引導程序,而不是直接由BIOS加載作業系統?原因是BOIS只會自動加載硬碟的第一個扇區的512位元組的內容,而作業系統的大小遠遠大於這個值,所以才會先加載引導程序,然後通過引導程序加載程序加載作業系統到內存中。
目前,各個Linux發行版主流的引導程序是GRUB(GRand Unified BootLoader)。GRUB的作用有以下幾個: - 加載作業系統的內核 - 擁有一個可以讓用戶選擇到底啟動哪個系統的的菜單界面 - 可以調用其他的啟動引導程序,來實現多系統引導
GRUB1現在已經逐步被棄用,在大多數現代發行版上它已經被GRUB2所替換。GRUB2通過/boot/grub2/grub.cfg進行配置,最終GRUB定位和加載linux內核程序到內存中,並轉移控制權到內核程序。
內核程序的相關文件位於/boot目錄下,這些內核文件均帶有前綴vmlinuz。內核文件都是以一種自解壓的壓縮格式存儲以節省空間。在選定的內核加載到內存中並開始執行後,在其進行任何工作之前,內核文件首先必須從壓縮格式解壓自身。
ps -ef
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 Mar29 ? 00:00:17 /sbin/init
root 2 0 0 Mar29 ? 00:00:00 [kthreadd]
root 3 2 0 Mar29 ? 00:00:22 [ksoftirqd/0]
root 5 2 0 Mar29 ? 00:00:00 [kworker/0:0H]
...
定義、管理和控制init進程的系統被稱為init系統。我們知道僅僅將內核運行起來是沒有什麼實際意義,必須由init系統將整個linux作業系統代入可操作狀態。init系統主要的任務就是去運行這些開機啟動的程序。但是,不同的場合需要啟動不同的程序來進入不同的預設運行模式。例如,啟動shell進行人機互動,這樣就可以讓計算機執行一些預訂程序完成有實際意義的任務,啟動X圖形系統以便提供友好的人機界面,更加高效的完成任務。這裡,命令行界面的shell或者X圖形界面都是一種預設的運行模式。
大多數linux發行版的init系統是和System V相兼容的,被稱為SysVInit,這就是最初的init系統。在linux主要應用於伺服器和PC機的時代,SysVInit運行概念簡單清晰,運行穩定。但是它主要依賴於Shell腳本,導致啟動速度太慢。這點在很少重啟的伺服器上並沒有多少影響,而隨著linux內核的不斷演化發展linux作業系統越來越多地被應用到移動終端設備的時候,啟動慢就嚴重影響用戶體驗,被越來越多人詬病。
為了解決啟動慢的問題,人們開始思考改進SysVInit,先後出現了UpStart和Systemd這兩個主要的新一代init系統。UpStart已經開發了8年多,在不少系統中已經替換SysVInit,而Systemd出現比較晚,但發展更快,大有取代UpStart統治各個linux發行版的趨勢。事實上,Ubuntu和RHEL採用UpStart替代了傳統的SysVInit。而Fedora發行版則從版本15開始使用Systemd。
對於使用SysVInit的老式linux發行版來說,使用「運行級別」(Run Level)來定義」預訂的運行模式」。這樣的linux發行版一般預置七種運行級別(0-6)。通常情況下,0代表關機,1代表單用戶模式(也就是維護模式),6代表重啟。運行級別2-5,各個發行版不太一樣,但基本都是多用戶模式(也就是正常模式)。
init進程首先讀取檢查運行級別的設置文件/etc/inittab是否含有initdefault項,它代表系統默認的運行級別;如果沒有默認運行級別,那麼就需要用戶進入系統控制臺手動決定進入何種運行模式。運行級別的設置文件/etc/inittab的第一行一般是這樣的:
id:2:initdefault:
這代表系統啟動時的默認運行級別為2,如果需要指定其他級別,需要手動修改這個值。
但是問題來了,在不同的運行級別中,系統需要初始化運行的服務進程和需要進行的初始化準備都是不同的,有些linux發行版的運行級別3不需要啟動X圖形界面系統。系統怎麼知道每個級別應該加載哪些程序呢?…回答是每個運行級別在/etc目錄下面,都有一個對應的子目錄,指定要加載的服務程序。這樣,用戶只需要指定需要進入哪種運行級別,SysVInit將負責執行所有該級別下必須的初始化工作。
ll /etc/rc2.d
total 20
lrwxrwxrwx 1 root root 16 Aug 20 2018 K01autofs -> ../init.d/autofs*
-rw-r--r-- 1 root root 677 Feb 5 2016 README
lrwxrwxrwx 1 root root 16 Aug 20 2018 S01apport -> ../init.d/apport*
lrwxrwxrwx 1 root root 17 Aug 20 2018 S01rsyslog -> ../init.d/rsyslog*
lrwxrwxrwx 1 root root 15 Aug 20 2018 S01uuidd -> ../init.d/uuidd*
lrwxrwxrwx 1 root root 17 Aug 20 2018 S02postfix -> ../init.d/postfix*
lrwxrwxrwx 1 root root 15 Aug 20 2018 S02rsync -> ../init.d/rsync*
lrwxrwxrwx 1 root root 13 Aug 20 2018 S02ssh -> ../init.d/ssh*
...
我們看到,除了README以外,其他文件名都是」字母S+兩位數字+程序名」的形式。字母S表示Start,代表在當前級別下運行啟動對應程序;字母K,就代表Kill,即從其他運行級別切換過來時需要關閉的程序;後面的兩位數字表示處理順序,數字越小越早處理,所以第一個啟動的程序是apport,然後是rsyslog、uuidd、postfix…數字相同時,則按照程序名的字母順序啟動,所以rsyslog會先於uuidd啟動。
同時,我們也注意到,/etc/rcN.d目錄裡列出的程序其實都設為符號連結,指向另外一個目錄/etc/init.d下真正的程序啟動腳本,init進程逐一加載開機啟動程序,其實就是運行這個目錄裡的啟動腳本。這樣做的好處是就是如果需要手動關閉或重啟某個進程,直接到目錄/etc/init.d中尋找對應的啟動腳本即可。比如,要重啟ssh服務,只需要運行下面的命令:
/etc/init.d/ssh restart
當linux內核進入2.6之後,內核的很多功能完全適用於桌面系統甚至嵌入式設備,於是開發人員就開始嘗試將linux移植的工作。最具有代表性的是Ubuntu的開發人員Scott James Remnant,他曾試圖將linux安裝在筆記本電腦上,但發現老式的SysVInit並不適合桌面系統或可攜式設備,原因是:
針對以上SysVInit的不足,Ubuntu開發人員決定重新設計和開發一個全新的init系統,也就是UpStart。
UpStart基於事件機制,例如U盤插入USB接口後,udev得到內核通知,發現新設備接入,這就是一個新的事件。UpStart在感知到該事件之後觸發相應的等待任務,比如處理/etc/fstab中存在的掛載點。採用這種事件驅動的模式,UpStart完美地解決了即插即用設備帶來的新問題,同時通過並行啟動程序顯著減少系統啟動時間。
在上面的例子中,有7個不同的程序需要啟動:Job A、Job B…Job F。在SysVInit中,每一個啟動項目都由一個獨立的腳本負責,它們由SysVInit按照順序串行調用。因此總的啟動時間為T1+T2+T3+T4+T5+T6+T7。其中一些任務有依賴關係,比如A,B,C,D,而E和F卻和A,B,C,D無關。這種情況下,UpStart能夠並發地運行任務{(A,B,C),(D,E),F},使得總的啟動時間減少為T1+T2+T3。這無疑增加了系統啟動的並行性,從而提高了系統啟動速度。但是在UpStart中,有依賴關係的服務還是必須先後啟動串行執行,比如任務{(A,B,C)}。
在UpStart中主要的概念是job和event。所謂job其實就是完成特定任務的工作單元,例如啟動一個後臺服務,或者運行一個配置命令。每個job都等待響應一個或多個event,一旦獲取到響應的event,UpStart就觸發該對應的job完成相應的工作。使用UpStart的linux發行版系統初始化的過程是在job和event的相互協作下完成的,可以大致描述如下:內核初始化完成之後,init進程開始運行,init進程自身會發出不同的event,這些最初的event會觸發一些job運行;而每個job運行過程中會釋放新的不同的event,這些event又將觸發新的job運行,如此反覆,直到整個系統正常運行起來。
那麼,哪些event會觸發某個job的運行?這是由」job配置文件」定義的。任何一個job都是由一個job配置的文本文件定義的。這個文件包含多個部分,每個部分是一個完整的定義模塊,定義了job的一個方面,比如author部分定義了工作的作者。工作配置文件存放在/etc/init目錄下面,以.conf作為文件後綴的文件。下面,我們就來看看linux下面cron的job配置文件定義:
cron - regular background program processing daemon
cron is a standard UNIX program that runs user-specified programs at
34;regular background program processing daemon&cat /etc/system/system/sshd.service
[Unit]
Description=OpenSSH server daemon
[Service]
EnvironmentFile=/etc/sysconfig/sshd
ExecStartPre=/usr/sbin/sshd-keygen
ExecStart=/usrsbin/sshd –D $OPTIONS
ExecReload=/bin/kill –HUP $MAINPID
KillMode=process
Restart=on-failure
RestartSec=42s
[Install]
WantedBy=multi-user.target
可以看到,SSH服務配置單元文件分為三個部分:第一個是[Unit]部分,這裡僅僅包含一個簡短的描述信息;第二部分是[Service]定義,其中ExecStartPre定義啟動服務之前應該運行的命令,ExecStart定義啟動服務的具體命令行語法;第三部分是[Install],WangtedBy表明這個服務是在多用戶模式下所需要的。
既然說到多用戶模式,那就來看看multi-user.target的配置單元,它屬於target類型的配置單元:
34;$BASH_VERSION&34;$HOME/.bashrc&34;$HOME/.bashrc"
fi
fi
總之,不管是哪種情況,.bashrc在用戶登錄的時候都會執行,用戶的設置可以放心地都寫入這個文件了。
之所以有這麼繁多複雜的登錄設置文件是由於歷史原因造成的。在linux出現早期的時候,計算機運行速度很慢,載入配置文件需要很長時間,多數Shell的作者只好把配置文件分成了幾個部分,階段性載入。系統的通用設置放在/etc/profile,用戶個人的、需要被所有子進程繼承的設置放在.profile,不需要被繼承的設置放在.bashrc。