天天看點

start_kernel初始化

start_kernel 函數主要初始化 linux 系統各個功能子系統,然後進入 kernel 的下一步啟動函數  rest_init(),下面簡單說一下函數内部相關的初始化,有個初步的了解。

詳細内容來自 https://www.cnblogs.com/lifexy/p/7366782.html 僅做學習記錄

asmlinkage void __init start_kernel(void) 

{ 
  char * command_line; 
  extern struct kernel_param __start___param[], __stop___param[];  

  smp_setup_processor_id();  //來設定smp process id,當然目前看到的代碼裡面這裡是空的
  
  unwind_init(); 

//lockdep是linux核心的一個調試子產品,用來檢查核心互斥機制尤其是自旋鎖潛在的死鎖問題。  
//自旋鎖由于是查詢方式等待,不釋放處理器,比一般的互斥機制更容易死鎖,  
//故引入lockdep檢查以下幾種情況可能的死鎖(lockdep将有專門的文章詳細介紹,在此隻是簡單列舉):  
//  
//·同一個程序遞歸地加鎖同一把鎖;  
//  
//·一把鎖既在中斷(或中斷下半部)使能的情況下執行過加鎖操作,  
// 又在中斷(或中斷下半部)裡執行過加鎖操作。這樣該鎖有可能在鎖定時由于中斷發生又試圖在同一處理器上加鎖;  
//  
//·加鎖後導緻依賴圖産生成閉環,這是典型的死鎖現象。  
   lockdep_init(); 
   

//關閉目前CUP中斷  
local_irq_disable(); 

//修改标記early_boot_irqs_enabled;  
//通過一個靜态全局變量 early_boot_irqs_enabled來幫助我們調試代碼,  
//通過這個标記可以幫助我們知道是否在”early bootup code”,也可以通過這個标志警告是有無效的終端打開  
early_boot_irqs_off(); 

//每一個中斷都有一個IRQ描述符(struct irq_desc)來進行描述。  
//這個函數的主要作用是設定所有的 IRQ描述符(struct irq_desc)的鎖是統一的鎖,  
//還是每一個IRQ描述符(struct irq_desc)都有一個小鎖。  
early_init_irq_lock_class(); 

 

 

 

/*

 * Interrupts are still disabled. Do necessary setups, then
 * enable them
 */ 
// 大核心鎖(BKL--Big Kernel Lock)  
//大核心鎖本質上也是自旋鎖,但是它又不同于自旋鎖,自旋鎖是不可以遞歸獲得鎖的,因為那樣會導緻死鎖。  
//但大核心鎖可以遞歸獲得鎖。大核心鎖用于保護整個核心,而自旋鎖用于保護非常特定的某一共享資源。  
//程序保持大核心鎖時可以發生排程,具體實作是:  
//在執行schedule時,schedule将檢查程序是否擁有大核心鎖,如果有,它将被釋放,以緻于其它的程序能夠獲得該鎖,  
//而當輪到該程序運作時,再讓它重新獲得大核心鎖。注意在保持自旋鎖期間是不運作發生排程的。  
//需要特别指出,整個核心隻有一個大核心鎖,其實不難了解,核心隻有一個,而大核心鎖是保護整個核心的,當然有且隻有一個就足夠了。  
//還需要特别指出的是,大核心鎖是曆史遺留,核心中用的非常少,一般保持該鎖的時間較長,是以不提倡使用它。  
//從2.6.11核心起,大核心鎖可以通過配置核心使其變得可搶占(自旋鎖是不可搶占的),這時它實質上是一個互斥鎖,使用信号量實作。  
//大核心鎖的API包括:  
//  
//void lock_kernel(void);  
//  
//該函數用于得到大核心鎖。它可以遞歸調用而不會導緻死鎖。  
//  
//void unlock_kernel(void);  
//  
//該函數用于釋放大核心鎖。當然必須與lock_kernel配對使用,調用了多少次lock_kernel,就需要調用多少次unlock_kernel。  
//大核心鎖的API使用非常簡單,按照以下方式使用就可以了:  
//lock_kernel(); //對被保護的共享資源的通路 … unlock_kernel();  
//http://blog.csdn.net/universus/archive/2010/05/25/5623971.aspx  
  lock_kernel(); 

 
//初始化time ticket,時鐘  
  tick_init(); 

 

//函數 tick_init() 很簡單,調用 clockevents_register_notifier 函數向 clockevents_chain 通知鍊注冊元素:  
// tick_notifier。這個元素的回調函數指明了當時鐘事件裝置資訊發生變化(例如新加入一個時鐘事件裝置等等)時,  
//應該執行的操作,該回調函數為 tick_notify   
//http://blogold.chinaunix.net/u3/97642/showart_2050200.html  
  boot_cpu_init();

  

//初始化頁位址,當然對于arm這裡是個空函數  
//http://book.chinaunix.net/special/ebook/PrenticeHall/PrenticeHallPTRTheLinuxKernelPrimer/0131181637/ch08lev1sec5.html  
  page_address_init(); 

  

/*列印KER_NOTICE,這裡的KER_NOTICE是字元串<5>*/
  printk(KERN_NOTICE);

 

 

/*列印以下linux版本資訊:       
“Linux version 2.6.22.6 ([email protected]) (gcc version 3.4.5) #1 Fri Jun 16 00:55:53 CST 2017” */
 printk(linux_banner);
      

//系結構相關的核心初始化過程,處理uboot傳遞進來的atag參數( setup_memory_tags()和setup_commandline _tags() )  
//http://www.cublog.cn/u3/94690/showart_2238008.html  
setup_arch(&command_line); 

  

//處理啟動指令,這裡就是設定的cmd_line,
//儲存未改變的comand_line到字元數組static_command_line[] 中。
//儲存  boot_command_line到字元數組saved_command_line[]中  
setup_command_line(command_line); 

unwind_setup();

//如果沒有定義CONFIG_SMP宏,則這個函數為空函數。  
//如果定義了CONFIG_SMP宏,則這個setup_per_cpu_areas()函數給每個CPU配置設定記憶體,  
//并拷貝.data.percpu段的資料。為系統中的每個CPU的per_cpu變量申請空間。  
setup_per_cpu_areas();

//定義在include/asm-x86/smp.h。  
//如果是SMP環境,則設定boot CPU的一些資料。在引導過程中使用的CPU稱為boot CPU  
smp_prepare_boot_cpu(); /* arch-specific boot-cpu hooks */ 
/* 程序排程器初始化 */
sched_init(); 

/* 禁止核心搶占 */  
preempt_disable();
      
//設定node 和 zone 資料結構  
//記憶體管理的講解:http://blog.chinaunix.net/space.php?uid=361890&do=blog&cuid=2146541  
build_all_zonelists(NULL); 
 
//初始化page allocation相關結構  
page_alloc_init();

/* 列印Linux啟動指令行參數 */   
printk(KERN_NOTICE "Kernel command line: %s/n", boot_command_line); 
  
//解析核心參數  
//對核心參數的解析:http://hi.baidu.com/yuhuntero/blog/item/654a7411e45ce519b8127ba9.html  
parse_early_param(); 
parse_args("Booting kernel", static_command_line, __start___param, 
  __stop___param - __start___param, 
  &unknown_bootoption); 

/*
* These use large bootmem allocations and must precede
* kmem_cache_init()
*/ 
//初始化hash表,以便于從程序的PID獲得對應的程序描述指針,按照實際的實體記憶體初始化pid hash表  
//這裡涉及到程序管理http://blog.csdn.net/satanwxd/archive/2010/03/27/5422053.aspx  
pidhash_init(); 

//初始化VFS的兩個重要資料結構dcache和inode的緩存。  
//http://blog.csdn.net/yunsongice/archive/2011/02/01/6171324.aspx  
vfs_caches_init_early(); 

//把編譯期間,kbuild設定的異常表,也就是__start___ex_table和__stop___ex_table之中的所有元素進行排序  
sort_main_extable(); 

//初始化中斷向量表  
//http://blog.csdn.net/yunsongice/archive/2011/02/01/6171325.aspx  
trap_init(); 

//memory map初始化  
//http://blog.csdn.net/huyugv_830913/archive/2010/09/15/5886970.aspx  
mm_init(); 

/*
* Set up the scheduler prior starting any interrupts (such as the
* timer interrupt). Full topology setup happens at smp_init()
* time - but meanwhile we still have a functioning scheduler.
*/ 
//核心程序排程器初始化,排程器的初始化的優先級要高于任何中斷的建立,  
//并且初始化程序0,即idle程序,但是并沒有設定idle程序的NEED_RESCHED标志,  
//是以還會繼續完成核心初始化剩下的事情。  
//這裡僅僅為程序排程程式的執行做準備。  
//它所做的具體工作是調用init_bh函數(kernel/softirq.c)把timer,tqueue,immediate三個人物隊列加入下半部分的數組  
sched_init(); 

/*
* Disable preemption - early bootup scheduling is extremely
* fragile until we cpu_idle() for the first time.
*/ 
//搶占計數器加1   
 preempt_disable();

 

//檢查中斷是否打開,如果已經打開,則關閉中斷   
  if (!irqs_disabled()) { 
  printk(KERN_WARNING "start_kernel(): bug: interrupts were " 
  "enabled *very* early, fixing it/n"); 
  local_irq_disable(); 
  } 

 

  sort_main_extable();  /*
 * trap_init函數完成對系統保留中斷向量(異常、非屏蔽中斷以及系統調用)              
 * 的初始化,init_IRQ函數則完成其餘中斷向量的初始化
 */
 trap_init();    

//Read-Copy-Update的初始化  
//RCU機制是Linux2.6之後提供的一種資料一緻性通路的機制,  
//從RCU(read-copy-update)的名稱上看,我們就能對他的實作機制有一個大概的了解,  
//在修改資料的時候,首先需要讀取資料,然後生成一個副本,對副本進行修改,  
//修改完成之後再将老資料update成新的資料,此所謂RCU。  
//http://blog.ednchina.com/tiloog/193361/message.aspx  
//http://blogold.chinaunix.net/u1/51562/showart_1341707.html  
rcu_init();
 
//初始化IRQ中斷和終端描述符。  
//初始化系統中支援的最大可能的中斷描述結構struct irqdesc變量數組irq_desc[NR_IRQS],  
//把每個結構變量irq_desc[n]都初始化為預先定義好的壞中斷描述結構變量bad_irq_desc,  
//并初始化該中斷的連結清單表頭成員結構變量pend  
init_IRQ();    

/* 初始化hash表,便于從程序的PID獲得對應的程序描述符指針 */
pidhash_init();

 

//初始化定時器Timer相關的資料結構  
//http://www.ibm.com/developerworks/cn/linux/l-cn-clocks/index.html  
init_timers();  

//對高精度時鐘進行初始化  
hrtimers_init();
 
//軟中斷初始化  
//http://blogold.chinaunix.net/u1/51562/showart_494363.html  
softirq_init(); 
 
//初始化時鐘源  
timekeeping_init(); 
 
//初始化系統時間,  
//檢查系統定時器描述結構struct sys_timer全局變量system_timer是否為空,  
//如果為空将其指向dummy_gettimeoffset()函數。  
//http://www.ibm.com/developerworks/cn/linux/l-cn-clocks/index.html  
time_init();  

//profile隻是核心的一個調試性能的工具,  
//這個可以通過menuconfig中的Instrumentation Support->profile打開。  
//http://www.linuxdiyf.com/bbs//thread-71446-1-1.html  
profile_init();
    
/*if判斷中斷是否打開,如果已經打開,列印資料*/   
if (!irqs_disabled()) 
    printk(KERN_CRIT "start_kernel(): bug: interrupts were enabled early/n"); 

//與開始的early_boot_irqs_off相對應  
early_boot_irqs_on(); 
 
//與local_irq_disbale相對應,開CPU中斷  
local_irq_enable();

/** HACK ALERT! This is early. We're enabling the console before
* we've done PCI setups etc, and console_init() must be aware of
* this. But we do want output early, in case something goes wrong.
*/ 
//初始化控制台以顯示printk的内容,在此之前調用的printk,隻是把資料存到緩沖區裡,  
//隻有在這個函數調用後,才會在控制台列印出内容  
//該函數執行後可調用printk()函數将log_buf中符合列印級别要求的系統資訊列印到控制台上。  
console_init(); 

if (panic_later) 
panic(panic_later, panic_param);  

//如果定義了CONFIG_LOCKDEP宏,那麼就列印鎖依賴資訊,否則什麼也不做  
lockdep_info(); 

/*
* Need to run this when irqs are enabled, because it wants
* to self-test [hard/soft]-irqs on/off lock inversion bugs
* too:
*/ 
//如果定義CONFIG_DEBUG_LOCKING_API_SELFTESTS宏  
//則locking_selftest()是一個空函數,否則執行鎖自測  
 locking_selftest(); 

  

#ifdef CONFIG_BLK_DEV_INITRD  
if (initrd_start && !initrd_below_start_ok && 
   page_to_pfn(virt_to_page((void *)initrd_start)) < min_low_pfn) { 
printk(KERN_CRIT "initrd overwritten (0x%08lx < 0x%08lx) - " 
   "disabling it./n",    page_to_pfn(virt_to_page((void *)initrd_start)), 
   min_low_pfn); 
initrd_start = 0; 
} 
#endif

 
 /* 虛拟檔案系統的初始化 */
 vfs_caches_init_early();       
 cpuset_init_early();
 mem_init();

/* slab初始化 */
kmem_cache_init();  

//是否是對SMP的支援,單核是否需要??這個要分析  
setup_per_cpu_pageset(); 

numa_policy_init(); 

if (late_time_init) 
   late_time_init(); 

//calibrate_delay()函數可以計算出cpu在一秒鐘内執行了多少次一個極短的循環,  
//計算出來的值經過處理後得到BogoMIPS 值,  
//Bogo是Bogus(僞)的意思,MIPS是millions of instructions per second(百萬條指令每秒)的縮寫。  
//這樣我們就知道了其實這個函數是linux核心中一個cpu性能測試函數。  
//http://blogold.chinaunix.net/u2/86768/showart_2196664.html  
calibrate_delay();
   
//PID是process id的縮寫  
//http://blog.csdn.net/satanwxd/archive/2010/03/27/5422053.aspx  
pidmap_init();

 /* 接下來的函數中,大多數都是為有關的管理機制建立專用的slab緩存 */ 
pgtable_cache_init();
 
/* 初始化優先級樹index_bits_to_maxindex數組 */
prio_tree_init();          

//來自mm/rmap.c  
//配置設定一個anon_vma_cachep作為anon_vma的slab緩存。  
//這個技術是PFRA(頁框回收算法)技術中的組成部分。  
//這個技術為定位而生——快速的定位指向同一頁框的所有頁表項。  
anon_vma_init();

    
#ifdef CONFIG_X86  
if (efi_enabled) 
efi_enter_virtual_mode(); 
#endif 

//根據實體記憶體大小計算允許建立程序的數量  
//http://www.jollen.org/blog/2006/11/jollen_linux_3_fork_init.html  
fork_init(totalram_pages);

   
//給程序的各種資源管理結構配置設定了相應的對象緩存區  
//http://www.shangshuwu.cn/index.php/Linux核心的程序建立  
proc_caches_init(); 

//建立 buffer_head SLAB 緩存  
buffer_init(); 

unnamed_dev_init();

//初始化key的management stuff  
key_init(); 

//關于系統安全的初始化,主要是通路控制  
//http://blog.csdn.net/nhczp/archive/2008/04/29/2341194.aspx  
security_init();  

//調用kmem_cache_create()函數來為VFS建立各種SLAB配置設定器緩存  
//包括:names_cachep、filp_cachep、dquot_cachep和bh_cachep等四個SLAB配置設定器緩存  
vfs_caches_init(totalram_pages); 
 
radix_tree_init(); 

//建立信号隊列  
signals_init();

   

/* rootfs populating might need page-writeback */ 
//回寫相關的初始化  
//http://blog.csdn.net/yangp01/archive/2010/04/06/5454822.aspx  \page_writeback_init();   

#ifdef CONFIG_PROC_FS  
   proc_root_init(); 
#endif  

//http://blogold.chinaunix.net/u1/51562/showart_1777937.html  
cpuset_init();
 
程序狀态初始化,實際上就是配置設定了一個存儲線程狀态的高速緩存  
taskstats_init_early(); 

delayacct_init();  

//測試CPU的各種缺陷,記錄檢測到的缺陷,以便于核心的其他部分以後可以使用他們工作。  
check_bugs(); 
 
//電源相關的初始化  
//http://blogold.chinaunix.net/u/548/showart.php?id=377952  
acpi_early_init(); /* before LAPIC and SMP init */ 
 

//接着進入rest_init()建立init程序,建立根檔案系統,啟動應用程式
rest_init(); 
}
           

此函數之後,rest_init()  進一步啟動 linux。

繼續閱讀