歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux綜合 >> Linux資訊 >> 更多Linux >> 啟動過程跟蹤

啟動過程跟蹤

日期:2017/2/27 14:31:07   编辑:更多Linux
  本文以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作為用戶的命令解釋器。



如果用戶名不是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作為用戶的命令解釋器。



八.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作為用戶的命令解釋器。



Copyright © Linux教程網 All Rights Reserved