Linux 開機流程(x86版) --下


()


rc
執行完畢後,返回init。這時基本系統環境已經設置好了,各種守護行程也已經啟動了。init接下來會打開6個終端,以便用戶登錄系統。通過按Alt+Fn(n=1~6)可以在這6個終端中切換。在inittab中還會指定運行tty的方式,例如mingetty程式,以打開終端、設置模式。然後它會顯示一個登錄介面,這個介面就是我們開機完看到的登錄介面,在這個登錄介面中會提示使用輸入用戶名,其將作為參數傳給login程式來驗證使用者身份,看這個使用者是偷渡客還是合法的使用者。

看到這裡,相信各位己經建立初步的Linux啟動流程的概念了。什麼?您已經睡著了,不怪您,因為我也寫到睡著了,等等要偷睡一下才行

值得注意的是,不同版本的Linuxkernel,其細部內容會稍微不同。所以我傾向講訴的是開機的方法和流程,而不是原始碼。對於kernel原始碼有興趣的朋友可自行參考/usr/src/linux-xxx(核心版本)。然而Linuxkernel有大約百分之十左右的assemblycode,若要Tracecode,還需要了解X86assemblerprogramming才行。


 













()

這時大家對於Linux開機流程己經有了初步的認識,對於一般使用者其實己經足夠。因此,若有興趣者才有再看下去的必要,因為接下來會對前文的內容做進一步的探討,會更加枯燥乏味。但本文也不傾向對每個細節都一一交待清楚,只對重要的部份做進一步的說明。

(
)Bootsect.S
前文已說明這個程式就是linuxPcbootloader。它的主要任務就是決定載入後續哪些程式;後續的這些程式包括要做初式化以及載入核心的Assemblycodes



上圖為這幾個重要的assemblycode的記憶 體配置圖。左邊為LinuxKernel的映象檔,位於檔案最上方為bootsect.S,大小為512B。其結尾為0xAA55的標籤。因此BIOS便 可據此找到MBR內的bootloader內容,然後把它載入如右圖所示的0x00007C00。把執行權交給 bootloader後,它會把自己載 入到0x00090000。接著呢,它會把下一個要執行的程式:setup.S載到0x00090200處,換句話說 ,它setup.S載到bootloader的接續空間(bootloader=512B = 0x200)也就是0x00090000+0x200處。而setup.S則佔據4sector的大小。

接著設定Real-modestack 0x00098000處,還有就是把壓縮過的kernel載到0x00100000的位置。載入的動作都完成後,接著就跳轉到setup.S的位置去執行它,以便執行一些初始化的動作。



()


                                                               
 

()setup.S


如前所述,setup()函數被Linker放在核心映射檔中的0x200偏移處。它的主要任務為初始化電腦中的硬體設備並為核心程式的執行建立環境。首先,它通過BIOS中斷獲取記憶體容量資訊,設置鍵盤的回應速度,設置顯示器的基本模式,獲取硬碟資訊,檢測是否有PS/2滑鼠等等操作,都是在386RealMode下進行。而後才準備讓CPU進入保護模式了。



這時別忘了先把中斷信號masked掉,否則,系統可能受到中斷信號的干擾而陷入不可知狀態。然後呼叫作業系統指令lidtlgdtIDT是中斷向量表,而GDT是全局描述符表。此時的中斷描述符表放置的就是開機時由BIOS設定的那張表。



完成一系列對硬體 設 備的初始化動作以後 ,即跳轉到startup_32()函式:jmpi 0x100000, __BOOT_CS,進入核心Head.S



()

(
)arch/i386/boot/compressed/head.S

為什麼要叫head.S?我想,大概是因為它是屬於Kernel最前面部份的程式,也就是說它是進入核心時要執行的第一個程式。還記得前面前經提過嗎,Linux核心是經過壓縮過的壓縮檔,在執 行以前會先做解壓縮,然後再置於記憶體中。於是head.S也有兩套,一個是在解壓縮以前的kernel中的head.S,另一個則是解壓縮後的kernel裡的head.S

由此可見,自然是先執行解壓縮前的head.S。也因此我們會看到兩個不同的startup_32()函式,一個在arch/i386/boot/compressed/head.S檔中,在setup結束以後,該函數被放在0x00001000或者0x00100000位置,其主要的任務描述如下:



  • segment register和臨時的stack做初始化。由於剛剛進入保護模式,六種Segment   Register,只有CS有初始化(讀者可參考setup.S程式),所以還須將DSESFSGSSS也初始化,程式才能正常運作。






  • 清除eflags register的所有bits






  • _edata_end區間的所有核心未初始化區填0 (有關Linker定義,讀者可下指令:ld        –verbose 做參考)






  • 呼叫decompress_kernel()函式解壓Linux Kernel Image。也就是說 ,呼叫misc.c檔中decompress_kernel函式將Linux Kernel Image解壓縮。所以開機時,我們會看到類 似"Uncompressing Linux...",待解壓完成後將顯示 "OK, booting the kernel."。核心解壓後,如果在低地址載入,則放在0x00100000位置;否則解壓後的映射先放在壓縮映射後的臨時暫存器裏,最後解壓後的映射被放置到實體位置0x00100000處;



  • 跳轉到0x00100000實體記憶體處執行;

()

(
)arch/i386/kernel/head.S
解壓後要執行的是位於arch/i386/kernel/head.S檔中的startup_32()函式,其主要的操作任務如下:



  •         對一系列的register        例如DSESFSGS做初始化






  •         將核心的bss段填0






  •         初始化一些分頁表






  •         處理一系列register,中斷向量表等






  •          將從BIOS獲取的系統參數傳遞給Linux kernel






  •         檢查CPU類型






  •         跳轉到start_kernel函式,這個函式是執行kernel的第一個C函式




本階段其實是相當複雜的(大約500行的assemblycode),在此只節錄部份的操作任務,請有興趣了解其細部運作的朋友自行閱讀其原始碼。

 
(十一)
(
)/usr/src/linux/init/main.c
這時已經進入系統 核心的C語言部份,別以為可以就此輕鬆,事實上這裡還是相當地複雜,同樣地,我也不打算全介紹,請有興趣了解其細部運作的朋友自行閱讀其原始碼。

從上一個階段以後,386處理器可說是完全進入了全面執行作業系統的狀態。接下來就要執行軟體部份,也就是作業系統相關的初始化了。首先執行start_kernel(),它主要用於對處理器、記憶體等最基本的硬體相關部分做初始化,例如處理器的類型,記憶體所佔用的空間等等。

再來也“可能”呼叫到paging_init()(不同版本kernel可能稍微不同),把線性位址中尚未mappingphysicaladdress上的部分,用page機制來做mapping以完成page的初始化。然後就是對中斷向量表的初始化,trap_init();完成中斷向量表以後很自然地就是初始化中斷向量的函式們,呼叫init_IRQ()來完成。

然後就進行排程的初始化,sched_init()執行這個任務。time_init()對系統時鐘做初始化。parse_options()則解析開機時取得的一些參數。再來執行的console_init()還不是對tty做初始化,而kmalloc_init()檢查可用記憶體的大小。

接下來是跟檔案系統(filesystem)相關的初始化工作。例如inode_init()VFS的索引節點管理機制進行初始化,name_cache_init()則對VFS的目錄暫存機制進行初始化;Buffer_init()初始化bufferfree list的指標。

看到這裡,要不睡著也是很難的。好啦,接下來介紹的應該足以讓各位的精神為之掁奮。因為在start_kernel()的最後部份就是建立init行程。這個行程是Linux的第一個行程,也是其他所有行程的父行程。在建立起init行程之前,首先試著去找“/sbin/init”,如果找不到,會去找“/etc/init”,再找不到就往“/bin/init”找。注意,這部份也是因kernel版本的不同而異。在執行etc/init時,會根據一些設定好的scriptfiles內容去呼叫一些重要的行程以完成一些必要的操作,例如檔案系統的檢查,啟動系統的守護行程,對終端機建立getty行程,最後再執行“/etc/rc”下的scriptfile

這個getty會等待使用者的登錄。使用者透過login登入系統,getty便呼叫exec執行login程式。Login程式會檢查user的帳號和密碼,如果密碼正確,便呼叫exec執行shellX-windows。然而執行user預設的系統環境檔,也就是home/username/.profile檔。

user登出之後,init行程要再重新啟動一個getty行程,讓其他user登錄。

 
(十二)

較早之前我們曾提到etc/inittab/etc/rd.c/rc.sysinit。這些都是啟動Linux系統時會執行的scriptfiles。以下便針對這些部份做進一步的探討。

不知各位是否還記得在之前介紹如何製作LinuxLive USB時有提到過etc/inittab的內容:
::sysinit:/etc/rc.d/rc.sysinit
::askfirst:/bin/sh

但其實之前在製作LinuxLiveUSB,由於該系統較單純,所以inittab也相對簡單得多。它主要設定runlevel為何。系統會根據不同的開機級別(runlevel)來分配資源。主要分成7種級別(或許會改變)。由於這部份的說明相當豐富,所以建議各位上網搜索相關的文件。以下節錄來自http://163.23.79.65/html/techdoc/startup.htm 的內容,方便不便上網搜索相關的文件的朋友做參考:
RedHat
run-level有以下7 層定義在/etc/inittab)



  • 0        --- halt : 關機        level






  • 1        --- Single user mode : 單人模式,        如果你忘記        root 密碼,        這是補救的方式之一.        






  • 2        --- Multiuser, without NFS : 多人使用模式,        但沒有 NFS        功能,        如果安裝時沒有使用網路功能,        那麼 level        3 是一樣的.        






  • 3        --- Full multiuser mode : 這是預定的        run-level






  • 4        --- unused : 這個        run-level        目前尚未定義使用        






  • 5        --- X11 : X Windows 使用的        level



  • 6 --- reboot :        重新開機時使用的        level
Linux 系統開機時,最重要的觀念便是這個run-level ,run-level 可以說是Linux的系統狀態(systemstates of Linux), 根據不同的情況進入不同的系統狀態,以執行不同的初始化動作.
另外一點,Linux 雖然融合了System V BSD 的特色,但在開機起動的部份,比較接近System V 的作法,並且這種方式幾乎已成為Linux 世界的一項標準.因為它具有容易使用,功能強大以及富有彈性的特色.
其目錄檔案結構如下:
/etc/rc.d
中包含:                       
                            
目錄
                      
                            
script 檔案
                      
                            
  • /etc/rc.d/init.d
  • /etc/rc.d/rc0.d
  • /etc/rc.d/rc1.d
  • /etc/rc.d/rc2.d
  • /etc/rc.d/rc3.d
  • /etc/rc.d/rc4.d
  • /etc/rc.d/rc5.d



  • /etc/rc.d/rc6.d



                      
                            
  • rc
  • rc.local



  • rc.sysinit



                      
rc0.d 便是run-level 0 起動script 存放的目錄,rc3.d run-level 3,其它依此類推
不過,rc0.d ~ rc6.d 中的script並不是各自獨立的,其實它們都是symbolic file,連結到/etc/rc.d/init.d中的 script.



(十三)

Example:
#Runxdm in runlevel 5
x:5:respawn:/usr/bin/X11/xdm-nodaemon


接下來探討inittab的格式及格式的意義為何:

格式:
id:runleveld:action:process

意義:
id

代表由幾個字元所組成的識別字。

Runlevels

說明action以及process會在哪些runlevel中被執行,合法值為012...6s以及S

Action

呼叫行程時,對行程所採取的應答方式,計有:
initdefault
:指出系統在啟動時預設的runlevel。例如系統在啟動時,進入runlevel3的模式。如果把3改為5,那將會執行/etc/rc.d/rc.5,也就是X-Window
sysinit
:在系統啟動時,一定要執行的行程。而所有的inittab的行中,如果它的action中有bootbootwait,則該行必須等到這些actionsysinit的行程執行完之後才能夠執行。
wait
:在啟動一個行程之後,若要再啟動另一個行程,則必須等到這個行程結束之後才能繼續。
respawn:
代表這個process即使在結束之後,也可能會重新被啟動,如剛才介紹的getty

inittab一般會看到:
#Systeminitialization.
si::sysinit:/etc/rc.d/rc.sysinit
它是系統無論進入到哪個級別,都必須執行/etc/rc.d/rd.sysinit
它主要的任務包括:檢查檔案系統,設置硬體設備,檢查並載入模組。完成後會回到inittab,根據inittab所設定的runlevel去執行/etc/rc.d目錄下的對應的rc檔。也就是說當runlevel=3,對應的rc檔即為rc.3。這會根據不同的啟動程式去初始化各個runlevel的系統環境,包括啟動系統的守護行程。最後init行程將執行getty行程,等待user登錄,整個Linux的開機流程就到此結束。







0 意見:

張貼留言

 
Copyright 2009 Linux學習誌
BloggerTheme by BloggerThemes | Design by 9thsphere