Linux啟動過程綜述(轉)

post0發表於2007-08-09
Linux啟動過程綜述(轉)[@more@]

內容:

一. Bootloader

二.Kernel引匯入口

三.核心資料結構初始化--核心引導第一部分

四.外設初始化--核心引導第二部分

五.init程式和inittab引導指令

六.rc啟動指令碼

七.getty和login

八.bash

附:XDM方式登入

作者:楊沙洲

本文以Redhat 6.0 Linux 2.2.19 for Alpha/AXP為平臺,描述了從開機到登入的 Linux 啟動全過程。該文對i386平臺同樣適用。

一. Bootloader

在Alpha/AXP平臺上引導Linux通常有兩種方法,一種是由MILO及其他類似的載入程式引導,另一種是由Firmware直接引導。MILO功能與i386平臺的LILO相近,但內建有基本的磁碟驅動程式(如IDE、SCSI等),以及常見的檔案系統驅動程式(如ext2,iso9660等), firmware有ARC、SRM兩種形式,ARC具有類BIOS介面,甚至還有多重引導的設定;而SRM則具有功能強大的命令列介面,使用者可以在控制檯上使用boot等命令引導系統。ARC有分割槽(Partition)的概念,因此可以訪問到分割槽的首扇區;而SRM只能將控制轉給磁碟的首扇區。兩種firmware都可以透過引導MILO來引導Linux,也可以直接引導Linux的引導程式碼。

“arch/alpha/boot”下就是製作Linux Bootloader的檔案。“head.S”檔案提供了對 OSF PAL/1的呼叫入口,它將被編譯後置於引導扇區(ARC的分割槽首扇區或SRM的磁碟0扇區),得到控制後初始化一些資料結構,再將控制轉給“main.c”中的start_kernel(), start_kernel()向控制檯輸出一些提示,呼叫pal_init()初始化PAL程式碼,呼叫openboot() 開啟引導裝置(透過讀取Firmware環境),呼叫load()將核心程式碼載入到START_ADDR(見 “include/asm-alpha/system.h”),再將Firmware中的核心引導引數載入到ZERO_PAGE(0) 中,最後呼叫runkernel()將控制轉給0x100000的kernel,bootloader部分結束。

“arch/alpha/boot/bootp.c”以“main.c”為基礎,可代替“main.c”與“head.S” 生成用於BOOTP協議網路引導的Bootloader。

Bootloader中使用的所有“srm_”函式在“arch/alpha/lib/”中定義。

以上這種Boot方式是一種最簡單的方式,即不需其他工具就能引導Kernel,前提是按照 Makefile的指導,生成bootimage檔案,內含以上提到的bootloader以及vmlinux,然後將 bootimage寫入自磁碟引導扇區始的位置中。

當採用MILO這樣的載入程式來引導Linux時,不需要上面所說的Bootloader,而只需要 vmlinux或vmlinux.gz,載入程式會主動解壓載入核心到0x1000(小核心)或0x100000(大核心),並直接進入核心引導部分,即本文的第二節。

對於I386平臺

i386系統中一般都有BIOS做最初的引導工作,那就是將四個主分割槽表中的第一個可引導 分割槽的第一個扇區載入到真實模式地址0x7c00上,然後將控制轉交給它。

在“arch/i386/boot”目錄下,bootsect.S是生成引導扇區的彙編原始碼,它首先將自己複製到0x90000上,然後將緊接其後的setup部分(第二扇區)複製到0x90200,將真正的核心程式碼複製到0x100000。以上這些複製動作都是以bootsect.S、setup.S以及vmlinux在磁碟上連續存放為前提的,也就是說,我們的bzImage檔案或者zImage檔案是按照bootsect,setup, vmlinux這樣的順序組織,並存放於始於引導分割槽的首扇區的連續磁碟扇區之中。

bootsect.S完成載入動作後,就直接跳轉到0x90200,這裡正是setup.S的程式入口。 setup.S的主要功能就是將系統引數(包括記憶體、磁碟等,由BIOS返回)複製到 0x90000-0x901FF記憶體中,這個地方正是bootsect.S存放的地方,這時它將被系統引數覆蓋。以後這些引數將由保護模式下的程式碼來讀取。

除此之外,setup.S還將video.S中的程式碼包含進來,檢測和設定顯示器和顯示模式。最後,setup.S將系統轉換到保護模式,並跳轉到0x100000(對於bzImage格式的大核心是 0x100000,對於zImage格式的是0x1000)的核心引導程式碼,Bootloader過程結束。

對於2.4.x版核心

沒有什麼變化。

二.Kernel引匯入口

在arch/alpha/vmlinux.lds的連結指令碼控制下,連結程式將vmlinux的入口置於 "arch/alpha/kernel/head.S"中的__start上,因此當Bootloader跳轉到0x100000時, __start處的程式碼開始執行。__start的程式碼很簡單,只需要設定一下全域性變數,然後就跳轉到start_kernel去了。start_kernel()是"init/main.c"中的asmlinkage函式,至此,啟動過程轉入體系結構無關的通用C程式碼中。

對於I386平臺

在i386體系結構中,因為i386本身的問題,在"arch/alpha/kernel/head.S"中需要更多的設定,但最終也是透過call SYMBOL_NAME(start_kernel)轉到start_kernel()這個體系結構無關的函式中去執行了。

所不同的是,在i386系統中,當核心以bzImage的形式壓縮,即大核心方式(__BIG_KERNEL__)壓縮時就需要預先處理bootsect.S和setup.S,按照大核模式使用$(CPP) 處理生成bbootsect.S和bsetup.S,然後再編譯生成相應的.o檔案,並使用 "arch/i386/boot/compressed/build.c"生成的build工具,將實際的核心(未壓縮的,含 kernel中的head.S程式碼)與"arch/i386/boot/compressed"下的head.S和misc.c合成到一起,其中的 head.S代替了"arch/i386/kernel/head.S"的位置,由Bootloader引導執行(startup_32入口),然後它呼叫misc.c中定義的decompress_kernel()函式,使用 "lib/inflate.c"中定義的gunzip()將核心解壓到0x100000,再轉到其上執行 "arch/i386/kernel/head.S"中的startup_32程式碼。

對於2.4.x版核心

沒有變化。

三.核心資料結構初始化--核心引導第一部分

start_kernel()中呼叫了一系列初始化函式,以完成kernel本身的設定。 這些動作有的是公共的,有的則是需要配置的才會執行的。

在start_kernel()函式中,

輸出Linux版本資訊(printk(linux_banner))

設定與體系結構相關的環境(setup_arch())

頁表結構初始化(paging_init())

使用"arch/alpha/kernel/entry.S"中的入口點設定系統自陷入口(trap_init())

使用alpha_mv結構和entry.S入口初始化系統IRQ(init_IRQ())

核心程式排程器初始化(包括初始化幾個預設的Bottom-half,sched_init())

時間、定時器初始化(包括讀取CMOS時鐘、估測主頻、初始化定時器中斷等,time_init())

提取並分析核心啟動引數(從環境變數中讀取引數,設定相應標誌位等待處理,(parse_options())

控制檯初始化(為輸出資訊而先於PCI初始化,console_init())

剖析器資料結構初始化(prof_buffer和prof_len變數)

核心Cache初始化(描述Cache資訊的Cache,kmem_cache_init())

延遲校準(獲得時鐘jiffies與CPU主頻ticks的延遲,calibrate_delay())

記憶體初始化(設定記憶體上下界和頁表項初始值,mem_init())

建立和設定內部及通用cache("slab_cache",kmem_cache_sizes_init())

建立uid taskcount SLAB cache("uid_cache",uidcache_init())

建立檔案cache("files_cache",filescache_init())

建立目錄cache("dentry_cache",dcache_init())

建立與虛存相關的cache("vm_area_struct","mm_struct",vma_init())

塊裝置讀寫緩衝區初始化(同時建立"buffer_head"cache使用者加速訪問,buffer_init())

建立頁cache(記憶體頁hash表初始化,page_cache_init())

建立訊號佇列cache("signal_queue",signals_init())

初始化記憶體inode表(inode_init())

建立記憶體檔案描述符表("filp_cache",file_table_init())

檢查體系結構漏洞(對於alpha,此函式為空,check_bugs())

SMP機器其餘CPU(除當前引導CPU)初始化(對於沒有配置SMP的核心,此函式為空,smp_init())

啟動init過程(建立第一個核心執行緒,呼叫init()函式,原執行序列呼叫cpu_idle() 等待排程,init())

至此start_kernel()結束,基本的核心環境已經建立起來了。

對於I386平臺

i386平臺上的核心啟動過程與此基本相同,所不同的主要是實現方式。

對於2.4.x版核心

2.4.x中變化比較大,但基本過程沒變,變動的是各個資料結構的具體實現,比如Cache。

四.外設初始化--核心引導第二部分

init()函式作為核心執行緒,首先鎖定核心(僅對SMP機器有效),然後呼叫 do_basic_setup()完成外設及其驅動程式的載入和初始化。過程如下:

匯流排初始化(比如pci_init())

網路初始化(初始化網路資料結構,包括sk_init()、skb_init()和proto_init()三部分,在proto_init()中,將呼叫protocols結構中包含的所有協議的初始化過程,sock_init())

建立bdflush核心執行緒(bdflush()過程常駐核心空間,由核心喚醒來清理被寫過的記憶體緩衝區,當bdflush()由kernel_thread()啟動後,它將自己命名為kflushd)

建立kupdate核心執行緒(kupdate()過程常駐核心空間,由核心按時排程執行,將記憶體緩衝區中的資訊更新到磁碟中,更新的內容包括超級塊和inode表)

設定並啟動核心調頁執行緒kswapd(為了防止kswapd啟動時將版本資訊輸出到其他資訊中間,核心線呼叫kswapd_setup()設定kswapd執行所要求的環境,然後再建立 kswapd核心執行緒)

建立事件管理核心執行緒(start_context_thread()函式啟動context_thread()過程,並重新命名為keventd)

裝置初始化(包括並口parport_init()、字元裝置chr_dev_init()、塊裝置 blk_dev_init()、SCSI裝置scsi_dev_init()、網路裝置net_dev_init()、磁碟初始化及分割槽檢查等等, device_setup())

執行檔案格式設定(binfmt_setup())

啟動任何使用__initcall標識的函式(方便核心開發者新增啟動函式,do_initcalls())

檔案系統初始化(filesystem_setup())

安裝root檔案系統(mount_root())

至此do_basic_setup()函式返回init(),在釋放啟動記憶體段(free_initmem())並給核心解鎖以後,init()開啟 /dev/console裝置,重定向stdin、stdout和stderr到控制檯,最後,搜尋檔案系統中的init程式(或者由init=命令列引數指定的程式),並使用 execve()系統呼叫載入執行init程式。

init()函式到此結束,核心的引導部分也到此結束了,這個由start_kernel()建立的第一個執行緒已經成為一個使用者模式下的程式了。此時系統中存在著六個執行實體:

start_kernel()本身所在的執行體,這其實是一個"手工"建立的執行緒,它在建立了init()執行緒以後就進入cpu_idle()迴圈了,它不會在程式(執行緒)列表中出現

init執行緒,由start_kernel()建立,當前處於使用者態,載入了init程式

kflushd核心執行緒,由init執行緒建立,在核心態執行bdflush()函式

kupdate核心執行緒,由init執行緒建立,在核心態執行kupdate()函式

kswapd核心執行緒,由init執行緒建立,在核心態執行kswapd()函式

keventd核心執行緒,由init執行緒建立,在核心態執行context_thread()函式

對於I386平臺

基本相同。

對於2.4.x版核心

這一部分的啟動過程在2.4.x核心中簡化了不少,預設的獨立初始化過程只剩下網路 (sock_init())和建立事件管理核心執行緒,而其他所需要的初始化都使用__initcall()宏 包含在do_initcalls()函式中啟動執行。

五.init程式和inittab引導指令

init程式是系統所有程式的起點,核心在完成核內引導以後,即在本執行緒(程式)空 間內載入init程式,它的程式號是1。

init程式需要讀取/etc/inittab檔案作為其行為指標,inittab是以行為單位的描述性(非執行性)文字,每一個指令行都具有以下格式:

id:runlevel:action:process其中id為入口識別符號,runlevel為執行級別,action為動作代號,process為具體的執行程式。

id一般要求4個字元以內,對於getty或其他login程式項,要求id與tty的編號相同,否則getty程式將不能正常工作。

runlevel是init所處於的執行級別的標識,一般使用0-6以及S或s。0、1、6執行級別被系統保留,0作為shutdown動作,1作為重啟至單使用者模式,6為重啟;S和s意義相同,表示單使用者模式,且無需inittab檔案,因此也不在inittab中出現,實際上,進入單使用者模式時, init直接在控制檯(/dev/console)上執行/sbin/sulogin。

在一般的系統實現中,都使用了2、3、4、5幾個級別,在Redhat系統中,2表示無NFS支援的多使用者模式,3表示完全多使用者模式(也是最常用的級別),4保留給使用者自定義,5表示XDM圖形登入方式。7-9級別也是可以使用的,傳統的Unix系統沒有定義這幾個級別。runlevel可以是並列的多個值,以匹配多個執行級別,對大多數action來說,僅當runlevel與當前執行級別匹配成功才會執行。

initdefault是一個特殊的action值,用於標識預設的啟動級別;當init由核心啟用以後,它將讀取inittab中的initdefault項,取得其中的runlevel,並作為當前的執行級別。如果沒有inittab檔案,或者其中沒有initdefault項,init將在控制檯上請求輸入 runlevel。

sysinit、boot、bootwait等action將在系統啟動時無條件執行,而忽略其中的runlevel,其餘的action(不含 initdefault)都與某個runlevel相關。各個action的定義在inittab的man手冊中有詳細的描述。

在Redhat系統中,一般情況下inittab都會有如下幾項:

id:3:initdefault:

#表示當前預設執行級別為3--完全多工模式;

si::sysinit:/etc/rc.d/rc.sysinit

#啟動時自動執行/etc/rc.d/rc.sysinit指令碼

l3:3:wait:/etc/rc.d/rc 3

#當執行級別為3時,以3為引數執行/etc/rc.d/rc指令碼,init將等待其返回

0:12345:respawn:/sbin/mingetty tty0

#在1-5各個級別上以tty0為引數執行/sbin/mingetty程式,開啟tty0終端用於

#使用者登入,如果程式退出則再次執行mingetty程式

x:5:respawn:/usr/bin/X11/xdm -nodaemon

#在5級別上執行xdm程式,提供xdm圖形方式登入介面,並在退出時重新執行

六.rc啟動指令碼

上一節已經提到init程式將啟動執行rc指令碼,這一節將介紹rc指令碼具體的工作。

一般情況下,rc啟動指令碼都位於/etc/rc.d目錄下,rc.sysinit中最常見的動作就是啟用交換分割槽,檢查磁碟,載入硬體模組,這些動作無論哪個執行級別都是需要優先執行的。僅當rc.sysinit執行完以後init才會執行其他的boot或bootwait動作。

如果沒有其他boot、bootwait動作,在執行級別3下,/etc/rc.d/rc將會得到執行,命令列引數為3,即執行 /etc/rc.d/rc3.d/目錄下的所有檔案。rc3.d下的檔案都是指向/etc/rc.d/init.d/目錄下各個Shell指令碼的符號連線,而這些指令碼一般能接受start、stop、restart、status等引數。rc指令碼以start引數啟動所有以S開頭的指令碼,在此之前,如果相應的指令碼也存在K打頭的連結,而且已經處於執行態了(以/var/lock/subsys/下的檔案作為標誌),則將首先啟動K開頭的指令碼,以stop 作為引數停止這些已經啟動了的服務,然後再重新執行。顯然,這樣做的直接目的就是當init改變執行級別時,所有相關的服務都將重啟,即使是同一個級別。

rc程式執行完畢後,系統環境已經設定好了,下面就該使用者登入系統了。

七.getty和login

在rc返回後,init將得到控制,並啟動mingetty(見第五節)。mingetty是getty的簡化,不能處理串列埠操作。getty的功能一般包括:

開啟終端線,並設定模式

輸出登入介面及提示,接受使用者名稱的輸入

以該使用者名稱作為login的引數,載入login程式

預設的登入提示記錄在/etc/issue檔案中,但每次啟動,一般都會由rc.local指令碼根據系統環境重新生成。

注:用於遠端登入的提示資訊位於/etc/issue.net中。

login程式在getty的同一個程式空間中執行,接受getty傳來的使用者名稱引數作為登入的使用者名稱。

如果使用者名稱不是root,且存在/etc/nologin檔案,login將輸出nologin檔案的內容,然後退出。這通常用來系統維護時防止非root使用者登入。

只有/etc/securetty中登記了的終端才允許root使用者登入,如果不存在這個檔案,則root可以在任何終端上登入。/etc/usertty檔案用於對使用者作出附加訪問限制,如果不存在這個檔案,則沒有其他限制。

當使用者登入透過了這些檢查後,login將搜尋/etc/passwd檔案(必要時搜尋 /etc/shadow檔案)用於匹配密碼、設定主目錄和載入shell。如果沒有指定主目錄,將預設為根目錄;如果沒有指定shell,將預設為 /bin/sh。在將控制轉交給shell以前, getty將輸出/var/log/lastlog中記錄的上次登入系統的資訊,然後檢查使用者是否有新郵件(/usr/spool/mail/ {username})。在設定好shell的uid、gid,以及TERM,PATH 等環境變數以後,程式載入shell,login的任務也就完成了。

八.bash

執行級別3下的使用者login以後,將啟動一個使用者指定的shell,以下以/bin/bash為例繼續我們的啟動過程。

bash是Bourne Shell的GNU擴充套件,除了繼承了sh的所有特點以外,還增加了很多特性和功能。由login啟動的bash是作為一個登入shell啟動的,它繼承了 getty設定的TERM、PATH等環境變數,其中PATH對於普通使用者為"/bin:/usr/bin:/usr/local/bin",對於 root 為"/sbin:/bin:/usr/sbin:/usr/bin"。作為登入shell,它將首先尋找/etc/profile 指令碼檔案,並執行它;然後如果存在~/.bash_profile,則執行它,否則執行 ~/.bash_login,如果該檔案也不存在,則執行~/.profile檔案。然後bash將作為一個互動式shell執行~/.bashrc檔案(如果存在的話),很多系統中,~/.bashrc都將啟動 /etc/bashrc作為系統範圍內的配置檔案。

當顯示出命令列提示符的時候,整個啟動過程就結束了。此時的系統,執行著核心,執行著幾個核心執行緒,執行著init程式,執行著一批由rc啟動指令碼啟用的守護程式(如 inetd等),執行著一個bash作為使用者的命令直譯器。

附:XDM方式登入

如果預設執行級別設為5,則系統中不光有1-6個getty監聽著文字終端,還有啟動了一個XDM的圖形登入視窗。登入過程和文字方式差不多,也需要提供使用者名稱和口令,XDM 的配置檔案預設為/usr/X11R6/lib/X11/xdm/xdm-config檔案,其中指定了 /usr/X11R6/lib/X11/xdm/xsession作為XDM的會話描述指令碼。登入成功後,XDM將執行這個指令碼以執行一個會話管理器,比如gnome-session等。

除了XDM以外,不同的視窗管理系統(如KDE和GNOME)都提供了一個XDM的替代品,如gdm和kdm,這些程式的功能和XDM都差不多。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/8225414/viewspace-939133/,如需轉載,請註明出處,否則將追究法律責任。

相關文章