說實話感受本身快寫不下去了,其一是有些勉強跟不上來,其二是感受本身越寫越差,剛開始多是新鮮感以及不少讀者的鼓勵,如今就是想快點完成本身制定的任務,不過總有幾個讀者給本身鼓勵,很欣慰的事情,很少感慨了,加緊時間多多去探索吧,今天要去描述的是電源開和關時都發生了什麼,一塊兒去看看吧~~css
bootloader引導裝入程序將內核映像加載到內存並處理控制權傳送到內核後在內核引導時每一個子系統都必需要初始化,咱們根據實際執行的線性順序跟蹤內核的初始化過程,下圖說明了從系統加電到斷電這一過程當中全部事情發生的順序,這個圖很少加解釋了,看圖就知道其線性執行順序,中間過程也是很簡單的步驟啦。html
咱們首先討論的是BIOS和Open和Fireware,它們分別是x86和PPC系統加電後在只讀內存的某一地址(通常是Flash ROM)最早運行的代碼,這些代碼負責激活系統中相應的部分,以便處理內核的加載,對於x86而言,這就是系統BIOS的駐留之處,基本的輸入輸出是一塊引導系統並與硬件相關的系統初始化代碼,這裏很少說了,對於PowerPC而言,初始化代碼的類型與PowerPC體系結構的出現時間有關,詳情就參見Open Fireware 的主頁www.openfireware.orgnode
引導裝入程序Bootloaders你們應該早就有所瞭解了,Boot Loaders是駐留在計算機引導設備的程序,第一個引導設備每每是系統中的第一個硬盤,完成足夠的系統初始化工做後,BIOS或固件調用引導裝入程序,一旦成功加載進來,內核就初始化並配置操做系統,對x86系統而言,BIOS容許用戶爲其系統設置引導設備的順序,這裏提一下GRUB,Grand Unified Bootloader 是基於x86的引導裝入程序,用來加載Linux。GRUB2在設計之初就考慮了移植到PPC系統的問題,哎具體的就www.gnu.org/software/grub上有豐富的文檔,而且百度上也有相關的例程之類的,我以爲這裏我就再也不闡釋了。linux
Linux裝入程序即LILO這個我仍是得說說,和GRUB相相似可是LILO僅僅使用配置文件,而且沒有命令行接口。LILO運行時的第一階段步驟以下:web
第一階段:算法
第二階段:數組
肯定啓動哪一個操做系統而且跳轉到該操做系統,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的主要區別安全
最後說一下Yaboot,yaboot是另外一個引導程序(好比說,grub和lilo是比較出名的引導程序),用於Macintosh。主頁:http://yaboot.ozlabs.org/ Yaboot引導時的步驟以下:數據結構
這裏須要在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內核的初始化過程:
小結
今天主要是描述了系統加電和斷電時候的內核引導期間發生了什麼事情,先討論了BIOS和Firmware以及它們是如何與內核引導裝入程序交互的,也討論了裝入程序LILO,GRUB和Yaboot,,最後着重分析了start_kernel()函數的代碼,這個是我看網上代碼的,主要是別人分析的太好了,因此借鑑了一下,只要能懂就行,最後列出了一系列的函數,只要看了連接上的 都能懂的,,我也努力在看,,共同進步吧你們~
版權全部,轉載請註明轉載地址:http://www.cnblogs.com/lihuidashen/p/4250095.html