說實話感覺自己快寫不下去了,其一是有些勉強跟不上來,其二是感覺自己越寫越差,剛開始可能是新鮮感以及很多讀者的鼓勵,現在就是想快點完成自己制定的任務,不過總有幾個讀者給自己鼓勵,很欣慰的事情,不多感慨了,加緊時間多多去探索吧,今天要去描述的是電源開和關時都發生了什么,一起去看看吧~~
bootloader引導裝入程序將內核映像加載到內存並處理控制權傳送到內核后在內核引導時每個子系統都必須要初始化,我們根據實際執行的線性順序跟蹤內核的初始化過程,下圖說明了從系統加電到斷電這一過程中所有事情發生的順序,這個圖不多加解釋了,看圖就知道其線性執行順序,中間過程也是很簡單的步驟啦。
我們首先討論的是BIOS和Open和Fireware,它們分別是x86和PPC系統加電后在只讀內存的某一地址(一般是Flash ROM)最先運行的代碼,這些代碼負責激活系統中相應的部分,以便處理內核的加載,對於x86而言,這就是系統BIOS的駐留之處,基本的輸入輸出是一塊引導系統並與硬件相關的系統初始化代碼,這里不多說了,對於PowerPC而言,初始化代碼的類型與PowerPC體系結構的出現時間有關,詳情就參見Open Fireware 的主頁www.openfireware.org
引導裝入程序Bootloaders大家應該早就有所了解了,Boot Loaders是駐留在計算機引導設備的程序,第一個引導設備往往是系統中的第一個硬盤,完成足夠的系統初始化工作后,BIOS或固件調用引導裝入程序,一旦成功加載進來,內核就初始化並配置操作系統,對x86系統而言,BIOS允許用戶為其系統設置引導設備的順序,這里提一下GRUB,Grand Unified Bootloader 是基於x86的引導裝入程序,用來加載Linux。GRUB2在設計之初就考慮了移植到PPC系統的問題,哎具體的就www.gnu.org/software/grub上有豐富的文檔,並且百度上也有相關的例程之類的,我覺得這里我就不再闡釋了。
Linux裝入程序即LILO這個我還是得說說,和GRUB相類似但是LILO僅僅使用配置文件,並且沒有命令行接口。LILO運行時的第一階段步驟如下:
第一階段:
- 開始執行並且顯示“L.";
- 檢測磁盤幾何信息並且顯示“I.";
- 加載第二階段的代碼。
第二階段:
- 開始執行並且顯示“L.";
- 確定引導數據和操作系統的位置,並且顯示”O.";
確定啟動哪個操作系統並且跳轉到該操作系統,LILO配置文件中的一段代碼如下(代碼在etc/lilo.conf上可以查看):
1 image = /boot/bzimage-2.6.7-mytestkernel //image指明內核所在地 2 label = Kernel 2.6.7, my test kernel //label描述配置的字符串 3 root = /dev/hda6 //root指明根文件系統駐留的分區 4 read-only //表明根分區在引導時候不可被修改
GRUB和LILO的主要區別
- LILO將配置信息存儲在主引導記錄中,若有任何改動,必須運行/sbin/lilo來更新主引導記錄
- LILO沒有交互式的命令行接口
- LILO不能讀取不同的文件系統
最后說一下Yaboot,yaboot是另一個引導程序(比如說,grub和lilo是比較出名的引導程序),用於Macintosh。主頁:http://yaboot.ozlabs.org/ Yaboot引導時的步驟如下:
- OF調用Yaboot
- 找到引導設備和引導路徑並打開引導分區
- 打開/etc/yaboot.conf或命令解釋器
- 加載映像或內核以及initrd
- 執行映像
這里需要在Ubuntu上自己操作,當然對Ubuntu的基本命令操作是首先要了解的,然后才能根據步驟一步一步執行下去。
x86和PowerPC體系結構的硬件初始化,由於內存管理的初始化與硬件息息相關,要理解其初始化過程就必須了解硬件的規格,那么都只能去看看資料才能了解了,這里我多講不了。現在的PowerPC和x86的代碼都集中在init/main.c的start_kernel()中,該例程位於體系結構無關的代碼段,它調用特定體系結構的例程來完成內存初始化。下面我們來探究一下start_kernel()函數。
跳轉到start_kernel()時候,執行進程0,也就是平時說的超級用戶進程,進程0孕育了進程1,也就是init進程,然后進程0就變成CPU的空閑進程,調/sbin/init時,僅有這兩個進程在運行:
1 asmlinkage void __init start_kernel(void) 2 { 3 char * command_line; 4 extern struct kernel_param __start___param[], __stop___param[]; 5 //來設置smp process id,當然目前看到的代碼里面這里是空的 6 smp_setup_processor_id(); 7 //lockdep是linux內核的一個調試模塊,用來檢查內核互斥機制尤其是自旋鎖潛在的死鎖問題。 8 //自旋鎖由於是查詢方式等待,不釋放處理器,比一般的互斥機制更容易死鎖, 9 //故引入lockdep檢查以下幾種情況可能的死鎖(lockdep將有專門的文章詳細介紹,在此只是簡單列舉): 10 // 11 //·同一個進程遞歸地加鎖同一把鎖; 12 // 13 //·一把鎖既在中斷(或中斷下半部)使能的情況下執行過加鎖操作, 14 // 又在中斷(或中斷下半部)里執行過加鎖操作。這樣該鎖有可能在鎖定時由於中斷發生又試圖在同一處理器上加鎖; 15 // 16 //·加鎖后導致依賴圖產生成閉環,這是典型的死鎖現象。 17 lockdep_init(); 18 debug_objects_early_init(); 19 //初始化stack_canary棧3 20 //stack_canary的是帶防止棧溢出攻擊保護的堆棧。 21 // 當user space的程序通過int 0x80進入內核空間的時候,CPU自動完成一次堆棧切換, 22 //從user space的stack切換到kernel space的stack。 23 // 在這個進程exit之前所發生的所有系統調用所使用的kernel stack都是同一個。 24 //kernel stack的大小一般為4096/8192, 25 //內核堆棧示意圖幫助大家理解: 26 // 27 // 內存低址 內存高址 28 // | |<-----------------------------esp| 29 // +-----------------------------------4096-------------------------------+ 30 // | 72 | 4 | x < 4016 | 4 | 31 // +------------------+-----------------+---------------------------------+ 32 // |thread_info | | STACK_END_MAGIC | var/call chain |stack_canary | 33 // +------------------+-----------------+---------------------------------+ 34 // | 28 | 44 | | | 35 // V | | 36 // restart_block V 37 // 38 //esp+0x0 +0x40 39 // +---------------------------------------------------------------------------+ 40 // |ebx|ecx|edx|esi|edi|ebp|eax|ds|es|fs|gs|orig_eax|eip|cs|eflags|oldesp|oldss| 41 // +---------------------------------------------------------------------------+ 42 // | kernel完成 | cpu自動完成 | 43 boot_init_stack_canary(); 44 // cgroup: 它的全稱為control group.即一組進程的行為控制. 45 // 比如,我們限制進程/bin/sh的CPU使用為20%.我們就可以建一個cpu占用為20%的cgroup. 46 // 然后將/bin/sh進程添加到這個cgroup中.當然,一個cgroup可以有多個進程. 47 48 cgroup_init_early(); 49 //更新kernel中的所有的立即數值,但是包括哪些需要再看? 50 core_imv_update(); 51 //關閉當前CUP中斷 52 local_irq_disable(); 53 //修改標記early_boot_irqs_enabled; 54 //通過一個靜態全局變量 early_boot_irqs_enabled來幫助我們調試代碼, 55 //通過這個標記可以幫助我們知道是否在”early bootup code”,也可以通過這個標志警告是有無效的終端打開 56 early_boot_irqs_off(); 57 //每一個中斷都有一個IRQ描述符(struct irq_desc)來進行描述。 58 //這個函數的主要作用是設置所有的 IRQ描述符(struct irq_desc)的鎖是統一的鎖, 59 //還是每一個IRQ描述符(struct irq_desc)都有一個小鎖。 60 early_init_irq_lock_class(); 61 62 // 大內核鎖(BKL--Big Kernel Lock) 63 //大內核鎖本質上也是自旋鎖,但是它又不同於自旋鎖,自旋鎖是不可以遞歸獲得鎖的,因為那樣會導致死鎖。 64 //但大內核鎖可以遞歸獲得鎖。大內核鎖用於保護整個內核,而自旋鎖用於保護非常特定的某一共享資源。 65 //進程保持大內核鎖時可以發生調度,具體實現是: 66 //在執行schedule時,schedule將檢查進程是否擁有大內核鎖,如果有,它將被釋放,以致於其它的進程能夠獲得該鎖, 67 //而當輪到該進程運行時,再讓它重新獲得大內核鎖。注意在保持自旋鎖期間是不運行發生調度的。 68 //需要特別指出,整個內核只有一個大內核鎖,其實不難理解,內核只有一個,而大內核鎖是保護整個內核的,當然有且只有一個就足夠了。 69 //還需要特別指出的是,大內核鎖是歷史遺留,內核中用的非常少,一般保持該鎖的時間較長,因此不提倡使用它。 70 //從2.6.11內核起,大內核鎖可以通過配置內核使其變得可搶占(自旋鎖是不可搶占的),這時它實質上是一個互斥鎖,使用信號量實現。 71 //大內核鎖的API包括: 72 // 73 //void lock_kernel(void); 74 // 75 //該函數用於得到大內核鎖。它可以遞歸調用而不會導致死鎖。 76 // 77 //void unlock_kernel(void); 78 // 79 //該函數用於釋放大內核鎖。當然必須與lock_kernel配對使用,調用了多少次lock_kernel,就需要調用多少次unlock_kernel。 80 //大內核鎖的API使用非常簡單,按照以下方式使用就可以了: 81 //lock_kernel(); //對被保護的共享資源的訪問 … unlock_kernel(); 82 lock_kernel(); 83 //初始化time ticket,時鍾 84 tick_init(); 85 //函數 tick_init() 很簡單,調用 clockevents_register_notifier 函數向 clockevents_chain 通知鏈注冊元素: 86 // tick_notifier。這個元素的回調函數指明了當時鍾事件設備信息發生變化(例如新加入一個時鍾事件設備等等)時, 87 //應該執行的操作,該回調函數為 tick_notify 88 boot_cpu_init(); 89 //初始化頁地址,當然對於arm這里是個空函數 90 page_address_init(); 91 printk(KERN_NOTICE "%s", linux_banner); 92 //系結構相關的內核初始化過程 93 setup_arch(&command_line); 94 //初始化內存管理 95 mm_init_owner(&init_mm, &init_task); 96 //處理啟動命令,這里就是設置的cmd_line 97 setup_command_line(command_line); 98 //這個在定義了SMP的時候有作用,現在這里為空函數;對於smp的使用,后面在看。。。 99 setup_nr_cpu_ids(); 100 //如果沒有定義CONFIG_SMP宏,則這個函數為空函數。 101 //如果定義了CONFIG_SMP宏,則這個setup_per_cpu_areas()函數給每個CPU分配內存, 102 //並拷貝.data.percpu段的數據。為系統中的每個CPU的per_cpu變量申請空間。 103 setup_per_cpu_areas(); 104 //定義在include/asm-x86/smp.h。 105 //如果是SMP環境,則設置boot CPU的一些數據。在引導過程中使用的CPU稱為boot CPU 106 smp_prepare_boot_cpu(); /* arch-specific boot-cpu hooks */ 107 //設置node 和 zone 數據結構 108 //內存管理的講解: 109 build_all_zonelists(NULL); 110 //初始化page allocation相關結構 111 page_alloc_init(); 112 printk(KERN_NOTICE "Kernel command line: %s/n", boot_command_line); 113 //解析內核參數 114 parse_early_param(); 115 parse_args("Booting kernel", static_command_line, __start___param, 116 __stop___param - __start___param, 117 &unknown_bootoption); 118 119 //初始化hash表,以便於從進程的PID獲得對應的進程描述指針,按照實際的物理內存初始化pid hash表 120 //這里涉及到進程管理 121 pidhash_init(); 122 //初始化VFS的兩個重要數據結構dcache和inode的緩存。 123 vfs_caches_init_early(); 124 //把編譯期間,kbuild設置的異常表,也就是__start___ex_table和__stop___ex_table之中的所有元素進行排序 125 sort_main_extable(); 126 //初始化中斷向量表 127 trap_init(); 128 //memory map初始化 129 mm_init(); 130 //核心進程調度器初始化,調度器的初始化的優先級要高於任何中斷的建立, 131 //並且初始化進程0,即idle進程,但是並沒有設置idle進程的NEED_RESCHED標志, 132 //所以還會繼續完成內核初始化剩下的事情。 133 //這里僅僅為進程調度程序的執行做准備。 134 //它所做的具體工作是調用init_bh函數(kernel/softirq.c)把timer,tqueue,immediate三個人物隊列加入下半部分的數組 135 sched_init(); 136 //搶占計數器加1 137 preempt_disable(); 138 //檢查中斷是否打開 139 if (!irqs_disabled()) { 140 printk(KERN_WARNING "start_kernel(): bug: interrupts were " 141 "enabled *very* early, fixing it/n"); 142 local_irq_disable(); 143 } 144 //Read-Copy-Update的初始化 145 //RCU機制是Linux2.6之后提供的一種數據一致性訪問的機制, 146 //從RCU(read-copy-update)的名稱上看,我們就能對他的實現機制有一個大概的了解, 147 //在修改數據的時候,首先需要讀取數據,然后生成一個副本,對副本進行修改, 148 //修改完成之后再將老數據update成新的數據,此所謂RCU。 149 rcu_init(); 150 //定義在lib/radix-tree.c。 151 //Linux使用radix樹來管理位於文件系統緩沖區中的磁盤塊, 152 //radix樹是trie樹的一種 153 radix_tree_init(); 154 /* init some links before init_ISA_irqs() */ 155 //early_irq_init 則對數組中每個成員結構進行初始化, 156 //例如, 初始每個中斷源的中斷號.其他的函數基本為空. 157 early_irq_init(); 158 //初始化IRQ中斷和終端描述符。 159 //初始化系統中支持的最大可能的中斷描述結構struct irqdesc變量數組irq_desc[NR_IRQS], 160 //把每個結構變量irq_desc[n]都初始化為預先定義好的壞中斷描述結構變量bad_irq_desc, 161 //並初始化該中斷的鏈表表頭成員結構變量pend 162 init_IRQ(); 163 //prio-tree是一棵查找樹,管理的是什么? 164 //http://blog.csdn.net/dog250/archive/2010/06/28/5700317.aspx 165 prio_tree_init(); 166 //初始化定時器Timer相關的數據結構 167 init_timers(); 168 //對高精度時鍾進行初始化 169 hrtimers_init(); 170 //軟中斷初始化 171 softirq_init(); 172 //初始化時鍾源 173 timekeeping_init(); 174 //初始化系統時間, 175 //檢查系統定時器描述結構struct sys_timer全局變量system_timer是否為空, 176 //如果為空將其指向dummy_gettimeoffset()函數。 177 time_init(); 178 //profile只是內核的一個調試性能的工具, 179 //這個可以通過menuconfig中的Instrumentation Support->profile打開。 180 profile_init(); 181 if (!irqs_disabled()) 182 printk(KERN_CRIT "start_kernel(): bug: interrupts were " 183 "enabled early/n"); 184 //與開始的early_boot_irqs_off相對應 185 early_boot_irqs_on(); 186 //與local_irq_disbale相對應,開中斷 187 local_irq_enable(); 188 gfp_allowed_mask = __GFP_BITS_MASK; 189 //memory cache的初始化 190 kmem_cache_init_late(); 191 //初始化控制台以顯示printk的內容,在此之前調用的printk,只是把數據存到緩沖區里, 192 //只有在這個函數調用后,才會在控制台打印出內容 193 //該函數執行后可調用printk()函數將log_buf中符合打印級別要求的系統信息打印到控制台上。 194 console_init(); 195 if (panic_later) 196 panic(panic_later, panic_param); 197 //如果定義了CONFIG_LOCKDEP宏,那么就打印鎖依賴信息,否則什么也不做 198 lockdep_info(); 199 200 //如果定義CONFIG_DEBUG_LOCKING_API_SELFTESTS宏 201 //則locking_selftest()是一個空函數,否則執行鎖自測 202 locking_selftest(); 203 #ifdef CONFIG_BLK_DEV_INITRD 204 if (initrd_start && !initrd_below_start_ok && 205 page_to_pfn(virt_to_page((void *)initrd_start)) < min_low_pfn) { 206 printk(KERN_CRIT "initrd overwritten (0x%08lx < 0x%08lx) - " 207 "disabling it./n", 208 page_to_pfn(virt_to_page((void *)initrd_start)), 209 min_low_pfn); 210 initrd_start = 0; 211 } 212 #endif 213 //頁面初始化,可以參考上面的cgroup機制 214 page_cgroup_init(); 215 //頁面分配debug啟用 216 enable_debug_pagealloc(); 217 //此處函數為空 218 kmemtrace_init(); 219 //memory lead偵測初始化,如何偵測??? 220 kmemleak_init(); 221 222 //在kmem_caches之后表示建立一個高速緩沖池,建立起SLAB_DEBUG_OBJECTS標志。??? 223 debug_objects_mem_init(); 224 //idr在linux內核中指的就是整數ID管理機制, 225 //從本質上來說,這就是一種將整數ID號和特定指針關聯在一起的機制 226 //idr機制適用在那些需要把某個整數和特定指針關聯在一起的地方。 227 idr_init_cache(); 228 //是否是對SMP的支持,單核是否需要??這個要分析 229 setup_per_cpu_pageset(); 230 //NUMA (Non Uniform Memory Access) policy 231 //具體是什么不懂 232 numa_policy_init(); 233 if (late_time_init) 234 late_time_init(); 235 //初始化調度時鍾 236 sched_clock_init(); 237 //calibrate_delay()函數可以計算出cpu在一秒鍾內執行了多少次一個極短的循環, 238 //計算出來的值經過處理后得到BogoMIPS 值, 239 //Bogo是Bogus(偽)的意思,MIPS是millions of instructions per second(百萬條指令每秒)的縮寫。 240 //這樣我們就知道了其實這個函數是linux內核中一個cpu性能測試函數。 241 calibrate_delay(); 242 //PID是process id的縮寫 243 pidmap_init(); 244 //來自mm/rmap.c 245 //分配一個anon_vma_cachep作為anon_vma的slab緩存。 246 //這個技術是PFRA(頁框回收算法)技術中的組成部分。 247 //這個技術為定位而生——快速的定位指向同一頁框的所有頁表項。 248 anon_vma_init(); 249 #ifdef CONFIG_X86 250 if (efi_enabled) 251 efi_enter_virtual_mode(); 252 #endif 253 //創建thread_info緩存 254 thread_info_cache_init(); 255 //申請了一個slab來存放credentials??????如何理解? 256 cred_init(); 257 //根據物理內存大小計算允許創建進程的數量 258 fork_init(totalram_pages); 259 //給進程的各種資源管理結構分配了相應的對象緩存區 260 proc_caches_init(); 261 //創建 buffer_head SLAB 緩存 262 buffer_init(); 263 //初始化key的management stuff 264 key_init(); 265 //關於系統安全的初始化,主要是訪問控制 266 security_init(); 267 //與debug kernel相關 268 dbg_late_init(); 269 //調用kmem_cache_create()函數來為VFS創建各種SLAB分配器緩存 270 //包括:names_cachep、filp_cachep、dquot_cachep和bh_cachep等四個SLAB分配器緩存 271 vfs_caches_init(totalram_pages); 272 //創建信號隊列 273 signals_init(); 274 //回寫相關的初始化 275 page_writeback_init(); 276 #ifdef CONFIG_PROC_FS 277 proc_root_init(); 278 #endif 279 //它將剩余的subsys初始化.然后將init_css_set添加進哈希數組css_set_table[ ]中. 280 //在上面的代碼中css_set_hash()是css_set_table的哈希函數. 281 //它是css_set->subsys為哈希鍵值,到css_set_table[ ]中找到對應項.然后調用hlist_add_head()將init_css_set添加到沖突項中. 282 //然后,注冊了cgroup文件系統.這個文件系統也是我們在用戶空間使用cgroup時必須掛載的. 283 //最后,在proc的根目錄下創建了一個名為cgroups的文件.用來從用戶空間觀察cgroup的狀態. 284 cgroup_init(); 285 cpuset_init(); 286 ////進程狀態初始化,實際上就是分配了一個存儲線程狀態的高速緩存 287 taskstats_init_early(); 288 delayacct_init(); 289 //此處為一空函數 290 imv_init_complete(); 291 //測試CPU的各種缺陷,記錄檢測到的缺陷,以便於內核的其他部分以后可以使用他們工作。 292 check_bugs(); 293 //電源相關的初始化 294 acpi_early_init(); /* before LAPIC and SMP init */ 295 // 296 sfi_init_late(); 297 ftrace_init(); 298 //創建1號進程,詳細分析之 299 rest_init(); 300 }
smp_setup_processor_id()boot_cpu_init()setup_arch(&command_line);setup_nr_cpu_ids()setup_per_cpu_areas()smp_prepare_boot_cpu()setup_per_cpu_pageset();calibrate_delay();cpuset_init();
boot_init_stack_canary()page_address_init();mm_init_owner();page_alloc_init();mm_init();rcu_init();kmem_cache_init_late();page_cgroup_init();kmemleak_init();numa_policy_init();anon_vma_init();page_writeback_init();
pidhash_init();sched_init();sched_clock_init()pidmap_init();fork_init(totalram_pages);taskstats_init_early();
vfs_caches_init_early();thread_info_cache_init();vfs_caches_init(totalram_pages);
early_irq_init();init_IRQ();softirq_init();
lockdep_init();lockdep_info();locking_selftest();
tick_init();init_timers();hrtimers_init();timekeeping_init();time_init();
debug_objects_early_init();console_init();enable_debug_pagealloc();debug_objects_mem_init();dbg_late_init();
sort_main_extable();trap_init();efi_enter_virtual_mode();cred_init();proc_caches_init();buffer_init();key_init();security_init();signals_init();proc_root_init();delayacct_init();check_bugs();acpi_early_init();sfi_init_late();
cgroup_init_early(); build_all_zonelists(NULL); preempt_disable(); radix_tree_init(); prio_tree_init(); profile_init(); idr_init_cache(); cgroup_init(); ftrace_init();
最后來總結一下Linux內核的初始化過程:
- 啟動和鎖住內核
- 為Linux的內存管理初始化頁高速緩存和頁面地址
- 為多CPU做好准備
- 顯示內核標志
- 初始化Linux調度程序
- 分析傳到Linux內核的參數
- 初始化中斷處理程序,定時器處理程序和信號處理程序
- 掛載初始文件系統
- 完成系統的初始化,並且將控制權從init交回系統
小結
今天主要是描述了系統加電和斷電時候的內核引導期間發生了什么事情,先討論了BIOS和Firmware以及它們是如何與內核引導裝入程序交互的,也討論了裝入程序LILO,GRUB和Yaboot,,最后着重分析了start_kernel()函數的代碼,這個是我看網上代碼的,主要是別人分析的太好了,所以借鑒了一下,只要能懂就行,最后列出了一系列的函數,只要看了鏈接上的 都能懂的,,我也努力在看,,共同進步吧大家~
版權所有,轉載請注明轉載地址:http://www.cnblogs.com/lihuidashen/p/4250095.html