核心的配置和編譯及程式碼分析(一)
阿新 • • 發佈:2019-02-04
關於核心的配置和編譯和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
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<
.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