1. 程式人生 > >核心的配置和編譯及程式碼分析(一)

核心的配置和編譯及程式碼分析(一)

關於核心的配置和編譯和uboot是一樣的
1 解壓縮
2 打patch
3 配置核心
4 編譯
配置核心有3種方法:
1 make menuconfig這樣就需要配置所有的配置項
2 使用預設的配置,在其上面做修改。在arch/arm/configs下有很多預設的配置,XXX_defconfig,可以根據你板子所
  使用的晶片來選擇以下相似的配置,比如make s3c2410_defconfig,之後再make menuconfig,make menuconfig是需要頂
  層目錄下有一個.config檔案
3 使用廠家提供的 比如: 廠家_config


 注意:我們一般會選用 make XXX_defconfig 然後再make menuconfig。make XXX_defconfig之後會在原始碼樹下生成.config檔案,(其實,我們也不需要執行該條命令,可以這樣做,直接執行 cp /arch/arm/configs/s3c2410_defconfig  .config  )都是對配置項的編譯與否。以一個配置項CONFIG_DM9000網絡卡為例,這個配置項有三種選擇,CONFIG_DM9000=y,CONFIG_DM9000=m,CONFIG_DM9000=空,為y說明將這個網絡卡編譯進核心,為m說明將這個網絡卡編譯成模組,為空說明核心不支援該網絡卡。
 之後我們再make menuconfig會生成兩個很重要的檔案,include/linux/autoconf.h和include/config/auto.conf 這兩個
 檔案都是來源於.config檔案,autoconf.h檔案裡此時CONFIG_DM9000=1,也即只要是在.config檔案裡無論編譯選項
 是m或者y的,都#define CONFIG_DM9000 1,autoconf.h檔案會被c語言原始碼用到該巨集。
 auto.conf檔案裡此時CONFIG_DM9000 = y(如果你在.config檔案裡是y),
 該檔案會被頂層makefile所包含(-include include/config/auto.conf)主要是用於子目錄makefile所連結需要,
 如obj-$(CONFIG_DM9000) += xxx;生成的auto.conf檔案與.config檔案有點類似。當然,對伊廠家提供的 廠家_config,
 可以直接把它變成.config檔案,即cp 廠家_config  .config,然後make menuconfig


 當我們編譯的時候用make 或者make uImage ?這時候我們就需要分析makefile檔案了,非常重要的兩個makefile檔案,
 一個是頂層makefile,一個是arch/arm/makefile當我們編譯的時候如果用make的話,則會生成vmlinux,這個是真正的核心。
 而make uImage,會生成uImage,是 uboot能夠識別和解析的核心:也即 頭部+真正的核心(vmlinux).我們在頂層makefile中
 是搜尋不到uImage的,可以搜到vmlinux,但是在arch/arm/makefile下可以搜到uImage的。所以頂層makefile一定是會
 包含arch/arm/makefile的,如include $(srctree)/arch/$(SRCARCH)/Makefile  我們在搜尋 SRCARCH := $(ARCH)  
 當然 我們可以在這寫死 ARCH = arm;否則,你就要寫 make uImage ARCH=arm,意思是使用arch/arm下的makefile,


另外你同樣也要配置編譯器聯結器。。。,同樣寫死CROSS_COMPILE = arm-linux- ;因為後面都會用到
AS= $(CROSS_COMPILE)as
LD= $(CROSS_COMPILE)ld
CC= $(CROSS_COMPILE)gcc
CPP= $(CC) -E
AR= $(CROSS_COMPILE)ar
NM= $(CROSS_COMPILE)nm
STRIP= $(CROSS_COMPILE)strip
OBJCOPY= $(CROSS_COMPILE)objcopy
OBJDUMP= $(CROSS_COMPILE)objdump


如果你不寫死,那麼你編譯的時候就需要 “make uImage ARCH = arm CROSS_COMPILE=arm-linux-” 所以,你開啟頂層makefile
的時候一般需要將這寫死,ARCH?= $(SUBARCH)CROSS_COMPILE?= $(CONFIG_CROSS_COMPILE:"%"=%) 
改成 ARCH = arm  CROSS_COMPILE = arm-linux-
export ARCH SRCARCH CONFIG_SHELL HOSTCC HOSTCFLAGS CROSS_COMPILE AS LD CC 這樣子目錄下的makefile就可以用了
    在頂層makefile裡搜尋all:,出現all: vmlinux,這個是預設下的核心,也是真正的核心,當僅僅用make編譯的時候。
    我們在搜vmlinux:,出現vmlinux: $(vmlinux-lds) $(vmlinux-init) $(vmlinux-main) vmlinux.o $(kallsyms.o) FORCE
vmlinux-init := $(head-y) $(init-y)
vmlinux-main := $(core-y) $(libs-y) $(drivers-y) $(net-y)
vmlinux-all  := $(vmlinux-init) $(vmlinux-main)
vmlinux-lds  := arch/$(SRCARCH)/kernel/vmlinux.lds
export KBUILD_VMLINUX_OBJS := $(vmlinux-all)


  在這裡我們先分析下 vmlinux-init := $(head-y) $(init-y)  head-y:是在arch/arm/makefile裡定義的 
  head-y:= arch/arm/kernel/head$(MMUEXT).o arch/arm/kernel/init_task.o這幾個是最開始呼叫的檔案。 
  init-y := init/, init-y:= $(patsubst %/, %/built-in.o, $(init-y));patsubst是makefile的函式,是模式字串替換
  的函式,即將init目錄下的所涉及到的檔案編譯成 init/built-in.o 如果我們想看到具體的編譯連結規則,
  可以make uImage V=1  我們現在進入arch/arm下的makefile檔案 搜尋uImage:  
  出現zImage Image xipImage bootpImage uImage: vmlinux。即我們的uImage是基於真正的核心vmlinux而來的,
  而vmlinux的編譯是在頂層makefile寫好的。會生成兩個檔案,vmlinux和uImage vmlinux是在頂層目錄下 。
  最終生成的uImage是在arch/arm/boot目錄下


   頂層makefile決定了核心跟目錄下哪些子目錄被編譯進核心,arch/arm/makefile決定了arch/arm目錄下哪些檔案或者
   目錄被編譯進核心了,各級子目錄下的makefile決定所在目錄下哪些檔案將被編譯進核心,哪些檔案將被編譯成模組,
   進入哪些子目錄繼續呼叫他們的makefile。
   
  第一種情況下的makefile(頂層makefile)
   由上:
   vmlinux: $(vmlinux-lds) $(vmlinux-init) $(vmlinux-main) vmlinux.o $(kallsyms.o) FORCE
vmlinux-init := $(head-y) $(init-y)
vmlinux-main := $(core-y) $(libs-y) $(drivers-y) $(net-y)
vmlinux-all  := $(vmlinux-init) $(vmlinux-main)
vmlinux-lds  := arch/$(SRCARCH)/kernel/vmlinux.lds
export KBUILD_VMLINUX_OBJS := $(vmlinux-all)


head-y:= arch/arm/kernel/head$(MMUEXT).o arch/arm/kernel/init_task.o //在arch/arm/makefile 裡定義
init-y := init/  init-y:= $(patsubst %/, %/built-in.o, $(init-y));
drivers-y:= drivers/ sound/ firmware/           drivers-y:= $(patsubst %/, %/built-in.o, $(drivers-y))      
net-y:= net/                                  net-y:= $(patsubst %/, %/built-in.o, $(net-y))
libs-y:= lib/                                libs-y1:= $(patsubst %/, %/lib.a, $(libs-y))       libs-y2:= $(patsubst %/, %/built-in.o, $(libs-y))
core-y:= usr/
core-y+= kernel/ mm/ fs/ ipc/ security/ crypto/ block/    core-y:= $(patsubst %/, %/built-in.o, $(core-y))






可見頂層makefile將原始碼樹下這14個目錄分成5類 init-y, drivers-y,net-y,libs-y,core-y 
只有include目錄,documention目錄,scripts目錄,沒有被編譯進核心,因為他們不含核心原始碼,
arch下各相關的架構的makefile也被編譯進核心了因為arch/arm下的makefile已經被包含進了頂層makefile  
include $(srctree)/arch/$(SRCARCH)/Makefile


所以編譯核心的時候一次進入init-y, drivers-y,net-y,libs-y,core-y所列出來的目錄執行他們目錄下的makefile,
每個子目錄下都會生成built-in.o檔案,lib下會生成built-in.o和lib.a兩個,真正的核心vmlinux就是將這些各子目錄
下的built.o和lib.a檔案連結生成的


   對於第二種情況下的makefile(arch/arm/makefile決定了arch/arm目錄下哪些檔案或者目錄被編譯進核心了)就需
   要頂層makefile中包含的auto.conf檔案了
   -include include/config/auto.conf    //包含了很重要的有.config而make menuconfig生成的auto.conf。 
   auto.conf檔案與.config檔案非常相似,
   
   
   
   
 
   
  //它只是將.config檔案中的註釋去掉,並根據頂層makefile中定義的變數增加一些變數而已
 該檔案主要是給各個子目錄下的makefile使用(第二種情況和第三種情況)
 
core-$(CONFIG_FPE_NWFPE)+= arch/arm/nwfpe/
core-$(CONFIG_FPE_FASTFPE)+= $(FASTFPE_OBJ)
core-$(CONFIG_VFP)+= arch/arm/vfp/


# If we have a machine-specific directory, then include it in the build.
core-y+= arch/arm/kernel/ arch/arm/mm/ arch/arm/common/
core-y+= $(machdirs) $(platdirs)


drivers-$(CONFIG_OPROFILE)      += arch/arm/oprofile/


libs-y:= arch/arm/lib/ $(libs-y)


這些配置項需要auto.conf檔案,同時他們又進一步擴充套件了core-y,libs-y的內容


   對於第三種情況下的makefile(也即各子目錄下的makefile)
   obj-y += a.o c.o  //a.c c.c檔案被編譯進核心,最終和當前目錄下的各子目錄內的built-in.o連結生成當前目錄下的built-in.o檔案,當前目錄下的built-in.o檔案又被它的上一層makefile所使用
   obj-m += b.o      // 將b.c一個檔案編譯成核心模組b.ko
                    // 將a.c b.c c.c三個檔案編譯成核心模組 yangbo.ko
   obj-m += yangbo.o
   yangbo-objs :=a.o b.o c.o
  
  
   
分析一下arch/arm下的目錄結構:
boot目錄: 生成的image zimage uimage等等會放在此目錄內
configs目錄: 預設的一些單板配置檔案,隱形的.config檔案
tools目錄: 有一個mach-types檔案,很重要,定義單板的機器ID
include目錄: 標頭檔案目錄


common, kernel,mm目錄:會被編譯 放在core-y 這三個目錄非常關鍵
lib目錄: 庫檔案 被編譯 放在libs-y
mach-xxx,plat-xxx目錄:根據巨集的定義分別從mach目錄和plat目錄找一個進行編譯,放在core-y


nwfpe目錄:根據定義巨集來是否編譯 若被編譯,放到core-y或者core-m
vfp目錄:  根據定義巨集來是否編譯 若被編譯,放到core-y或者core-m
oprofile目錄: 根據定義巨集來是否編譯 若被編譯,放到drivers-y或者drivers-m






   KCONFIG
   


1. 依據arch/arm/kernel/vmlinux.lds 生成linux核心原始碼根目錄下的vmlinux,這個vmlinux屬於未壓縮,帶除錯資訊、
符號表的最初的核心,大小約23MB; 
arm-linux-gnu-ld -EL  -p --no-undefined -X -o vmlinux -T arch/arm/kernel/vmlinux.lds 


 


arch/arm/kernel/head.o  
arch/arm/kernel/init_task.o   
init/built-in.o  
--start-group   
usr/built-in.o   
arch/arm/kernel/built-in.o   
arch/arm/mm/built-in.o   
arch/arm/common/built-in.o   
arch/arm/mach-s3c2410/built-in.o   
arch/arm/nwfpe/built-in.o   
kernel/built-in.o          
mm/built-in.o   
fs/built-in.o   
ipc/built-in.o   
security/built-in.o   
crypto/built-in.o   
lib/lib.a   
arch/arm/lib/lib.a   
lib/built-in.o   
arch/arm/lib/built-in.o   
drivers/built-in.o   
sound/built-in.o   
net/built-in.o  
--end-group .tmp_kallsyms2.o 
2. 將上面的vmlinux去除除錯資訊、註釋、符號表等內容,生成arch/arm/boot/Image,這是不帶多餘資訊的linux核心,
Image的大小約3.2MB; 
命令:arm-linux-gnu-objcopy -O binary -R .note -R .comment -S  vmlinux arch/arm/boot/Image 


3. 將 arch/arm/boot/Image 用gzip -9 壓縮生成arch/arm/boot/compressed/piggy.gz大小約1.5MB; 
命令:gzip -f -9 < arch/arm/boot/compressed/../Image > arch/arm/boot/compressed/piggy.gz


4. 編譯arch/arm/boot/compressed/piggy.S 生成arch/arm/boot/compressed/piggy.o大小約1.5MB,這裡實際上是
將piggy.gz通過piggy.S編譯進piggy.o檔案中。而piggy.S檔案僅有6行,只是包含了檔案piggy.gz; 
 命令:arm-linux-gnu-gcc -Wp,-MD,arch/arm/boot/compressed/.piggy.o.d  -nostdinc -isystem /home/justin/crosstool/gcc-3.4.5-glibc-2.3.6/arm-linux-gnu/lib/gcc/arm-linux-gnu/3.4.5/include -D__KERNEL__ -Iinclude  -mlittle-endian -D__ASSEMBLY__ -Wa,-L -gdwarf-2 -mapcs-32 -mno-thumb-interwork -D__LINUX_ARM_ARCH__=4 -march=armv4 -mtune=arm9tdmi -msoft-float    -c -o arch/arm/boot/compressed/piggy.o arch/arm/boot/compressed/piggy.S 
 
5. 依據arch/arm/boot/compressed/vmlinux.lds 將arch/arm/boot/compressed/目錄下的檔案head.o 、piggy.o 、misc.o連結
生成 arch/arm/boot/compressed/vmlinux,這個vmlinux是經過壓縮且含有自解壓程式碼的核心,大小約1.5MB; 
命令:arm-linux-gnu-ld -EL   --defsym zreladdr=0x30008000 --defsym params_phys=0x30000100 -p --no-undefined -X /home/justin/crosstool/gcc-3.4.5-glibc-2.3.6/arm-linux-gnu/lib/gcc/arm-linux-gnu/3.4.5/libgcc.a -T arch/arm/boot/compressed/vmlinux.lds arch/arm/boot/compressed/head.o arch/arm/boot/compressed/piggy.o arch/arm/boot/compressed/misc.o -o arch/arm/boot/compressed/vmlinux 


6. 將arch/arm/boot/compressed/vmlinux去除除錯資訊、註釋、符號表等內容,生成arch/arm/boot/zImage大小約1.5MB;
這已經是一個可以使用的linux核心映像檔案了; 
命令:arm-linux-gnu-objcopy -O binary -R .note -R .comment -S  arch/arm/boot/compressed/vmlinux arch/arm/boot/zImage 


7. 將arch/arm/boot/zImage新增64Bytes的相關資訊打包為arch/arm/boot/uImage大小約1.5MB; 
命令:/bin/sh /home/farsight/Resources/kernel/linux-2.6.14/scripts/mkuboot.sh -A arm -O linux -T kernel -C none -a 0x30008000 -e 0x30008000 -n 'Linux-2.6.14' -d arch/arm/boot/zImage arch/arm/boot/uImage   在這裡會用uboot工具mkimage生成uimage
  
小結:真正的生成的核心是vmlinux,但它包含除錯資訊,註釋和各種符號表
這樣,去掉這些東西就是Image,在通過各種壓縮形式,有Image生成zImage
所以一旦執行make,會在頂層目錄下生成vmlinux,在arch/arm/boot目錄下生成image,zimage
若要生成uimage,則需要用zimage來生成 
------------------------------------------------------------------------------------------------------------------------------------------------------------------  
 
 
 
 
 
   
linux核心的啟動過程分析
也分為兩個階段,第一個階段是彙編寫的,第二階段是c語言寫的
先分析第一階段 在arch/arm/kernel/head.S


  ENTRY(stext)
     msr cpsr_c, #PSR_F_BIT|PSR_I_BIT|SVC_MODE   //設定為svc管理模式,靜止中斷
     mrc p15, 0, r9,c0,c0   //讀取協處理器cp15的暫存器c0獲得CPUID,存放在r9暫存器中注意,這裡的cpu主要是
     只cpu核,如arm920t,arm11eb,x86等等     r5暫存器返回一個用來描述這個處理器的結構體的地址 
    即proc_info_list結構
     bl _lookup_processor_type  //呼叫該函式確定核心是否支援該款cpu,如果支援,則r5返回一個描述處理器結構
     的地址,r5=procinfo 否則r5等於0 該函式在arch/arm/kernel/head-common.S中定義的
     movs r10, r5
     beq _error_p  如果r5=0,則報錯         r5暫存器返回一個用來描述這個開發板的結構體的地址 即machine_desc結構
     bl _lookup_machine_type    //呼叫該函式確定核心是否支援該款機器ID 此時我們uboot傳過來的機器ID是放在r1暫存器中,返回值為r5=machinfo,也即描述該款機器結構的地址,
     movs r8,r5               // 如果核心不支援該款機器,則r5=0;
     beq _error_a  // r5=0,則報錯
     
     
     核心用若干個proc_info_list結構來描述不同的cpu,也即這些都是核心所支援的,該結構體被強制定義為
     段屬性為.proc.info.init的結構,在vmlinux.lds中看到,
     __proc_info_begin =.;  //proc_info_list結構的起始地址
     *(.proc.info.init)
     __proc_info_end =.;  //proc_info_list結構的結束地址
     
     struct proc_info_list {


   unsigned int    cpu_val;   // 該成員表示cpu id


   unsigned int    cpu_mask;


   unsigned long      __cpu_mm_mmu_flags;   /* used by head.S */


   unsigned long      __cpu_io_mmu_flags;   /* used by head.S */


   unsigned long      __cpu_flush;    /* used by head.S */


   const char      *arch_name;


   const char      *elf_name;


   unsigned int    elf_hwcap;


   const char      *cpu_name;


   struct processor   *proc;


   struct cpu_tlb_fns *tlb;


   struct cpu_user_fns   *user;


   struct cpu_cache_fns  *cache;


};




     
     我們來分析下_lookup_processor_type函式
     這個函式是來檢查機器型號的,它會讀取你bootloader傳進來的機器ID和他能夠處 理的機器ID進行比較看是否能
     夠處理。核心的ID號定義在arc/arm/tool/mach_types檔案中MACH_TYPE_xxxx巨集定義。
     核心究竟就如何檢查是否是它支援的機器的呢?實際上每個機器都會
     在/arc/arm/mach-xxxx/smdk-xxxx.c檔案中有個描述特定機器的資料結構,如下
     
     MACHINE_START(S3C2440,"SMDK2440")   
       /* Maintainer: Ben Dooks<
[email protected]
> */   
       .phys_io  =S3C2410_PA_UART,   
       .io_pg_offst    = (((u32)S3C24XX_VA_UART) >> 18) & 0xfffc,   
       .boot_params  = S3C2410_SDRAM_PA + 0x100,   
    
       .init_irq   =s3c24xx_init_irq,   
       .map_io          =smdk2440_map_io,   
       .init_machine  = smdk2440_machine_init,   
       .timer           =&s3c24xx_timer,   
MACHINE_END   


   之後展開為
   staticconst struct machine_desc mach_desc_S3C2440     \   
 __used                                             \   
 __attribute__((__section__(".arch.info.init")))= {    \   
       .nr          =MACH_TYPE_S3C2440,          \   
       .name        =”SMDK2440”,};  
        
.phys_io  = S3C2410_PA_UART,   
       .io_pg_offst    = (((u32)S3C24XX_VA_UART) >> 18) & 0xfffc,   
       .boot_params  = S3C2410_SDRAM_PA + 0x100,   
    
       .init_irq   =s3c24xx_init_irq,   
       .map_io          =smdk2440_map_io,   
       .init_machine = smdk2440_machine_init,   
       .timer           =&s3c24xx_timer,     



每個機器都會有一個machine_desc mach_desc結構,核心通過檢查每個machine_desc mach_desc的nr 號和bootloader傳上來
的ID進行比較,如果相同,核心就認為支援該機器,
而且核心在後面的工作中會呼叫該機器的 machine_desc mach_desc_結構中的方法進行一些初始化工作。


在arch/arm/kernel/vmlinux.lds指令碼檔案中


__arch_info_begin = .; //machine_desc結構體的開始地址
*(.arch.info.init)
__arch_info_end =.;   //machine_desc結構體的結束地址
即所有的struct machine_desc結構都被連結進.arch.info.init段內,開始地址為__arch_info_begin,結束地址為__arch_info_end
同樣在arch/arm/kernel/head-common.S中


3: .long .
    .long __arch_info_begin
    .long __arch_info_end
    
    .type __lookup_machine_type,%function
    
 __lookup_machine_type:           
     adr r3,3b            //讀入3處的執行地址也即實體地址給r3 b指back 後面的意思
     ldmia r3, {r4,r5,r6} //r4= 3處的虛擬地址 r5=__arch_info_begin r6=__arch_info_end  r5,r6都是在連結指令碼上定義的虛擬地址,因為此時,mmu並沒有使能,所以要使用實體地址
     sub r3, r3, r4       // r3 = 實體地址和虛擬地址的差值
     add r5, r5, r3       //  r5 = 虛擬地址轉化成實體地址 __arch_info_begin對應的實體地址
     add r6, r6, r3       //  r6 = __arch_info_end對應的實體地址
     
1:  ldr r3,[r5,#MACHINFO_TYPE]  // 此時r3= 第一個machine_desc結構體的nr成員
    teq r3,r1     //比較r3和r1是否相等,r1存放的是uboot傳過來的機器ID
    beq 2f        如果相等,則意味著匹配,執行2處
    add r5, r5, #SIZEOF_MACHINE_DESC //否則r5指向下一個machine_desc結構
    cmp r5, r6    //是否比較完所有的machine_desc結構?
    blo 1b       //沒有則繼續比較,跳到1處執行
    mov r5, #0   //比較完畢,沒有匹配的machine_decs結構,則r5=0;
    
2:  mov pc, lr


 r5將返回__lookup_machine_type函式所確定的machine_desc結構
 
        _
 兩個彙編函式lookup_processor_type和lookup_machine_type 分別用來表示核心是否支援該款CPU ID和板子 ID
 核心把自己所支援的所有CPU id存放在.proc.info.init段內(vmlinux.lds中) 核心把自己所支援的所有單板結構
 放在.proc.info.init段內(vmlinux.lds中)這兩個彙編函式的原理都是一樣的,lookup_processor_type 通過cp15協處理器
 讀取cpu的id存放在r9暫存器中,該函式列舉.proc.info.init段內的某proc_info_list結構的cpu_val成員與r9進行比較
 若相等,則返回該proc_info_list結構的地址 _lookup_machine_type  uboot在臨死之前傳的機器id存放在r1暫存器,
 核心把自己所支援的所有機器結構放在.arch.info.init段內(vmlinux.lds中),該函式列舉段內的
 某machine_desc結構的nr成員與 r1進行比較,若相等,則返回macinene_desc結構的地址
 
 
 
 
 
 
 
 
 
 
 
 
 
 ------------------------------------------------------------------------------------------------------------------------------------------------------------
 
 現在我們來分析核心啟動的第二階段:在init/main.c中start_kernel函式
 
 
 asmlinkage void __init start_kernel(void)
{
char * command_line;
extern const struct kernel_param __start___param[], __stop___param[];


smp_setup_processor_id();


/*
* Need to run as early as possible, to initialize the
* lockdep hash:
*/
lockdep_init();
debug_objects_early_init();


/*
* Set up the the initial canary ASAP:
*/
boot_init_stack_canary();


cgroup_init_early();


local_irq_disable();
early_boot_irqs_disabled = true;


/*
 * Interrupts are still disabled. Do necessary setups, then
 * enable them
 */
tick_init();
boot_cpu_init();
page_address_init();
printk(KERN_NOTICE "%s", linux_banner);     
//列印核心版本資訊,但是此時並沒有列印,此時printk函式只是將列印資訊放在緩衝區中,並沒有列印到控制檯上
        (比如串列埠,lcd屏)上
//因為這個時候控制檯還沒有初始化化,在執行完console_init之後才打印輸出

setup_arch(&command_line);
//同時獲取從uboot傳過來的命令引數放在command_line指標     
//非常重要,主要用來處理uboot傳過來的引數
//setup_arch函式主要目的兩個:第一,解析uboot傳過來的引數,第二,對於machine_desc結構體相關函式的呼叫


mm_init_owner(&init_mm, &init_task);
setup_command_line(command_line);   //重要 拷貝command_line的值放在static_command_line
setup_nr_cpu_ids();
setup_per_cpu_areas();
smp_prepare_boot_cpu();/* arch-specific boot-cpu hooks */


build_all_zonelists(NULL);
page_alloc_init();


printk(KERN_NOTICE "Kernel command line: %s\n", boot_command_line);  /
/列印命令列引數,也即uboot傳過來的命令列引數




parse_early_param();   //或呼叫do_eary_param函式,主要是處理early_param巨集
parse_args("Booting kernel", static_command_line, __start___param,    //parse_args很重要,解析命令列引數,獲取root=xxx的值交給unknown_bootoption來執行,這樣就可以掛接根檔案系統了
  __stop___param - __start___param,
  &unknown_bootoption);               
   
 // __setup巨集和early_param巨集非常相似,定義的都是同一個型別的結構體,放在同一個段裡,
 用parse_early_param函式來解析所有的early_param巨集,用parse_args來解析所有的__setup巨集




/*
* These use large bootmem allocations and must precede
* kmem_cache_init()
*/
pidhash_init();
vfs_caches_init_early();
sort_main_extable();
trap_init();  //異常向量表
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.
*/
sched_init();
/*
* Disable preemption - early bootup scheduling is extremely
* fragile until we cpu_idle() for the first time.
*/
preempt_disable();
if (!irqs_disabled()) {
printk(KERN_WARNING "start_kernel(): bug: interrupts were "
"enabled *very* early, fixing it\n");
local_irq_disable();
}
idr_init_cache();
perf_event_init();
rcu_init();
radix_tree_init();
/* init some links before init_ISA_irqs() */
early_irq_init();
init_IRQ(); //中斷的初始化
prio_tree_init();
init_timers();
hrtimers_init();
softirq_init();
timekeeping_init();
time_init();
profile_init();
if (!irqs_disabled())
printk(KERN_CRIT "start_kernel(): bug: interrupts were "
"enabled early\n");
early_boot_irqs_disabled = false;
local_irq_enable();


/* Interrupts are enabled now so all GFP allocations are safe. */
gfp_allowed_mask = __GFP_BITS_MASK;


kmem_cache_init_late();


/*
* 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.
*/
console_init();              //執行到此處,核心才打印出核心版本資訊。。。
if (panic_later)
panic(panic_later, panic_param);


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:
*/
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
page_cgroup_init();
enable_debug_pagealloc();
debug_objects_mem_init();
kmemleak_init();
setup_per_cpu_pageset();
numa_policy_init();
if (late_time_init)
late_time_init();
sched_clock_init();
calibrate_delay();
pidmap_init();
anon_vma_init();
#ifdef CONFIG_X86
if (efi_enabled)
efi_enter_virtual_mode();
#endif
thread_info_cache_init();
cred_init();
fork_init(totalram_pages);
proc_caches_init();
buffer_init();
key_init();
security_init();
dbg_late_init();
vfs_caches_init(totalram_pages);  //該函式第三處最重要的地方,在記憶體中建立了一顆vfs目錄樹
signals_init();
/* rootfs populating might need page-writeback */
page_writeback_init();
#ifdef CONFIG_PROC_FS
proc_root_init();
#endif
cgroup_init();
cpuset_init();
taskstats_init_early();
delayacct_init();


check_bugs();


acpi_early_init(); /* before LAPIC and SMP init */
sfi_init_late();


ftrace_init();


/* Do the rest non-__init'ed, we're now alive */
rest_init(); // 很重要。
}
















分析setup_arch函式 是在arch/arm/kernel/setup.c中




static struct init_tags {
struct tag_header hdr1;  第一個tag
struct tag_core   core;
struct tag_header hdr2;  第二個tag
struct tag_mem32  mem;
struct tag_header hdr3;  第三個tag
} init_tags __initdata = {
{ tag_size(tag_core), ATAG_CORE },  ATAG_CORE為tag其實設定標記
{ 1, PAGE_SIZE, 0xff },
{ tag_size(tag_mem32), ATAG_MEM },
{ MEM_SIZE, PHYS_OFFSET },
{ 0, ATAG_NONE }   ATAG_NONE為結束標記
};


該核心版本有點老了~ 分析較新的核心
void __init setup_arch(char **cmdline_p)
{
struct tag *tags = (struct tag *)&init_tags;
struct machine_desc *mdesc;
char *from = default_command_line;//通過parse_tags函式中的__tagtable(ATAG_CMDLINE, parse_tag_cmdline);會將命令列字串拷貝到default_command_line,見後


init_tags.mem.start = PHYS_OFFSET;  設定記憶體的起始地址


unwind_init();


setup_processor();   
//處理器相關的設定,它會再次呼叫核心第一階段的lookup_processor_type的函式,
以獲得該處理器的proc_info_list結構




mdesc = setup_machine(machine_arch_type); 
//該函式會再次呼叫核心啟動第一階段的lookup_machine_type函式,根據當前的機器ID確定這款板子machine_desc


machine_desc = mdesc;
machine_name = mdesc->name;


if (mdesc->soft_reboot)
reboot_setup("s");


if (__atags_pointer)
tags = phys_to_virt(__atags_pointer);
else if (mdesc->boot_params) {
#ifdef CONFIG_MMU
/*
* We still are executing with a minimal MMU mapping created
* with the presumption that the machine default for this
* is located in the first MB of RAM.  Anything else will
* fault and silently hang the kernel at this point.
*/
 
if (mdesc->boot_params < PHYS_OFFSET ||    
 //若是定義了啟動引數的地址? 若是設定的啟動引數地址小於記憶體起始地址,但在離記憶體起始地址超過1M的地方
 ,則表示出錯


   mdesc->boot_params >= PHYS_OFFSET + SZ_1M) {
printk(KERN_WARNING
      "Default boot params at physical 0x%08lx out of reach\n",
      mdesc->boot_params);
} else
#endif
{                     // 執行此處
tags = phys_to_virt(mdesc->boot_params);   
//tags 這是地址就是tag列表中的首地址,因為mmu已經使能,所以要將實體地址轉換為虛擬地址
}
}


#if defined(CONFIG_DEPRECATED_PARAM_STRUCT)
/*
* If we have the old style parameters, convert them to
* a tag list.
*/
if (tags->hdr.tag != ATAG_CORE)
convert_to_tag_list(tags);
#endif
if (tags->hdr.tag != ATAG_CORE)
tags = (struct tag *)&init_tags;


if (mdesc->fixup)                             //呼叫mdesc的fixup結構
mdesc->fixup(mdesc, tags, &from, &meminfo);


if (tags->hdr.tag == ATAG_CORE) {
if (meminfo.nr_banks != 0)          //如果在核心中已經定義了meminfo結構
squash_mem_tags(tags);            //則忽略記憶體tag
save_atags(tags);
parse_tags(tags); 
//開始解析和處理每個tag,進過解析命令列引數的時候 from獲得從uboot傳過來的命令列引數的值
}


init_mm.start_code = (unsigned long) _text;
init_mm.end_code   = (unsigned long) _etext;
init_mm.end_data   = (unsigned long) _edata;
init_mm.brk   = (unsigned long) _end;


/* parse_early_param needs a boot_command_line */
strlcpy(boot_command_line, from, COMMAND_LINE_SIZE);  
//所以 from boot_command_line cmd_line值是一樣的,都是從uboot傳過來的命令列引數


/* populate cmd_line too for later use, preserving boot_command_line */
strlcpy(cmd_line, boot_command_line, COMMAND_LINE_SIZE);
*cmdline_p = cmd_line;                  
//此時setup_arch函式將通過cmdling_p這個二級指標獲取uboot傳過來的命令列引數


parse_early_param();  
//解析early_param引數  對於early_param("mem", early_mem); early_param該巨集表示,如果命令列字串中出現mem,就用early_mem函式來處理,見後


arm_memblock_init(&meminfo, mdesc);


paging_init(mdesc);   // 重新初始化頁表,此處paging_init-->devicemaps_init--->mdesc->map_io
request_standard_resources(mdesc);


#ifdef CONFIG_SMP
if (is_smp())
smp_init_cpus();
#endif
reserve_crashkernel();


cpu_init();
tcm_init();


#ifdef CONFIG_MULTI_IRQ_HANDLER
handle_arch_irq = mdesc->handle_irq;
#endif


#ifdef CONFIG_VT
#if defined(CONFIG_VGA_CONSOLE)
conswitchp = &vga_con;
#elif defined(CONFIG_DUMMY_CONSOLE)
conswitchp = &dummy_con;
#endif
#endif
early_trap_init();


if (mdesc->init_early)
mdesc->init_early();
}




 ===============================================新核心==================================================
較新的核心分析:


static struct init_tags {
struct tag_header hdr1;  第一個tag
struct tag_core   core;
struct tag_header hdr2;  第二個tag
struct tag_mem32  mem;
struct tag_header hdr3;  第三個tag

init_tags __initdata = {
{ tag_size(tag_core), ATAG_CORE },  ATAG_CORE為tag其實設定標記
{ 1, PAGE_SIZE, 0xff },
{ tag_size(tag_mem32), ATAG_MEM },
{ MEM_SIZE, PHYS_OFFSET },
{ 0, ATAG_NONE }   ATAG_NONE為結束標記
};


void __init setup_arch(char **cmdline_p)
{
struct tag *tags = (struct tag *)&init_tags;
struct machine_desc *mdesc;
char *from = default_command_line;


unwind_init();


setup_processor();  // 見下  獲取該cpu結構proc_info_list結構
mdesc = setup_machine(machine_arch_type);見下 獲取該單板結構machine_desc
machine_name = mdesc->name;


if (mdesc->soft_reboot)
reboot_setup("s");


if (__atags_pointer)
tags = phys_to_virt(__atags_pointer);
else if (mdesc->boot_params)
tags = phys_to_virt(mdesc->boot_params);  
// 執行 uboot臨終之前傳給核心的啟動引數地址都是以struct tag *形式


/*
* If we have the old style parameters, convert them to
* a tag list.
*/
if (tags->hdr.tag != ATAG_CORE)         不執行
convert_to_tag_list(tags);
if (tags->hdr.tag != ATAG_CORE)         不執行
tags = (struct tag *)&init_tags;


if (mdesc->fixup)  執行
mdesc->fixup(mdesc, tags, &from, &meminfo);


if (tags->hdr.tag == ATAG_CORE) {     執行
if (meminfo.nr_banks != 0)
squash_mem_tags(tags);
save_atags(tags);
parse_tags(tags);  開始解析tag   這個是該函式最重要的函式
}


init_mm.start_code = (unsigned long) _text;
init_mm.end_code   = (unsigned long) _etext;
init_mm.end_data   = (unsigned long) _edata;
init_mm.brk   = (unsigned long) _end;


/* parse_early_param needs a boot_command_line */
strlcpy(boot_command_line, from, COMMAND_LINE_SIZE);


/* populate cmd_line too for later use, preserving boot_command_line */
strlcpy(cmd_line, boot_command_line, COMMAND_LINE_SIZE);
*cmdline_p = cmd_line;


parse_early_param();


paging_init(mdesc);
request_standard_resources(&meminfo, mdesc);


#ifdef CONFIG_SMP
smp_init_cpus();
#endif


cpu_init();
tcm_init();


/*
* Set up various architecture-specific pointers
*/
init_arch_irq = mdesc->init_irq;     
system_timer = mdesc->timer;
init_machine = mdesc->init_machine;


#ifdef CONFIG_VT
#if defined(CONFIG_VGA_CONSOLE)
conswitchp = &vga_con;
#elif defined(CONFIG_DUMMY_CONSOLE)
conswitchp = &dummy_con;
#endif
#endif
early_trap_init();
}








extern struct proc_info_list *lookup_processor_type(unsigned int);    
extern struct machine_desc *lookup_machine_type(unsigned int);


static void __init setup_processor(void)
{
struct proc_info_list *list;


/*
* locate processor in the list of supported processor
* types.  The linker builds this table for us from the
* entries in arch/arm/mm/proc-*.S
*/
list = lookup_processor_type(read_cpuid_id());// 根據cpu id獲得核心所支援的cpu結構proc_info_list
if (!list) {
printk("CPU configuration botched (ID %08x), unable "
      "to continue.\n", read_cpuid_id());
while (1);
}


cpu_name = list->cpu_name;


#ifdef MULTI_CPU
processor = *list->proc;
#endif
#ifdef MULTI_TLB
cpu_tlb = *list->tlb;
#endif
#ifdef MULTI_USER
cpu_user = *list->user;
#endif
#ifdef MULTI_CACHE
cpu_cache = *list->cache;
#endif


printk("CPU: %s [%08x] revision %d (ARMv%s), cr=%08lx\n",
      cpu_name, read_cpuid_id(), read_cpuid_id() & 15,
      proc_arch[cpu_architecture()], cr_alignment);


sprintf(init_utsname()->machine, "%s%c", list->arch_name, ENDIANNESS);
sprintf(elf_platform, "%s%c", list->elf_name, ENDIANNESS);
elf_hwcap = list->elf_hwcap;
#ifndef CONFIG_ARM_THUMB
elf_hwcap &= ~HWCAP_THUMB;
#endif


cacheid_init();
cpu_proc_init();
}


static struct machine_desc * __init setup_machine(unsigned int nr)
{
struct machine_desc *list;


/*
* locate machine in the list of supported machines.
*/
list = lookup_machine_type(nr); // 根據機器id號來獲取該款機器的單板結構machine_desc
if (!list) {
printk("Machine configuration botched (nr %d), unable "
      "to continue.\n", nr);
while (1);
}


printk("Machine: %s\n", list->name);


return list;
}








=================================================================================================================


#define __tag __used __attribute__((__section__(".taglist.init")))   即__tagtable(tag, fn)巨集定義個段屬性為.taglist.init的一個結構體struct tagtable
#define __tagtable(tag, fn) \  
static struct tagtable __tagtable_##fn __tag = { tag, fn }


以下函式是解析每一個TAG
static void __init parse_tags(const struct tag *t)
{
for (; t->hdr.size; t = tag_next(t))
if (!parse_tag(t))
printk(KERN_WARNING
"Ignoring unrecognised tag 0x%08x\n",
t->hdr.tag);
}


static int __init parse_tag(const struct tag *tag)
{
extern struct tagtable __tagtable_begin, __tagtable_end;
struct tagtable *t;


for (t = &__tagtable_begin; t < &__tagtable_end; t++)
if (tag->hdr.tag == t->tag) {
t->parse(tag);
break;
}


return t < &__tagtable_end;
}
 
parse_tags(tags)----->parse_tag()----->__tagtable()
                                            //即為每種TAG定義了相應的處理函式
__tagtable(ATAG_CORE, parse_tag_core);
__tagtable(ATAG_MEM, parse_tag_mem32);     //記憶體TAG
__tagtable(ATAG_VIDEOTEXT, parse_tag_videotext);
__tagtable(ATAG_RAMDISK, parse_tag_ramdisk);
__tagtable(ATAG_SERIAL, parse_tag_serialnr);
__tagtable(ATAG_REVISION, parse_tag_revision);
__tagtable(ATAG_CMDLINE, parse_tag_cmdline);  //命令列TAG    


以上比較重要的兩個,記憶體TAG和命令列TAG
parse_tag_mem32 該函式根據tag定義的起始地址長度和大小,在全域性結構體變數meminfo中增加記憶體的描述資訊,
以後核心就可以通過meminfo瞭解開發板的記憶體資訊
parse_tag_cmdline -> strlcpy(default_command_line, tag->u.cmdline.cmdline, COMMAND_LINE_SIZE); 
它只是簡單的將命令列字串複製到default_command_line中儲存下來,做後續處理














下面看兩個非常重要的巨集__setup(str, fn) 和 early_param(str, fn),他們是在include/linux/init.h中定義
__setup(str,fn) 定義了一個段屬性為 .init.setup的結構體,成員分別是 str,fn,early(為0)


early_param(str,fn) 也同樣定義了一個段屬性為.init.setup的結構體,成員是 str,fn,early(為1)


在連結指令碼vmlinux.lds定義了該段


_setup_start=.;
*(.init.setup)
_setup_end=.;
即所有的__setup(str,fn),early_param(str,fn)兩個巨集所定義的結構體都被連結進這個段中,
我們可以在原始碼中搜索_setup_start _setup_end這兩個地址,以下會被用到


#define __setup_param(str, unique_id, fn, early) \
static const char __setup_str_##unique_id[] __initconst\
__aligned(1) = str; \
static struct obs_kernel_param __setup_##unique_id\
__used __section(.init.setup)\
__attribute__((aligned((sizeof(long)))))\
= { __setup_str_##unique_id, fn, early }


#define __setup(str, fn)\
__setup_param(str, fn, fn, 0)




#define early_param(str, fn)\
__setup_param(str, fn, fn, 1)


struct obs_kernel_param {
const char *str;
int (*setup_func)(char *);
int early;
};


即_setup巨集和early_param巨集定義了一個段屬性為.init.setup的結構體obs_kernel_param








/* Relies on boot_command_line being set */
void __init parse_early_param(void);
void __init parse_early_options(char *cmdline);












                                                                   
 命令列引數中幾個比較重要的_setup巨集  _setup巨集主要定義在init/do_mounts.c和init/main.c中 當我們遇到
 parse_args("Booting kernel", static_command_line, __start___param,__stop___param - __start___param,
&unknown_bootoption)------>unknown_bootoption------->
obsolete_checksetup(命令列引數指標)---->命令列字串中有的,處理所有的_setup巨集
 以下是在init/do_mounts.c檔案中定義的  
 
 static char __initdata saved_root_name[64];  //定義一個saved_root_name的字串陣列
 
 static int __init root_dev_setup(char *line)
{
strlcpy(saved_root_name, line, sizeof(saved_root_name));
return 1;
}


__setup("root=", root_dev_setup);// 將命令列root=xxx中 xxx值賦給saved_root_name




     init/main.c中
static int __init init_setup(char *str)
{
unsigned int i;


execute_command = str;
/*
* In case LILO is going to boot us with default command line,
* it prepends "auto" before the whole cmdline which makes
* the shell think it should execute a script with such name.
* So we ignore all arguments entered _before_ init=... [MJ]
*/
for (i = 1; i < MAX_INIT_ARGS; i++)
argv_init[i] = NULL;
return 1;
}
__setup("init=", init_setup);   命令列引數中出現init=


static int __init rdinit_setup(char *str)
{
unsigned int i;


ramdisk_execute_command = str;
/* See "auto" comment in init_setup */
for (i = 1; i < MAX_INIT_ARGS; i++)
argv_init[i] = NULL;
return 1;
}
__setup("rdinit=", rdinit_setup);   命令列引數中出現rdinit=


那麼是什麼時候會呼叫這個root_dev_setup這個函式指標呢
有之前的分析可以知道,__setup巨集定義個一個段屬性為.init.setup的結構體,我們搜尋_setup_start _setup_end
第一個obsolete_checksetup函式(init/main.c)
static int __init obsolete_checksetup(char *line)  該引數是uboot傳過來的命令列引數的指標
{
const struct obs_kernel_param *p;
int had_early_param = 0;


p = __setup_start;      p所指向的結構是核心儲存的所有的可能設計到的命令列引數
do {
int n = strlen(p->str);
if (!strncmp(line, p->str, n)) {
if (p->early) {                                                 處理early_param巨集
/* Already done in parse_early_param?
* (Needs exact match on param part).
* Keep iterating, as we can have early
* params and __setups of same names 8( */
if (line[n] == '\0' || line[n] == '=')
had_early_param = 1;       
} else if (!p->setup_func) {                            
printk(KERN_WARNING "Parameter %s is obsolete,"
      " ignored\n", p->str);
return 1;
} else if (p->setup_func(line + n))
//在這裡解析了所有__setup巨集定義的結構體,即該結構體包含的函式會被執行,比如__setup("root=", root_dev_setup) __setup("init=", init_setup) ,會依次執行root_dev_setup init_setup函式
//在這裡呼叫了真正的root_dev_setup函式,引數是root=後面的值,這樣我們的saved_root_name就獲得了這個引數值
return 1;
}
p++;
} while (p < __setup_end);


return had_early_param;
}
而obsolete_checksetup函式是被同一個檔案中的unknown_bootoption函式所呼叫,而unknown_bootoption函式
是在start_kernel中被呼叫 parse_args("Booting kernel", static_command_line, __start___param,
  __stop___param - __start___param,
  &unknown_bootoption);  這樣我們在此處就獲得了root=xxx的把值存放在saved_root_name




在arch/arm/kernel/中生成的vmlinux.lds中
OUTPUT_ARCH(arm)
ENTRY(stext)
jiffies = jiffies_64;
SECTIONS
{
 . = 0xC0000000 + 0x00008000;
 .init : { /* Init code and data*/
  _stext = .;
  _sinittext = .;
   *(.head.text)
   *(.init.text) *(.cpuinit.text) *(.meminit.text)
  _einittext = .;
  __proc_info_begin = .;    所有的cpu結構proc_info_list 存放在該段
   *(.proc.info.init)
  __proc_info_end = .;
  
  __arch_info_begin = .;  所有的單板結構machine_desc 存放在該段   巨集MECHINE_START和MACHINE_END結構表示 即所有的MECHINE_START和MACHINE_END巨集定義的全部放在這裡
   *(.arch.info.init)
  __arch_info_end = .;
  
  __tagtable_begin = .;  所有的解析tag結構tag_table存放在該段  用巨集_tagtable來表示該結構 核心所有能涉及到的tag全部儲存在這裡,即所有的_tagtable巨集全部放在這裡
   *(.taglist.init)
  __tagtable_end = .;
  
  . = ALIGN(16);       
  __setup_start = .;    所有的解析命令列引數obs_kernel_param結構體存放在該段  用巨集 _setup和early_param來表示  核心所有能處理的命令列字串全部儲存在這兩個巨集裡,即_setup和early_param巨集定義的全部放在這裡
   *(.init.setup) 
   __setup_end = .;


//小結:parse_tags是解析所有的uboot傳過來的TAG引數 執行所有的__tagtable()
        parse_args是解析命令列引數的  執行所有的__setup()
        parse_early_param是解析early_param(),實際是也會呼叫parse_args函式  




====================================================================================================================================================================


我們繼續分析核心啟動應用程式的流程
start_kernel函式快結束的時候呼叫了rest_init函式(在init/main.c中定義)
stat_kernel
          -->rest_init
                      -->kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);
                                       --->prepare_namespace() 
                                                   -->mount_root()
                                                                                                         
                                               掛接好根檔案系統                      
                                                        ---->init_post()
                                                         run_init_process("/sbin/init");
run_init_process("/etc/init");
run_init_process("/bin/init");
run_init_process("/bin/sh");       //執行應用程式



在核心和 initrd映像被解壓並拷貝到記憶體中之後,核心就會被呼叫了。它會執行不同的初始化操作,最終您會發現
自己到了init/main.c:init()(subdir/file:function)函式中。這個函式執行了大量的子系統初始化操作。此處會執行
一個對init/do_mounts.c:prepare_namespace() 的呼叫,這個函式用來準備名稱空間(掛載 dev 檔案系統、RAID或 md、
裝置以及最後的 initrd)。載入 initrd 是通過呼叫init/do_mounts_initrd.c:initrd_load() 實現的。


initrd_load() 函式呼叫了init/do_mounts_rd.c:rd_load_image(),它通過呼叫init/do_mounts_rd.c:identify_ramdisk_image() 
來確定要載入哪個 RAM磁碟。這個函式會檢查映像檔案的 magic 號來確定它是 minux、etc2、romfs、cramfs 或 gzip 格式。
在返回到initrd_load_image 之前,它還會呼叫 init/do_mounts_rd:crd_load()。這個函式負責為 RAM磁碟分配空間,
並計算迴圈冗餘校驗碼(CRC),然後對 RAM磁碟映像進行解壓,並將其載入到記憶體中。現在,我們在一個適合掛載的
塊裝置中就有了這個 initrd 映像。


現在使用一個 init/do_mounts.c:mount_root()呼叫將這個塊裝置掛載到根檔案系統上。它會建立根裝置,並呼叫 
init/do_mounts.c:mount_block_root()。在這裡呼叫init/do_mounts.c:do_mount_root(),後者又會
呼叫 fs/namespace.c:sys_mount()來真正掛載根檔案系統,然後 chdir 到這個檔案系統中。這就是我們在清單 6 中
所看到的熟悉訊息 VFS: Mounted root(ext2 file system). 的地方。


最後,返回到 init 函式中,並呼叫init/main.c:run_init_process。這會導致呼叫 execve 來啟動 init 程序
(在本例中是/linuxrc)。linuxrc 可以是一個可執行程式,也可以是一個指令碼
  
                                                                                                      
void __init prepare_namespace(void)
{
int is_floppy;


if (root_delay) {
printk(KERN_INFO "Waiting %dsec before mounting root device...\n",
      root_delay);
ssleep(root_delay);
}


/*
* wait for the known devices to complete their probing
*
* Note: this is a potential source of long boot delays.
* For example, it is not atypical to wait 5 seconds here
* for the touchpad of a laptop to initialize.
*/
wait_for_device_probe();


md_run_setup();


if (saved_root_name[0]) {
root_device_name = saved_root_name;   //注意 saved_root_name此時已經有值了
if (!strncmp(root_device_name, "mtd", 3) ||
   !strncmp(root_device_name, "ubi", 3)) {
mount_block_root(root_device_name, root_mountflags);
goto out;
}
ROOT_DEV = name_to_dev_t(root_device_name);   //ROOT_DEV
if (strncmp(root_device_name, "/dev/", 5) == 0)
root_device_name += 5;
}


if (initrd_load())  //載入initrd 初始化記憶體盤檔案系統
goto out;


/* wait for any asynchronous scanning to complete */
if ((ROOT_DEV == 0) && root_wait) {
printk(KERN_INFO "Waiting for root device %s...\n",
saved_root_name);
while (driver_probe_done() != 0 ||
(ROOT_DEV = name_to_dev_t(saved_root_name)) == 0)
msleep(100);
async_synchronize_full();
}


is_floppy = MAJOR(ROOT_DEV) == FLOPPY_MAJOR;


if (is_floppy && rd_doload && rd_load_disk(0))
ROOT_DEV = Root_RAM0;


mount_root();         //掛接根檔案系統
out:
devtmpfs_mount("dev");
sys_mount(".", "/", NULL, MS_MOVE, NULL);
sys_chroot((const char __user __force *)".");
}








掛接根檔案系統之後,開始啟動第一個程序init


static noinline int init_post(void)
{
/* need to finish all async __init code before freeing the memory */
async_synchronize_full();
free_initmem();
mark_rodata_ro();
system_state = SYSTEM_RUNNING;
numa_default_policy();




current->signal->flags |= SIGNAL_UNKILLABLE;


if (ramdisk_execute_command) {       所以如果uboot傳過來的命令列引數有rdinit=xxx,則會執行
run_init_process(ramdisk_execute_command);
printk(KERN_WARNING "Failed to execute %s\n",
ramdisk_execute_command);
}


/*
* We try each of these until one succeeds.
*
* The Bourne shell can be used instead of init if we are
* trying to recover a really broken machine.
*/
if (execute_command) {         所以如果uboot傳過來的命令列引數有init=xxx,則會執行
run_init_process(execute_command);
printk(KERN_WARNING "Failed to execute %s.  Attempting "
"defaults...\n", execute_command);
}
run_init_process("/sbin/init");
//如果uboot傳過來的命令列引數沒有init=xxx或者rdinit=xxx,則會執行該程序,一去不復返,後面的就不會執行了
run_init_process("/etc/init");
run_init_process("/bin/init");
run_init_process("/bin/sh");


panic("No init found.  Try passing init= option to kernel. "
     "See Linux Documentation/init.txt for guidance.");
}
                                                                                  
static int __init rdinit_setup(char *str)
{
unsigned int i;


ramdisk_execute_command = str;         //ramdisk_execute_command獲取了rdinit= xxx 的值
/* See "auto" comment in init_setup */
for (i = 1; i < MAX_INIT_ARGS; i++)
argv_init[i] = NULL;
return 1;
}
__setup("rdinit=", rdinit_setup);


static int __init init_setup(char *str)
{
unsigned int i;


execute_command = str;                 //execute_command獲取了init=xxx的值
/*
* In case LILO is going to boot us with default command line,
* it prepends "auto" before the whole cmdline which makes
* the shell think it should execute a script with such name.
* So we ignore all arguments entered _before_ init=... [MJ]
*/
for (i = 1; i < MAX_INIT_ARGS; i++)
argv_init[i] = NULL;
return 1;
}
__setup("init=", init_setup);








-----------------------------------------------------------------------------------------------------------------
-----------------------------------------------------------------------------------------------------------------
do_basic_setup函式
    分析include/linux/init.h
                    
#define __init__section(.init.text) __cold notrace    即__init巨集
#define __initdata__section(.init.data)
#define __initconst__section(.init.rodata)
#define __exitdata__section(.exit.data)
#define __exit_call__used __section(.exitcall.exit)   後面會用到 該巨集也即定義了“丟棄段”


/* modpost check for section mismatches during the kernel build.
 * A section mismatch happens when there are references from a
 * code or data section to an init section (both code or data).
 * The init sections are (for most archs) discarded by the kernel
 * when early init has completed so all such references are potential bugs.
 * For exit sections the same issue exists.
 * The following markers are used for the cases where the reference to
 * the *init / *exit section (code or data) is valid and will teach
 * modpost not to issue a warning.
 * The markers follow same syntax rules as __init / __initdata. */
#define __ref            __section(.ref.text) noinline
#define __refdata        __section(.ref.data)
#define __refconst       __section(.ref.rodata)


/* compatibility defines */
#define __init_refok     __ref
#define __initdata_refok __refdata
#define __exit_refok     __ref




#ifdef MODULE
#define __exitused
#else
#define __exitused  __used
#endif


#define __exit          __section(.exit.text) __exitused __cold


/* Used for HOTPLUG */
#define __devinit        __section(.devinit.text) __cold
#define __devinitdata    __section(.devinit.data)
#define __devinitconst   __section(.devinit.rodata)
#define __devexit        __section(.devexit.text) __exitused __cold
#define __devexitdata    __section(.devexit.data)
#define __devexitconst   __section(.devexit.rodata)


/* Used for HOTPLUG_CPU */
#define __cpuinit        __section(.cpuinit.text) __cold
#define __cpuinitdata    __section(.cpuinit.data)
#define __cpuinitconst   __section(.cpuinit.rodata)
#define __cpuexit        __section(.cpuexit.text) __exitused __cold
#define __cpuexitdata    __section(.cpuexit.data)
#define __cpuexitconst   __section(.cpuexit.rodata)


/* Used for MEMORY_HOTPLUG */
#define __meminit        __section(.meminit.text) __cold
#define __meminitdata    __section(.meminit.data)
#define __meminitconst   __section(.meminit.rodata)
#define __memexit        __section(.memexit.text) __exitused __cold
#define __memexitdata    __section(.memexit.data)
#define __memexitconst   __section(.memexit.rodata)


/* For assembly routines */
#define __HEAD.section".head.text","ax"
#define __INIT.section".init.text","ax"
#define __FINIT.previous


#define __INITDATA.section".init.data","aw"
#define __INITRODATA.section".init.rodata","a"
#define __FINITDATA.previous


#define __DEVINIT        .section".devinit.text", "ax"
#define __DEVINITDATA    .section".devinit.data", "aw"
#define __DEVINITRODATA  .section".devinit.rodata", "a"


#define __CPUINIT        .section".cpuinit.text", "ax"
#define __CPUINITDATA    .section".cpuinit.data", "aw"
#define __CPUINITRODATA  .section".cpuinit.rodata", "a"


#define __MEMINIT        .section".meminit.text", "ax"
#define __MEMINITDATA    .section".meminit.data", "aw"
#define __MEMINITRODATA  .section".meminit.rodata", "a"


/* silence warnings when references are OK */
#define __REF            .section       ".ref.text", "ax"
#define __REFDATA        .section       ".ref.data", "aw"
#define __REFCONST       .section       ".ref.rodata", "a"


#ifndef __ASSEMBLY__
/*
 * Used for initialization calls..
 */
typedef int (*initcall_t)(void);
typedef void (*exitcall_t)(void);


extern initcall_t __con_initcall_start[], __con_initcall_end[];
extern initcall_t __security_initcall_start[], __security_initcall_end[];


/* Used for contructor calls. */
typedef void (*ctor_fn_t)(void);


/* Defined in init/main.c */
extern int do_one_initcall(initcall_t fn);
extern char __initdata boot_command_line[];
extern char *saved_command_line;
extern unsigned int reset_devices;


/* used by init/main.c */
void setup_arch(char **);
void prepare_namespace(void);


extern void (*late_time_init)(void);


extern int initcall_debug;


#endif


#ifndef MODULE      如果驅動模組靜態編譯進核心
#ifndef __ASSEMBLY__


/* initcalls are now grouped by functionality into separate 
 * subsections. Ordering inside the subsections is determined
 * by link order. 
 * For backwards compatibility, initcall() puts the call in 
 * the device init subsection.
 *
 * The `id' arg to __define_initcall() is needed so that multiple initcalls
 * can point at the same handler without causing duplicate-symbol build errors.
 */
                          
         typedef int (*initcall_t)(void); /*定義函式指標型別*/
         extern initcall_t __initcall_start, __initcall_end; /*申明外部變數,在ld的指令碼檔案中定義*/
         非常重要的巨集  即該巨集定義個段屬性為.initcall" level ".init的一個initcall_t型別的函式指標
         
#define __define_initcall(level,fn,id) \                           
static initcall_t __initcall_##fn##id __used \
__attribute__((__section__(".initcall" level ".init"))) = fn


/*
 * Early initcalls run before initializing SMP.
 *
 * Only for built-in code, not modules.
 */
#define early_initcall(fn)__define_initcall("early",fn,early)


/*
 * A "pure" initcall has no dependencies on anything else, and purely
 * initializes variables that couldn't be statically initialized.
 *
 * This only exists for built-in code, not for modules.
 */
#define pure_initcall(fn)__define_initcall("0",fn,0)


#define core_initcall(fn)__define_initcall("1",fn,1)    定義了一個.initcall1.init的段屬性的函式指標_initcall_##fn##id 即core_initcall定義的函式fn全部放在.initcall1.init段中
#define core_initcall_sync(fn)__define_initcall("1s",fn,1s)
#define postcore_initcall(fn)__define_initcall("2",fn,2)    定義了一個.initcall2.init的段屬性
#define postcore_initcall_sync(fn)__define_initcall("2s",fn,2s)
#define arch_initcall(fn)__define_initcall("3",fn,3) // 重要 arch_initcall(customize_machine); 有的晶片在這個函式中進行裝置的註冊,執行machine_desc->init_machine();
#define arch_initcall_sync(fn)__define_initcall("3s",fn,3s)
#define subsys_initcall(fn)__define_initcall("4",fn,4)
#define subsys_initcall_sync(fn)__define_initcall("4s",fn,4s)
#define fs_initcall(fn)__define_initcall("5",fn,5)
#define fs_initcall_sync(fn)__define_initcall("5s",fn,5s)
#define rootfs_initcall(fn)__define_initcall("rootfs",fn,rootfs) //重要 rootfs_initcall(populate_rootfs)
#define device_initcall(fn)__define_initcall("6",fn,6)   //此處初始化了靜態編譯的驅動模組 優先順序排在第6位


#define device_initcall_sync(fn)__define_initcall("6s",fn,6s)
#define late_initcall(fn)__define_initcall("7",fn,7) //在mtk平臺上mt6575_board.c檔案中定義了late_initcall(board_init); 而board_init---mt6575_board_init 
                                                                      在該函式中對各平臺裝置進行註冊,也即在執行此刻的時候,會執行各platform_driver的probe函式
#define late_initcall_sync(fn)__define_initcall("7s",fn,7s)


#define __initcall(fn) device_initcall(fn)   //此處初始化了靜態編譯的驅動模組




#define __exitcall(fn) \
static exitcall_t __exitcall_##fn __exit_call = fn


#define console_initcall(fn) \
static initcall_t __initcall_##fn \
__used __section(.con_initcall.init) = fn


#define security_initcall(fn) \
static initcall_t __initcall_##fn \
__used __section(.security_initcall.init) = fn


struct obs_kernel_param {
const char *str;
int (*setup_func)(char *);
int early;
};


/*
 * Only for really core code.  See moduleparam.h for the normal way.
 *
 * Force the alignment so the compiler doesn't space elements of the
 * obs_kernel_param "array" too far apart in .init.setup.
 */
#define __setup_param(s