Linux啓動分析

一、系統引導過程總體介紹
啓動流程圖:
        
  系統引導過程主要由以下幾個步驟組成(以硬盤啓動爲例)
 1、開機;
  2、 BIOS加電自檢(POST——Power On Self Test),包括檢查RAM,keyboard,顯示器,軟硬磁盤等等。Intel系列的CPU首先進入的是實模式,並開始執行位於地址0xFFFF0處的代碼,也就是ROM-BIOS起始位置的代碼;
3、搜索啓動的操作系統,根據BIOS設置,可能會依次訪問每個軟盤的第一個扇區、硬盤、CD-ROW等;一旦找到有效的啓動設備,將第一個扇區(0頭0道1扇區,也就是Boot Sector)的內容讀入內存地址0x7c00處;
  4、檢查(WORD)0000:7dfe是否等於0xaa55.若不等於則轉去嘗試其他介質;如果沒有其他啓動介質,則顯示 “No ROM BASIC” ,然後死機;
  5、跳轉到0000:7c00處執行MBR中的程序bootsect.S;
6、 MBR先將自己複製到0x90000處,然後將緊接其後的setup部分(第二扇區)拷貝到0x90200,將真正的內核代碼拷貝到0x100000。以上這些拷貝動作都是以bootsect.S、setup.S以及vmlinux在磁盤上連續存放爲前提的;
7、bootsect.S完成加載動作後,就直接跳轉到0x90200,這裏正是setup.S的程序入口。 setup.S的主要功能就是將系統參數(包括內存、磁盤等,由BIOS返回)拷貝到 0x90000-0x901FF內存中,這個地方正是bootsect.S存放的地方,這時它將被系統參數覆蓋。以後這些參數將由保護模式下的代碼來讀取。
  8、 setup.S還將video.S中的代碼包含進來,檢測和設置顯示器和顯示模式。最後,setup.S將系統轉換到保護模式,並跳轉到0x100000(對於bzImage格式的大內核是 0x100000,對於zImage格式的是0x1000)的內核引導代碼,Bootloader過程結束;
  9、Bootloader跳轉到0x100000, 此處爲"arch/I386/init/head.S"中的startup_32, startup_32的代碼只需要設置一下全局變量,然後就跳轉到start_kernel去了;start_kernel()是"init/main.c"中的asmlinkage函數,至此,啓動過程轉入體系結構無關的通用C代碼中;
  10、start_kernel()中設置與體系結構相關的環境、頁表結構初始化、Trap/IRQ初始化、核心進程調度器初始化、時間/定時器初始化、控制檯初始化、核心Cache初始化、內存初始化、內部及通用等各種Cache初始化、信號量初始化、其他部分初始化(Init()及smp_init());
  11、啓動Init()過程,創建第一個進程;Init()中,取得 run-level 信息, 執行 /etc/rc.d/rc.sysinit 腳本, 激活核心的外掛式模塊 (/etc/modules.conf), 然後init 執行 run-level 的各個腳本, 接着執行 /etc/rc.d/rc.local腳本, 最後執行 /bin/login 程序, 登入之後開始以 Shell 控管主機;
  12、啓動完成。 

 

 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程序。 

本文來自CSDN博客,轉載請標明出處:http://blog.csdn.net/cxylaf/archive/2007/05/26/1626513.aspx

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章