生活随笔
收集整理的這篇文章主要介紹了
Linux内核引导简析
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
bootsect.S、setup.S、head.S分析 收藏 2010-01-14 13:36:34 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程序。
感謝
2010-01-14 13:41:32:?FutureChen?(Life With Youtube And Twitter) 從以上幾講我們知道,Linux簡化了分段機制,使得虛擬地址與線性地址總是一致,因此,Linux的虛擬地址空間也為0~4G。Linux內核將這4G字節的空間分為兩部分。將最高的1G字節(從虛擬地址0xC0000000到0xFFFFFFFF),供內核使用,稱為“內核空間”。而將較低的 3G字節(從虛擬地址0x00000000到0xBFFFFFFF),供各個進程使用,稱為“用戶空間)。因為每個進程可以通過系統調用進入內核,因此,Linux內核由系統內的所有進程共享。于是,從具體進程的角度來看,每個進程可以擁有4G字節的虛擬空間。? Linux使用兩級保護機制:0級供內核使用,3級供用戶程序使用。從圖中可以看出(這里無法表示圖),每個進程有各自的私有用戶空間(0~3G),這個空間對系統中的其他進程是不可見的。最高的1GB字節虛擬內核空間則為所有進程以及內核所共享。? 1.虛擬內核空間到物理空間的映射? 內核空間中存放的是內核代碼和數據,而進程的用戶空間中存放的是用戶程序的代碼和數據。不管是內核空間還是用戶空間,它們都處于虛擬空間中。讀者會問,系統啟動時,內核的代碼和數據不是被裝入到物理內存嗎?它們為什么也處于虛擬內存中呢?這和編譯程序有關,后面我們通過具體討論就會明白這一點。? 雖然內核空間占據了每個虛擬空間中的最高1GB字節,但映射到物理內存卻總是從最低地址(0x00000000)開始。對內核空間來說,其地址映射是很簡單的線性映射,0xC0000000就是物理地址與線性地址之間的位移量,在Linux代碼中就叫做PAGE_OFFSET。? ? ? 我們來看一下在include/asm/i386/page.h中對內核空間中地址映射的說明及定義:? /*? * This handles the memory map.. We could make this a config? * option, but too many people screw it up, and too few need? * it.? *? * A __PAGE_OFFSET of 0xC0000000 means that the kernel has? * a virtual address space of one gigabyte, which limits the? * amount of physical memory you can use to about 950MB.? *? * If you want more physical memory than this then see the CONFIG_HIGHMEM4G? * and CONFIG_HIGHMEM64G options in the kernel configuration.? */? ? #define __PAGE_OFFSET (0xC0000000)? ……? #define PAGE_OFFSET ((unsigned long)__PAGE_OFFSET)? #define __pa(x) ((unsigned long)(x)-PAGE_OFFSET)? #define __va(x) ((void *)((unsigned long)(x)+PAGE_OFFSET))? 源代碼的注釋中說明,如果你的物理內存大于950MB,那么在編譯內核時就需要加CONFIG_HIGHMEM4G和CONFIG_HIGHMEM64G 選項,這種情況我們暫不考慮。如果物理內存小于950MB,則對于內核空間而言,給定一個虛地址x,其物理地址為“x- PAGE_OFFSET”,給定一個物理地址x,其虛地址為“x+ PAGE_OFFSET”。? 這里再次說明,宏__pa()僅僅把一個內核空間的虛地址映射到物理地址,而決不適用于用戶空間,用戶空間的地址映射要復雜得多。? 2.內核映像? 在下面的描述中,我們把內核的代碼和數據就叫內核映像(kernel image)。當系統啟動時,Linux內核映像被安裝在物理地址0x00100000開始的地方,即1MB開始的區間(第1M留作它用)。然而,在正常運行時,整個內核映像應該在虛擬內核空間中,因此,連接程序在連接內核映像時,在所有的符號地址上加一個偏移量PAGE_OFFSET,這樣,內核映像在內核空間的起始地址就為0xC0100000。? 例如,進程的頁目錄PGD(屬于內核數據結構)就處于內核空間中。在進程切換時,要將寄存器CR3設置成指向新進程的頁目錄PGD,而該目錄的起始地址在內核空間中是虛地址,但CR3所需要的是物理地址,這時候就要用__pa()進行地址轉換。在mm_context.h中就有這么一行語句:? asm volatile(“movl %0,%%cr3”: :”r” (__pa(next->pgd));? 這是一行嵌入式匯編代碼,其含義是將下一個進程的頁目錄起始地址next_pgd,通過__pa()轉換成物理地址,存放在某個寄存器中,然后用mov指令將其寫入CR3寄存器中。經過這行語句的處理,CR3就指向新進程next的頁目錄表PGD了。
? Linux內核引導簡析?
?
2009-03-01 16:47? 以前學計算機的時候就很好奇,為什么電源一打開,操作系統就會在最后神奇般的出現?這中間到底發生了些什么事情?我試著用這篇小文來解釋,但水平有限,難免有錯誤和不足。因為引導過程與體系結構有關,這里就只以Intel X86體系結構32位機為例來進行說明。? ? 一,PC機物理編址? ? PC機最早是由IBM生產,使用的是Intel 8088處理器。這個處理器只有20根地址線,可以尋址1M的空間。這1M空間大概有如下的結構:? +------------------+ <- 0x00100000 (1MB)? | BIOS ROM |? +------------------+ <- 0x000F0000 (960KB)? | 16-bit devices, |? | expansion ROMs |? +------------------+ <-0x000C0000 (768KB)? | VGA Display |? +------------------+ <-0x000A0000 (640KB)? | Low Memory |? +------------------+ <- 0x00000000? 其中可以自由使用的空間是最低的640K(0x0000_0000 ~ 0x000F_FFFF),稱為Low Memory。余下的384K有特殊的用途,最突出的是最后的64K,那是BIOS的代碼。? 最后Intel終于打破了1MB的屏障,80286,80386處理器分別支持16MB和4GB內存。然而,為了向后兼容,最初的1M內存空間仍然保留了下來。因此現代的PC機物理內存中存在著0x000A_0000到0x0010_0000的“空洞“,它把RAM分成了兩個部分,一是最低的640K,稱為”傳統內存“,一是”擴展內存“(它的地址空間不固定)。另外,位于32位物理地址空間的最頂端的部分,高于任何物理RAM,被BIOS保留了下來,用于32位PCI設備。目前,物理內存可以超過4G,被保留的32位PCI設備地址空間又會形成新的“空洞”。? ? 二,BIOS? ? 當打開PC機的電源時,處理器處于實模式,CS:IP = 0xF000:0xFFFF。這個邏輯地址的虛擬地址是0xFFFFF0,是將CS寄存器的值左移4個二進制位,再加上IP寄存器的值得到的。這種方法隱含了一個信息,就是在實模式中,也是有分段的,只不過段是固定的,每個段的大小都是64K(2^16),段寄存器中保存的就是段的編號。那么這個初始地址是哪里的指令了?在PC機的物理編址一節提到,BIOS的位于1M的最后64K,也即0x000F0000~0x000FFFFF,所以第一條指令是 BIOS的代碼。BIOS,也即基本輸入輸出系統,它主要分為兩個部分,POST(加電自檢)以及Runtime Routines。實際上在0xFFFFF0這個地址上保存了一條跳轉指令,跳轉到BIOS的POST的第一代指令。POST主要進行一些硬件的檢測操作,這時可以在屏幕上看到很多輸出。當檢測完畢后,BIOS根據CMOS里的設置,查找引導設備,并從主引導分區中讀取第1個扇區,并加載到0x7C00 的位置,BIOS會在最后跳轉到這個地址。POST的代碼會在結束后從內存中移除,而Runtime Routeines的代碼不會。? ? 三,Boot Loader? ? 主引導記錄位于一個扇區里,有512字節,分為三個部分。前446字節是引導代碼部分,隨后64字節是分區表,最后的2個字節是魔數0xAA55。分區表里含有4個表項,每個使用16字節描述,這里不詳細說明。魔數起到一個標志的作用。操作系統是通過稱為Boot Loader的程序加載到內存中,主引導記錄的代碼就與Boot Loader有關。在早期的操作系統中(包括Linux),Boot Loader是做為內核的一部分,和內核同時編譯鏈接的。現在, Boot Loader和操作系統進行了分離,比如Grub就是一個Boot Loader,它即可以引導Linux,也可以引導Windows,而Linux還可以被LILO引導。? 引導操作系統的過程就好像如何把大象從冰箱里拿出來一樣(可憐的大象!),第一步,把冰箱門打開,第二步,把大象拿出來。目前的Boot Loader,比如Grub,也是一個兩階段的過程。第一階段的代碼就是位于MBR記錄里的,它負責加載第二階段的代碼。第二階段加載內核到內存中,并為其準備引導參數。GRUB(Grand Unified Bootloader)實際上是一個2.5階段的Boot Loader,多出的第1.5階段是為了支持多文件系統。為了實現操作系統與Boot Loader的分離,操作系統映像的第一個8K必須含有一個multiboot header,并以0x1BADB002結束。? ? 四,Linux 2.6 內核加載過程? ? GRUB將Linux內核映像的前兩個扇區(init扇區以及setup扇區)加載到物理內存的0x00090000地址處。這兩個扇區的代碼是體系結構相關的,位于arch/x86/boot/header.S中。init扇區最初是用做軟盤MBR的引導代碼的,現在的Linux不支持軟盤引導,所以這個扇區沒有什么意義,只是輸出一些提示信息"Direct booting form floppy is no longer supported. Please use a boot loader program instead.",(用bochs虛擬機去執行內核的壓縮映像bzImage,可以看到這些信息)。setup扇區是一些代碼和引導參數,它被加載到 0x00090200處。代碼部分的主要工作是調用引導階段的main函數,比較重要的引導參數是進入保護模式后的32位代碼的入口點。參數說明當內核是大內核時,內核映像會被加載到0x0010000的位置,否則,就被加載到0x1000處。? code32_start:? #ifndef __BIG_KERNEL__? .long 0x1000 # 0x1000 = default for zImage? #else? .long 0x100000 # 0x100000 = default for big kernel? 引導階段的main函數位于arch/x86/boot/main.c中,它首先會復制引導參數,然后初始堆,檢測物理內存布局,最重要是進入保護模式,跳轉到32位代碼的入口點。進入保護模式是通過位于arch/x86/boot/pm.c中的go_to_protected_mode()函數來實現的,它會開啟A20地址線,設置boot階段的IDT,GDT,(內核代碼段0x10,數據段0x18),最后,執行 protected_mode_jump(boot_params.hdr.code32_start, (u32)&boot_params + (ds() << 4)),跳轉到引導參數定義的入口點,如果是big kernel,則是0x00100000(1M)。? 位于0x00100000之后的代碼也可分為兩部分,一是解壓內核的代碼,一是被壓縮過的32位代碼。解壓縮的代碼位于arch/x86/boot /compressed/head_32.S,值得注意的是,解壓的最終位置會在計算后,保存在ebp寄存器中,實際上還是0x00100000。解壓后,位于1M位置的就是位于arch/x86/kernel/head_32.S中的入口點了,這也是真實意義的內核入口點。這段代碼會設置頁目錄,頁表,內核的虛地址空間被設成0xC0000000~0xFFFFFFFF,也就是最后1G,并使用宏__PAGE_OFFSET表示起始地址 0xC00000000。經過一系列基本的與硬件有關的初始化工作后,執行流跳轉到(*initial_code),也就是 i386_start_kernel函數。i386_start_kernel()位于arch/x86/kernel/head32.c中,如果需要,它首先初始化與ramdisk相關的數據。ramdisk會在引導過程中做為臨時的根文件系統,它包含一些可執行程序,腳本,可以用來加載內核模塊等。最后調用start_kernel()。? ? 五,start_kernel()函數? ? start_kernel()函數位于init/main.c中,是引導過程中最重要的一個函數,就像它的名字一樣,它初始化了內核所有的功能。? 1,調用lock_kernel(),防止內核被意外搶斷,定義在lib/kernel_lock.c中。在SMP或者搶斷式調度環境中,內核可以被搶斷。內核初始化時,功能還不完善,為防止此種情況發生,使用稱為Big Kernel Lock的spinlock。spinlock是一種忙等待鎖,如果等待周期不是很長,它比信號有效,因為信號會造成進程調度。Big Kernel Lock只在內核初始化時使用,當初始化結束后,該鎖被釋放。? 2,page_address_init()函數初始化頁管理,創建了頁管理所需的數據結構,定義在mm/highmem.c中。? 3,輸出內核版本信息,執行了兩個內核輸出語句printk(KERN_NOTICE)和printk(linux_banner)。因為此時還沒有初始化控制臺,所以這些信息不能輸出到屏幕上或者輸出到串口上,而是輸出到一個buffer中。printk()函數定義在kernel/printk.c 中,KERN_NOTICE宏定義在include/linux/kernel.h中,值為"<5>"。linux_banner定義在 init/version.c中,在我的實驗環境中是這樣的一個字符串:Linux version 2.6.28 (zctan@dbgkrnl) (gcc version 4.3.0 20080428 (Red Hat 4.3.0-8) (GCC) ) #1 SMP Sun Feb 8 20:56:17 CST 2009。? 4,setup_arch(),位于arch/x86/kernel/setup.c,初始化了許多體系結構相關的子系統。? 5,setup_per_cpu_area(),定義在arch/x86/kernel/setup_percpu.c中,如果是SMP環境,則為每個CPU創建數據結構,分配初始工作內存。? 6,smp_prepare_boot_cpu(),定義在include/asm-x86/smp.h。如果是SMP環境,則設置boot CPU的一些數據。在引導過程中使用的CPU稱為boot CPU。? 7,sched_init(),定義在kernel/sched.c。初始化每個CPU的運行隊列和超時隊列。Linux使用多優先級隊列的調度方法,就緒進程位于運行隊列中。? 8,build_all_zonelists(),定義在mm/page_alloc.c中,建立內存區域鏈表。Linux將所有物理內存分為三個區,ZONE_DMA, ZONE_NORMAM, ZONE_HIGHMEM。? 9,trap_init(),定義在arch/x86/kernel/traps_32.c中,初始化IDT, 如除0錯,缺頁中斷等。? 10,rcu_init(),定義在kernel/rcupdate.c中,初始化Read-Copy-Update子系統。當使用spinlock會造成效率低下時,RCU被用來實現臨界區的互斥。? 11,init_IRQ(),定義在arch/x86/kernel/paravirt.c中,初始化中斷控制器。? 12,pidhash_init(),定義在kernel/pid.c中,Linux的進程描述符稱為PID, 使用名稱空間以及hash表來管理。? 13,init_timers(),定義在kernel/timer.c中,初始化定時器。? 14,softirq_init(),定義在kernel/softirq.c中,初始化中斷子系統,如softirq, tasklet。? 15,time_init(),定義在arch/x86/kernel/time_32.c中,初始化系統時間。? 16,profile_init(),定義在kernel/profile.c中,為profiling data分配存儲空間。Profiling data這個術語描述在程序運行過程中采集到的一些數據,用于性能的分析。? 17,local_irq_enable(),定義在include/linux/irqflags.h中,開啟引導CPU的中斷。? 18,console_init(),定義在drivers/char/tty_io.c中,初始化控制臺,可以是顯示器也可以是串口。此時屏幕上才會有輸出,前面printk輸出到buffer中的內容會在這里全部輸出。? 19,initrd檢測。如果定義了Init Ram Disk,則檢測其是否有效。? 20,mem_init(),定義在arch/x86/mm/init_32.c,檢測所有可用物理頁。? 21,pgtable_cache_init(),定義在include/asm-x86/pgtable_32.h,在slab存儲管理子系統中創建頁目錄頁表的cache。? 22,fork_init(),定義在kernel/fork.c中,初始化多進程環境。此時,執行start_kernel的進程就是所謂的進程0。? 23,buffer_init(),定義在fs/buffer.c中,初始化文件系統的緩沖區。? 24,vfs_cache_init(),定義在fs/dcache.c中,創建虛擬文件系統的Slab Cache。? 25,radix_tree_init(),定義在lib/radix-tree.c。Linux使用radix樹來管理位于文件系統緩沖區中的磁盤塊,radix樹是trie樹的一種。? 26,signals_init(),定義在kernel/signal.c中,初始化信號隊列。? 27,page_writeback_init(),定義在mm/page-writeback.c中,初始化將臟頁頁同步到磁盤上的控制信息。? 28,proc_root_init(),定義在fs/proc/root.c, 初始化proc文件系統? 29,rest_init(),定義在init/main.c中,創建init內核線程(也就是進程1)。init進程創建成功后,進程0釋放Big Kernel Lock,重新調度(因為現在只有兩個進程,所以調度的是init進程)。進程0,就變成了idle進程,只負責調度。? 注:start_kernel函數涉及到很多內容和硬件知識,比如SMP等,有很多是我不知道的,所以只能簡要的從功能上說明一下,有些可能理解錯了,也會略過一些函數,請見諒。? ? 六,init進程? ? init進程執行定義在init/main.c中的kernel_init()函數,完成余下的初始化工作。? 1,lock_kernel(),加上Big Kernel Lock。? 2,初始化SMP環境。? 3,do_basic_setup()。調用driver_init(),加載設備驅動程序。執行do_initcalls(),調用內建模塊的初始化函數,比如kgdb。? 4,init_post()函數會打開/dev/console做為標準輸入文件,并復制出標準輸出和標準錯誤輸出。最后,按下列順序償試執行init程序,位于ramdisk的/init,以及磁盤上的/sbin/init, /etc/init, /bin/init和/bin/sh, 只要有一個能執行就可以。init進程會使用類exec()去調用其它進程,因而不會返回。? ? 七,結語? ? 以上簡要說明了從打開電源到Linux內核引導成功之間發生的一些操作,可以使用bochs虛擬機安裝一個Linux進行調試驗證。各個部分之間的切換我覺得是沒有什么大問題的,其余的就不好說了(^_^!)。
總結
以上是生活随笔 為你收集整理的Linux内核引导简析 的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔 網站內容還不錯,歡迎將生活随笔 推薦給好友。