現在的位置: 首頁 > 綜合 > 正文

Linux啟動分析(2)— bootsect.S、setup.S、head.S分析

2013年11月26日 ⁄ 綜合 ⁄ 共 4411字 ⁄ 字型大小 評論關閉
bootsect.S,系統引導程序,一般不超過512位元組。
PC系統結構中,線性地址0xA0000以上,即640K以上用於圖形介面卡和BIOS自身,640K以下為系統的基本內存。如果配置更多的內存,則0x100000,即1MB處開始稱為高內存。當BIOS引導一個系統時,總是把引導扇區讀入到基本內存地址為0x7c00的地方,然後跳轉到此執行引導扇區的代碼。這段代碼將自身搬運到0x90000處,並跳轉到那繼續執行,然後通過BIOS提供的讀磁碟調用「int
0x13」從磁碟上讀入setup
和內核映像。其中setup的映像讀入到0x90200處,然後跳轉到setup的代碼中。
0x900000xA0000一共64Kbootsect僅占512位元組,所以setup大小理論上可到63.5KB
Linux2.4版本以前,在最前面的512位元組里保護了一個mini 「boot loader」,只要拷貝啟動代碼運行就可從軟盤啟動;但在2.6版本中不再保護這樣的」boot
loader」
,所以必須在第一個磁碟分區上存儲一個合適的boot loader才能從軟盤啟動,軟盤、硬碟和光碟機啟動都是一樣的過程。
setup進行映像的解壓縮,從BIOS收集一些數據,在控制台顯示一些信息。
基本內存中開頭一部分空間是保留給BIOS自己用的,另一方面對於Linux內核的引導也需要保留一些運行空間,一共保存了64K。基本內存中用於內核映像的就是8*64K=512K,其中頂端留4K用於引導命令行及從BIOS獲取需要傳遞給內核的數據。內核映像一般都經過壓縮,壓縮後的映像和引導扇區及輔助引導程序的映像拼接在一起,成為內核的引導映像。大小不超過508K的映像稱為小映像zImage,早期版本放在0x10000位置處,否則稱為大內核bzImage,放在0x100000位置處。
CPUbootsect時處於16位實地址模式,然後在setup的執行過程中轉入32位保護模式。
SetupBIOS中讀取系統數據(內存大小、顯卡模式、磁碟等參數),將數據保存在0x90000-0x901FF,覆蓋了bootsect的內容。設置32位運行方式:載入中斷描述表寄存器IDTR、全局描述表寄存器GDTR;臨時設置IDT表和GDT表,並在GDT表中設置內核代碼段和數據段的描述符,在Head.S中會根據內核的需要重新設置這些描述符表;開啟A20地址線;重新設置兩個中斷控制器8259A,將硬體中斷號重新設置為0x200x2f;最後設置CPU的控制寄存器CR0(機器狀態字)的保護模式比特(PE)位,從而進入32位保護模式運行;然後跳轉到head.S中的startup_32執行。
對於小內核映像放在0x10000處,Setup會把system0x10000移到0x0000開始處。對於大內核映像,vmlinux中普通內核代碼被編譯成以PAGE_OFFSET+1MB為起始地址,在Head.S中初始化代碼把虛擬地址減去PAGE_OFFSET就能得到以1MB為起始位置的物理地址,這也正是內核映像在物理內存中的存放位置。
Head.S中的startup_32主要用於開啟頁面單元。初始化工作在編譯過程中開始進行,它先定義一個稱為swapper_pg_dir的數組,使用鏈接器指示在地址0x00101000。然後分別為兩個頁面pg0pg1創建頁表項。第一組指向pg0pg1的指針放在能覆蓋19MB內存的位置,第二組指針放在PAGE_OFFSET+1MB的位置。一旦開始頁機制,在上述頁表和頁表項指針建立後可以保證,在內核映像中不論是採用物理地址還是虛擬地址,都可以進行正確的頁面映射。內核其他部分的頁表初始化在paging_init()中完成。映射建立後,通過設置cr0寄存器中的某位開啟頁面映射,然後通過一個跳轉指令保證指令指針的正確性。
 
1Bootsect啟動過程:
假設用LILO啟動,啟動時用戶可以選擇啟動哪個操作系統。LILOboot
loader
分為兩部分,一部分放到啟動分區的第一個扇區;
1)        BIOSMBR或啟動分區的第一個扇區的啟動部分載入到地址0x00007c00處;
2)        該程序將自身移到0x00096a00,建立實模式棧(0x000980000x000969ff),將LILO的第二部分載入到0x00096c00處,然後跳轉到此執行;
3)        然後第二部分程序從磁碟讀取一個可啟動的操作系統列表讓用戶選擇,最後用戶選擇每個OS後,boot
loader
可以拷貝不啟動分區或者之間拷貝內核映像到RAM中去;
4)        載入Linux內核映像時,LILO
boot loader
首先調用BIOS常式顯示」Loading …」信息;
5)        調用BIOS常式載入內核映像的初始化部分到RAM上,內核映像的前512位元組放在0x00090000位置,setup()函數代碼放在0x00090200位置;
6)        接著調用BIOS常式裝載內核映像的其餘部分,映像可能放在低地址0x00010000(使用make
zImage
編譯的小內核映像)或者高地址0x00100000(使用make
bzImage
編譯的大內核映像)。
7)        然後跳至剛剛setup部分。
 
2Setup.S分析
setup()彙編函數被連接器放在內核映像文件中的0x200偏移處。Setup函數必須初始化計算機中的硬體設備並為內核程序的執行建立環境。
1)        ACPI兼容的系統中,調用BIOS常式建立描述系統物理內存布局的表。在早期系統中,它調用BIOS常式返回系統可以的RAM大小;
2)        設置鍵盤的重複延遲和速率;
3)        初始化顯卡;
4)        檢測IBM MCA匯流排、PS/2滑鼠設備、APM
BIOS
支持等;
5)        如果BIOS支持Enhanced
Disk Drive Services (EDD)
,將調用正確的BIOS常式建立描述系統可用硬碟的表;
6)        如果內核載入在低RAM地址0x00010000,則把它移動到0x00001000處;如果映像載入在高內存1M位置,則不動;
7)        啟動位於8042鍵盤控制器的A20
pin
8)        建立一個中斷描述表IDT和全局描述表GDT表;
9)        如果有的話,重啟FPU單元;
10)    對可編程中斷控制器進行重新編程,屏蔽所以中斷,級連PICIRQ2不需要;
11)    設置CR0狀態寄存器的PE位使CPU從實模式切換到保護模式,PG位清0,禁止分頁功能;
12)    跳轉到startup_32()彙編函數jmpi
0x100000, __BOOT_CS
,終於進入內核Head.S
 
3Head.S分析
有兩個不同的startup_32()函數,一個在arch/i386/boot/compressed/head.S文件中,setup結束後,該函數被放在0x00001000或者0x00100000位置,該函數主要操作:
1)        首先初始化段寄存器和臨時堆棧;
2)        清除eflags寄存器的所有位;
3)        _edata_end區間的所有內核未初始化區填充0
4)        調用decompress_kernel( )函數解壓內核映像。首先顯示"Uncompressing
Linux..."
信息,解壓完成後顯示 "OK, booting the kernel."。內核解壓後,如果時低地址載入,則放在0x00100000位置;否則解壓後的映像先放在壓縮映像後的臨時緩存里,最後解壓後的映像被放置到物理位置0x00100000處;
5)        跳轉到0x00100000物理內存處執行;
   
    解壓後的映像開始於arch/i386/kernel/head.S 文件中的startup_32()函數,因為通過物理地址的跳轉執行該函數的,所以相同的函數名並沒有什麼問題。該函數未Linux第一個進程建立執行環境,操作如下:
1)         初始化ds,es,fs,gs段寄存器的最終值;
2)        0填充內核bss段;
3)        初始化swapper_pg_dir數組和pg0包含的臨時內核頁表:
l          swapper_pg_dir0x1000)pg0(0x2000)清空,swapper_pg_dir作為整個系統的頁目錄;
l          pg0作為第一個頁表,將其地址賦到swapper_pg_dir的第一個32位字中。
l          同時將該頁表項也賦給swapper_pg_dir的第3072個入口,表示虛擬地址0xc0000000也指向pg0
l          pg0這個頁表填滿指向內存前4M
l          cr3寄存器中存放PGD的地址,並設置cr0寄存器中的PG位,啟用分頁支持。
4)        建立進程0idle進程的內核模式的堆棧;
5)        再次清除eflags寄存器的所有位;
6)        調用setup_idt()用非空的中斷處理函數填充IDT表;
7)        將從BIOS獲取的系統參數傳遞到操作系統的第一個頁面幀;
8)        識別處理器的模式;
9)        GDTIDT表的地址載入到gdtridtr寄存器中;
10) 跳轉到start_kernel函數,這個函數是第一個C編製的函數,內核又有了一個新的開始。
 
4start_kernel()分析:
1)        調度器初始化,調用sched_init();
2)        調用build_all_zonelists函數初始化內存區;
3)        調用page_alloc_init()mem_init()初始化夥伴系統分配器;
4)        調用trap_init()init_IRQ()對中斷控制表IDT進行最後的初始化;
5)        調用softirq_init() 初始化TASKLET_SOFTIRQHI_SOFTIRQ
6)        Time_init()對系統日期和時間進行初始化;
7)        調用kmem_cache_init()初始化slab分配器;
8)        調用calibrate_delay()計算CPU時鐘頻率;

通過調用kernel_thread()啟動進程1init進程的內核線程,然後該線程再創建其他的內核線程執行/sbin/init程序。 

抱歉!評論已關閉.