很好的linux启动说明( bootsect.S、setup.S、head.S)
生活随笔
收集整理的這篇文章主要介紹了
很好的linux启动说明( bootsect.S、setup.S、head.S)
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
bootsect.S,系統引導程序,一般不超過512字節。
在PC系統結構中,線性地址0xA0000以上,即640K以上用于圖形接口卡和BIOS自身,640K以下為系統的基本內存。如果配置更多的內存,則0x100000,即1MB處開始稱為高內存。當BIOS引導一個系統時,總是把引導扇區讀入到基本內存地址為0x7c00的地方,然后跳轉到此執行引導扇區的代碼。這段代碼將自身搬運到0x90000處,并跳轉到那繼續執行,然后通過BIOS提供的讀磁盤調用“int 0x13”從磁盤上讀入setup和內核映像。其中setup的映像讀入到0x90200處,然后跳轉到setup的代碼中。
從0x90000到0xA0000一共64K,bootsect僅占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位置處。
CPU在bootsect時處于16位實地址模式,然后在setup的執行過程中轉入32位保護模式。
Setup從BIOS中讀取系統數據(內存大小、顯卡模式、磁盤等參數),將數據保存在0x90000-0x901FF,覆蓋了bootsect的內容。設置32位運行方式:加載中斷描述表寄存器IDTR、全局描述表寄存器GDTR;臨時設置IDT表和GDT表,并在GDT表中設置內核代碼段和數據段的描述符,在Head.S中會根據內核的需要重新設置這些描述符表;開啟A20地址線;重新設置兩個中斷控制器8259A,將硬件中斷號重新設置為0x20和0x2f;最后設置CPU的控制寄存器CR0(機器狀態字)的保護模式比特(PE)位,從而進入32位保護模式運行;然后跳轉到head.S中的startup_32執行。
對于小內核映像放在0x10000處,Setup會把system從0x10000移到0x0000開始處。對于大內核映像,vmlinux中普通內核代碼被編譯成以PAGE_OFFSET+1MB為起始地址,在Head.S中初始化代碼把虛擬地址減去PAGE_OFFSET就能得到以1MB為起始位置的物理地址,這也正是內核映像在物理內存中的存放位置。
Head.S中的startup_32主要用于開啟頁面單元。初始化工作在編譯過程中開始進行,它先定義一個稱為swapper_pg_dir的數組,使用鏈接器指示在地址0x00101000。然后分別為兩個頁面pg0和pg1創建頁表項。第一組指向pg0和pg1的指針放在能覆蓋1~9MB內存的位置,第二組指針放在PAGE_OFFSET+1MB的位置。一旦開始頁機制,在上述頁表和頁表項指針建立后可以保證,在內核映像中不論是采用物理地址還是虛擬地址,都可以進行正確的頁面映射。內核其他部分的頁表初始化在paging_init()中完成。映射建立后,通過設置cr0寄存器中的某位開啟頁面映射,然后通過一個跳轉指令保證指令指針的正確性。
1.Bootsect啟動過程:
假設用LILO啟動,啟動時用戶可以選擇啟動哪個操作系統。LILO將boot loader分為兩部分,一部分放到啟動分區的第一個扇區;
1)????????BIOS將MBR或啟動分區的第一個扇區的啟動部分加載到地址0x00007c00處;
2)????????該程序將自身移到0x00096a00,建立實模式棧(從0x00098000到0x000969ff),將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部分。
2.Setup.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)????對可編程中斷控制器進行重新編程,屏蔽所以中斷,級連PIC的IRQ2不需要;
11)????設置CR0狀態寄存器的PE位使CPU從實模式切換到保護模式,PG位清0,禁止分頁功能;
12)????跳轉到startup_32()匯編函數,?jmpi 0x100000, __BOOT_CS,終于進入內核Head.S;
3.Head.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_dir(0x1000)和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)????????將GDT和IDT表的地址加載到gdtr和idtr寄存器中;
10)?跳轉到start_kernel函數,這個函數是第一個C編制的函數,內核又有了一個新的開始。
4.start_kernel()分析:
1)????????調度器初始化,調用sched_init();
2)????????調用build_all_zonelists函數初始化內存區;
3)????????調用page_alloc_init()和mem_init()初始化伙伴系統分配器;
4)????????調用trap_init()和init_IRQ()對中斷控制表IDT進行最后的初始化;
5)????????調用softirq_init()?初始化TASKLET_SOFTIRQ和HI_SOFTIRQ;
6)????????Time_init()對系統日期和時間進行初始化;
7)????????調用kmem_cache_init()初始化slab分配器;
8)????????調用calibrate_delay()計算CPU時鐘頻率;
通過調用
kernel_thread()
啟動進程
1init
進程的內核線程,然后該線程再創建其他的內核線程執行
/sbin/init
程序。
總結
以上是生活随笔為你收集整理的很好的linux启动说明( bootsect.S、setup.S、head.S)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Linxu 进程描述符task_stru
- 下一篇: A20 地址线问题