當(dāng)內(nèi)核映像被加載到RAM之后,Bootloader的控制權(quán)被釋放。內(nèi)核映像并不是可直接運(yùn)行的目標(biāo)代碼,而是一個(gè)壓縮過的zImage(小內(nèi)核)。但是,也并非是zImage映像中的一切均被壓縮了,映像中包含未被壓縮的部分,這部分中包含解壓縮程序,解壓縮程序會解壓縮映像中被壓縮的部分。zImage使用gzip壓縮的,它不僅僅是一個(gè)壓縮文件,而且在這個(gè)文件的開頭部分內(nèi)嵌有g(shù)zip解壓縮代碼。當(dāng)zImage被調(diào)用時(shí)它從arch/arm/boot/compressed/head.S的start匯編例程開始執(zhí)行。這個(gè)例程進(jìn)行一些基本的硬件設(shè)置,并調(diào)用arch/arm/boot/compressed/misc.c中的decompress_kernel()解壓縮內(nèi)核。
arch/arm/kernel/head.S文件是內(nèi)核真正的啟動入口點(diǎn),一般是由解壓縮內(nèi)核的程序來調(diào)用的。首先先看下對于運(yùn)行這個(gè)文件的要求:
MMU = off; D-cache = off; I-cache = 無所謂,開也可以,關(guān)也可以; r0 = 0;r1 = 機(jī)器號;r2 = atags 指針。
這段代碼是位置無關(guān)的,所以,如果以地址0xC0008000來鏈接內(nèi)核,那么就可以直接用__pa(0xc0008000)地址來調(diào)用這里的代碼。
其實(shí),在這個(gè)(linux內(nèi)核中總共有多達(dá)幾十個(gè)的以head.S命名的文件)head.S文件中的一項(xiàng)重要工作就是設(shè)置內(nèi)核的臨時(shí)頁表,不然mmu開起來也玩不轉(zhuǎn),但是內(nèi)核怎么知道如何映射內(nèi)存呢?linux的內(nèi)核將映射到虛地址0xCxxx?xxxx處,但他怎么知道在4GB的地址空間中有哪一片ram是可用的,從而可以映射過去呢??
因?yàn)椴煌南到y(tǒng)有不通的內(nèi)存映像,所以,LINUX約定,要調(diào)用內(nèi)核代碼,一定要滿足上面的調(diào)用要求,以為最初的內(nèi)核代碼提供一些最重要的關(guān)于機(jī)器的信息。內(nèi)核代碼開始的時(shí)候,R1存放的是系統(tǒng)目標(biāo)平臺的代號,對于一些常見的,標(biāo)準(zhǔn)的平臺,內(nèi)核已經(jīng)提供了支持,只要在編譯的時(shí)候選中就行了,例如對X86平臺,內(nèi)核是從物理地址1M開始映射的。
好了好了,看下面的代碼。
ENTRY(stext)是這個(gè)文件的入口點(diǎn)。最初的幾行是這樣的:
setmode? PSR_F_BIT | PSR_I_BIT | SVC_MODE, r9
@ ensure svc mode
@ and irqs disabled
// 設(shè)置為SVC模式,關(guān)閉中斷和快速中斷?
// 此處設(shè)定系統(tǒng)的工作狀態(tài)為SVC,arm有7種狀態(tài)每種狀態(tài)
// 都有自己的堆棧,SVC為管理模式,具有完全的權(quán)限,可以執(zhí)行任意指令
// 訪問任意地址的內(nèi)存
//?setmode是一個(gè)宏,其定義為:
//?.macro setmode, mode, reg
//?msr?? cpsr_c, #\mode
//?.endm
?
mrc?? p15, 0, r9, c0, c0??? @ get processor id
bl __lookup_processor_type???? @ r5=procinfo r9=cpuid
movs? r10, r5??????????? @ invalid processor (r5=0)?
beq?? __error_p?????? @ yes, error 'p'
這幾行是查詢處理器的類型的,我們知道arm系列有很多型號,arm7、arm9、arm11、Cortex核等等類型,這么多型號要如何區(qū)分呢?其實(shí),在arm的15號協(xié)處理器(其實(shí)ARM暫時(shí)也就這么一個(gè)協(xié)處理器)中有一個(gè)只讀寄存器,存放與處理器相關(guān)信息。
__lookup_processor_type是arch/arm/kernel/head-common.S文件中定義的一個(gè)例程,這個(gè)head-common.S用include命令被包含在head.S文件中。其定義為:
__lookup_processor_type:
adr?? r3, 3f
ldmia r3, {r5 - r7}
add?? r3, r3, #8
sub?? r3, r3, r7???????? @ get offset between virt&phys
add?? r5, r5, r3???????? @ convert virt addresses to
add?? r6, r6, r3???????? @ physical address space
1: ldmia r5, {r3, r4}?????? @ value, mask
and?? r4, r4, r9???????? @ mask wanted bits
teq?? r3, r4
beq?? 2f
add?? r5, r5, #PROC_INFO_SZ??? @ sizeof(proc_info_list)
cmp?? r5, r6
blo?? 1b
mov?? r5, #0????????? @ unknown processor
2: mov?? pc, lr
ENDPROC(__lookup_processor_type)
這個(gè)例程接受處理器ID(保存在寄存器r9中)為參數(shù),查找鏈接器建立的支持的處理器表。此時(shí)此刻還不能使用__proc_info表的絕對地址,因?yàn)檫@時(shí)候MMU還沒有開啟,所以此時(shí)運(yùn)行的程序沒有在正確的地址空間中。所以不得不計(jì)算偏移量。若沒有找到processor ID對應(yīng)的處理器,則在r5寄存器中返回返回0,否則返回一個(gè)proc_info_list結(jié)構(gòu)體的指針(在物理地址空間)。proc_info_list結(jié)構(gòu)體在文件中定義:
struct proc_info_list {
unsigned int??? cpu_val;
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;
};
第一項(xiàng)是CPU?id,將與協(xié)處理器中讀出的id作比較,其余的字段也都是與處理器相關(guān)的信息,到下面初始化的過程中自然會用到。
另外,這個(gè)例程加載符地址的代碼也是挺值得我輩學(xué)習(xí)的:
adr?? r3, 3f
加載一個(gè)符號的地址,這個(gè)符號在加載語句前面(下面)定義,forward嘛,這個(gè)符號為3,離這條語句最近的那個(gè)。在那個(gè)符號為3的位置我們看到這樣的代碼:
.align 2
3: .long __proc_info_begin
.long __proc_info_end
4: .long .
.long __arch_info_begin
.long __arch_info_end
搜索這兩個(gè)符號的值,在文件arch/arm/kernel/vmlinux.lds.S中:
__proc_info_begin = .;
*(.proc.info.init)
__proc_info_end = .;
這兩個(gè)符號分別是一種初始化的段的結(jié)束開始地址和結(jié)束地址。為了了解由struct proc_info_list結(jié)構(gòu)體組成的段的實(shí)際構(gòu)成,我們還是得要了解一下在系統(tǒng)中到底都有哪些變量是聲明了要被放到這個(gè)段的。用關(guān)鍵字.proc.info.init來搜,全部都是arch/arm/mm/proc-*.S文件,這些都是特定于處理器的匯編語言文件,對于我們的mini2440, 自然是要看proc-arm920.S文件的,在其中可以看到這些內(nèi)容:
.section ".proc.info.init", #alloc, #execinstr
.type __arm920_proc_info,#object
__arm920_proc_info:
.long 0x41009200
.long 0xff00fff0
.long?? PMD_TYPE_SECT | \
PMD_SECT_BUFFERABLE | \
PMD_SECT_CACHEABLE | \
PMD_BIT4 | \
PMD_SECT_AP_WRITE | \
PMD_SECT_AP_READ
.long?? PMD_TYPE_SECT | \
PMD_BIT4 | \
PMD_SECT_AP_WRITE | \
PMD_SECT_AP_READ
b? __arm920_setup
.long cpu_arch_name
.long cpu_elf_name
.long HWCAP_SWP | HWCAP_HALF | HWCAP_THUMB
.long cpu_arm920_name
.long arm920_processor_functions
.long v4wbi_tlb_fns
.long v4wb_user_fns
#ifndef CONFIG_CPU_DCACHE_WRITETHROUGH
.long arm920_cache_fns
#else
.long v4wt_cache_fns
#endif
.size __arm920_proc_info, . - __arm920_proc_info
看到這兒我們再回國頭去看__lookup_processor_type的代碼:
ldmia r3, {r5 - r7}???????
add?? r3, r3, #8
sub?? r3, r3, r7
盡管符號3處只有兩個(gè)有效值,但它加載了三個(gè)數(shù),而第三個(gè)數(shù),我們看到是這樣定義的:
.long .
__lookup_processor_type中,給r3加上8,也就是讓r3指向“.”的地址,然后用r3減r7來獲取虛擬地址與物理地址的差,這樣看來,“.”就應(yīng)該是虛擬空間(編譯地址)里那個(gè)數(shù)據(jù)的地址。
之后的代碼獲得__proc_info_begin和__arch_info_end這兩個(gè)符號在物理空間中的地址:
add r5, r5, r3??????? @ convert virt addresses to
add r6, r6, r3
然后便是在那個(gè)段中逐個(gè)的檢查struct proc_info_list結(jié)構(gòu)體,以找到與我們的CPU相匹配的:
1: ldmia r5, {r3, r4}?????? @ value, mask
and?? r4, r4, r9???????? @ mask wanted bits
teq?? r3, r4
beq?? 2f
add?? r5, r5, #PROC_INFO_SZ??? @ sizeof(proc_info_list)
cmp?? r5, r6
blo?? 1b
mov?? r5, #0????????? @ unknown processor
2: mov?? pc, lr
__lookup_processor_type例程會返回在文件arch/arm/mm/proc-arm920.S中定義的一個(gè)保存有與我們的處理器相關(guān)的信息的struct proc_info_list結(jié)構(gòu)體的地址。
接下來我們繼續(xù)看stext的代碼:
?? bl __lookup_machine_type??? @ r5=machinfo
movs? r8, r5????????? @ invalid machine (r5=0)?
beq?? __error_a?????? @ yes, error 'a'
在獲得了處理器信息之后,則調(diào)用__lookup_machine_type來查找機(jī)器信息。這個(gè)例程同樣也在arch/arm/kernel/head-common.S文件中定義。這個(gè)例程的定義如下:
__lookup_machine_type:
adr?? r3, 4b
ldmia r3, {r4, r5, r6}
sub?? r3, r3, r4???????? @ get offset between virt&phys
add?? r5, r5, r3???????? @ convert virt addresses to
add?? r6, r6, r3???????? @ physical address space
1: ldr?? r3, [r5, #MACHINFO_TYPE] @ get machine type
teq?? r3, r1????????? @ matches loader number?
beq?? 2f????????? @ found
add?? r5, r5, #SIZEOF_MACHINE_DESC?? @ next machine_desc
cmp?? r5, r6
blo?? 1b
mov?? r5, #0????????? @ unknown machine
2: mov?? pc, lr
ENDPROC(__lookup_machine_type)
處理的過程和上面的__lookup_processor_type還是挺相似的。這個(gè)例程接收r1中傳進(jìn)來的機(jī)器號作為參數(shù),然后,在一個(gè)由struct machine_desc結(jié)構(gòu)體組成的段中查找和我們的機(jī)器號匹配的struct machine_desc結(jié)構(gòu)體,這個(gè)結(jié)構(gòu)體在arch/arm/include/asm/mach/arch.h文件中定義,用于保存機(jī)器的信息:
struct machine_desc {
/*
* Note! The first four elements are used
* by assembler code in head.S, head-common.S
*/
unsigned int??? nr;????? /* architecture number?? */
unsigned int??? phys_io; /* start of physical io? */
unsigned int??? io_pg_offst; /* byte offset for io
* page tabe entry */
const char????? *name;?? /* architecture name? */
unsigned long????? boot_params; /* tagged list???? */
unsigned int??? video_start; /* start of video RAM */
unsigned int??? video_end;? /* end of video RAM?? */
unsigned int??? reserve_lp0 :1; /* never has lp0?? */
unsigned int??? reserve_lp1 :1; /* never has lp1?? */
unsigned int??? reserve_lp2 :1; /* never has lp2?? */
unsigned int??? soft_reboot :1; /* soft reboot???? */
void??????? (*fixup)(struct machine_desc *,
struct tag *, char **,
struct meminfo *);
void??????? (*map_io)(void);/* IO mapping function? */
void??????? (*init_irq)(void);
struct sys_timer?? *timer;???? /* system tick timer? */
void??????? (*init_machine)(void);
};
同樣這個(gè)例程也用到了同上面很相似的方式來獲得符號的地址:
adr?? r3, 4b
b代表back,即向后,這個(gè)符號為4,緊接著我們前面看到的那個(gè)為3的標(biāo)號:
4: .long .
.long __arch_info_begin
.long __arch_info_end
在文件arch/arm/kernel/vmlinux.lds.S中我們可以看到段的定義:
__arch_info_begin = .;
*(.arch.info.init)
__arch_info_end = .;
這兩個(gè)符號也是分別表示某種初始化的段的開始地址和結(jié)束地址。為了找到段的填充內(nèi)容,還是得要了解一下到底都有哪些struct machine_desc結(jié)構(gòu)體類型變量聲明了要被放到這個(gè)段的。用關(guān)鍵字.arch.info.init?來搜索所有的內(nèi)核源文件。在arch/arm/include/asm/mach/arch.h文件中我們看到:
#define MACHINE_START(_type,_name)????? \
static const struct machine_desc __mach_desc_##_type \
__used???????????????????? \
__attribute__((__section__(".arch.info.init"))) = { \
.nr????? = MACH_TYPE_##_type,???? \
.name??? = _name,
#define MACHINE_END??????????? \
};
定義機(jī)器結(jié)構(gòu)體,也就是.arch.info.init段中的內(nèi)容,都是要通過兩個(gè)宏MACHINE_START和MACHINE_END來完成的啊,MACHINE_START宏定義一個(gè)truct machine_desc結(jié)構(gòu)體,并初始化它的機(jī)器號字段和機(jī)器名字段,可以在arch/arm/tools/mach-types文件中看到各種平臺的機(jī)器號的定義。那接著我們來搜MACHINE_START吧,這是一個(gè)用于定義機(jī)器結(jié)構(gòu)體的宏,所以可以看到這個(gè)符號好像都是在arch/arm/mach-*/mach-*.c這樣的文件中出現(xiàn)的,我們感興趣的應(yīng)該是arch/arm/mach-s3c2440/ mach-mini2440.c文件中的這個(gè)符號:
MACHINE_START(MINI2440, "MINI2440")
/* Maintainer: Michel Pollet */
.phys_io = S3C2410_PA_UART,
.io_pg_offst = (((u32)S3C24XX_VA_UART) >> 18) & 0xfffc,
.boot_params = S3C2410_SDRAM_PA + 0x100,
.map_io???? = mini2440_map_io,
.init_machine?? = mini2440_init,
.init_irq = s3c24xx_init_irq,
.timer?? = &s3c24xx_timer,
MACHINE_END
OK,?__lookup_machine_type這個(gè)例程的我們也搞明白了?;貞浺幌拢瑔哟a現(xiàn)在已經(jīng)完成的工作,R10寄存器中為指向proc_info_list結(jié)構(gòu)體的指針(物理地址空間),這個(gè)結(jié)構(gòu)體包含有關(guān)于我們的處理器的一些重要信息。R8寄存器中為指向一個(gè)與我們的平臺相匹配的machine_desc結(jié)構(gòu)體的指針,這個(gè)結(jié)構(gòu)體中保存有一些關(guān)于我們的平臺的重要信息。
回來接著看arch/arm/kernel/head.S文件中的stext:
bl __vet_atags
這個(gè)例程同樣同樣也是在arch/arm/kernel/head-common.S文件中定義:
__vet_atags:
tst?? r2, #0x3??????? @ aligned?
bne?? 1f
ldr?? r5, [r2, #0]?????? @ is first tag ATAG_CORE?
cmp?? r5, #ATAG_CORE_SIZE
cmpne r5, #ATAG_CORE_SIZE_EMPTY
bne?? 1f
ldr?? r5, [r2, #4]
ldr?? r6, =ATAG_CORE
cmp?? r5, r6
bne?? 1f
mov?? pc, lr????????? @ atag pointer is ok
1: mov?? r2, #0
mov?? pc, lr
ENDPROC(__vet_atags)
這個(gè)例程接收機(jī)器信息(R8寄存器)為參數(shù),并檢測r2中傳入的ATAGS 指針的合法性。內(nèi)核使用tag來作為bootloader傳遞內(nèi)核參數(shù)的方式。系統(tǒng)要求r2中傳進(jìn)來的ATAGS指針式4字節(jié)對齊的,同時(shí)要求ATAGS列表的第一個(gè)tag是一個(gè)ATAG_CORE類型的。
此時(shí)R10寄存器中保存有指向CPU信息結(jié)構(gòu)體的指針,R8寄存器中保存有指向機(jī)器結(jié)構(gòu)體的指針,R2寄存器中保存有指向tag表的指針,R9中還保存有CPU ID信息。
回到arch/arm/kernel/head.S文件中的stext,之后就要進(jìn)入初始化過程中比較關(guān)鍵的一步了,開始設(shè)置mmu,但首先要填充一個(gè)臨時(shí)的內(nèi)核頁表,映射4m的內(nèi)存,這在初始化過程中是足夠了:
bl? __create_page_tables
這個(gè)例程設(shè)置初始頁表,這里只設(shè)置最起碼的數(shù)量,只要能使內(nèi)核運(yùn)行即可,r8? = machinfo,r9? = cpuid,r10 = procinfo,在r4寄存器中返回物理頁表地址。
__create_page_tables例程在文件arch/arm/kernel/head.S中定義:
__create_page_tables:
pgtbl r4????????? @ page table address
// pgtbl是一個(gè)宏,本文件的前面部分有定義:
//?.macro pgtbl, rd
//?ldr?? \rd, =(KERNEL_RAM_PADDR - 0x4000)
//?.endm
// KERNEL_RAM_PADDR在本文件的前面有定義,為(PHYS_OFFSET + TEXT_OFFSET)
// PHYS_OFFSET在arch/arm/mach-s3c2410/include/mach/memory.h定義,
// 為UL(0x30000000)
// 而TEXT_OFFSET在arch/arm/Makefile中定義,為內(nèi)核鏡像在內(nèi)存中到內(nèi)存
// 開始位置的偏移(字節(jié)),為$(textofs-y)
// textofs-y也在文件arch/arm/Makefile中定義,
// 為textofs-y?? := 0x00008000
// r4 = 30004000為臨時(shí)頁表的起始地址
// 首先即是初始化16K的頁表,高12位虛擬地址為頁表索引,所以為
// 4K*4 = 16K,大頁表,每一個(gè)頁表項(xiàng),映射1MB虛擬地址。
// 這個(gè)地方還來了個(gè)循環(huán)展開,以優(yōu)化性能。
mov?? r0, r4
mov?? r3, #0
add?? r6, r0, #0x4000
1: str?? r3, [r0], #4
str?? r3, [r0], #4
str?? r3, [r0], #4
str?? r3, [r0], #4
teq?? r0, r6
bne?? 1b
ldr r7, [r10, #PROCINFO_MM_MMUFLAGS] @ mm_mmuflags
// PROCINFO_MM_MMUFLAGS在arch/arm/kernel/asm-offsets.c文件中定義,
// 為DEFINE(PROCINFO_MM_MMUFLAGS,?
// offsetof(struct proc_info_list, __cpu_mm_mmu_flags));
// R10寄存器保存的指針指向是我們前面找到的proc_info_list結(jié)構(gòu)嘛。
// 為內(nèi)核的第一個(gè)MB創(chuàng)建一致的映射,以為打開MMU做準(zhǔn)備,這個(gè)映射將會被
// paging_init()移除,這里使用程序計(jì)數(shù)器來獲得相應(yīng)的段的基地址。
// 這個(gè)地方是直接映射。
mov?? r6, pc
mov?? r6, r6, lsr #20?????? @ start of kernel section
orr?? r3, r7, r6, lsl #20????? @ flags + kernel base
str?? r3, [r4, r6, lsl #2]???? @ identity mapping
// 接下來為內(nèi)核的直接映射區(qū)設(shè)置頁表。KERNEL_START在文件的前面定義,
// 為KERNEL_RAM_VADDR,即內(nèi)核的虛擬地址。
// 而KERNEL_RAM_VADDR在文件的前面定義,則為(PAGE_OFFSET + TEXT_OFFSET)
// 映射完整的內(nèi)核代碼段,初始化數(shù)據(jù)段。
// PAGE_OFFSET為內(nèi)核鏡像開始的虛擬地址,在
// arch/arm/include/asm/memory.h中定義。在配置內(nèi)核時(shí)選定具體值,默認(rèn)
// 為0xC0000000。
// 因?yàn)樽罡?2位的值是頁表中的偏移地址,而第三高的四位必然為0,
// 每個(gè)頁表項(xiàng)為4字節(jié),右移20位之后,還得再左移兩位回來,所以,這里只// 是左移18位。
// R3寄存器在經(jīng)過了上面的操作之后,實(shí)際上是變成了指向內(nèi)核鏡像代碼段
// 的指針(物理地址),在這個(gè)地方,再一次為內(nèi)核鏡像的第一個(gè)MB做了映射。
// R6隨后指向了內(nèi)核鏡像的尾部。R0為頁表項(xiàng)指針。
// 這里以1MB為單位來映射內(nèi)核鏡像。
add?? r0, r4,? #(KERNEL_START & 0xff000000) >> 18
str?? r3, [r0, #(KERNEL_START & 0x00f00000) >> 18]!
ldr?? r6, =(KERNEL_END - 1)
add?? r0, r0, #4
add?? r6, r4, r6, lsr #18??//得到頁表的結(jié)束物理地址
1: cmp?? r0, r6
add?? r3, r3, #1 << 20
strls r3, [r0], #4
bls?? 1b
// 為了使用啟動參數(shù),將物理內(nèi)存的第一MB映射到內(nèi)核虛擬地址空間的
// 第一個(gè)MB,r4存放的是頁表的地址。這里的PAGE_OFFSET的虛擬地址
// 比上面的KERNEL_START要小0x8000
add?? r0, r4, #PAGE_OFFSET >> 18
orr?? r6, r7, #(PHYS_OFFSET & 0xff000000)
.if?? (PHYS_OFFSET & 0x00f00000)
orr?? r6, r6, #(PHYS_OFFSET & 0x00f00000)
.endif
str?? r6, [r0]
// 上面的這個(gè)步驟顯得似乎有些多余。
// 總結(jié)一下,這個(gè)建立臨時(shí)頁表的過程:
// 1、為內(nèi)核鏡像的第一個(gè)MB建立直接映射
// 2、為內(nèi)核鏡像完整的建立從虛擬地址到物理地址的映射
// 3、為物理內(nèi)存的第一個(gè)MB建立到內(nèi)核的虛擬地址空間的第一個(gè)MB的映射。
// OK,內(nèi)核的臨時(shí)頁表建立完畢。整個(gè)初始化臨時(shí)頁表的過程都沒有修改R8,
// R9和R10。
mov?? pc, lr
ENDPROC(__create_page_tables)
回到stext:
ldr?? r13, __switch_data??? @ address to jump to after
@ mmu has been enabled
這個(gè)地方實(shí)際上是在r13中保存了另一個(gè)例程的地址。后面的分析中,遇到執(zhí)行到這個(gè)例程的情況時(shí)會有詳細(xì)說明。
接著看stext:
adr?? lr, BSYM(__enable_mmu)????? @ return (PIC) address
BSYM()是一個(gè)宏,在文件arch/arm/include/asm/unified.h中定義,為:
#define BSYM(sym) sym
也就是說這個(gè)語句也僅僅是把__enable_mmu例程的地址加載進(jìn)lr寄存器中。為了方便之后調(diào)用的函數(shù)返回時(shí),直接執(zhí)行__enable_mmu例程。
接著看stext下一句:
ARM( add?? pc, r10, #PROCINFO_INITFUNC )
ARM()也是一個(gè)宏,同樣在文件arch/arm/include/asm/unified.h中定義,當(dāng)配置內(nèi)核為生成ARM鏡像,則為:#define ARM(x...)? x
所以這一條語句也就是在調(diào)用一個(gè)例程。R10中保存的是procinfo結(jié)構(gòu)的地址。PROCINFO_INITFUNC符號在arch/arm/kernel/asm-offsets.c文件中定義,為:
DEFINE(PROCINFO_INITFUNC, offsetof(struct proc_info_list, __cpu_flush));
也就是調(diào)用結(jié)構(gòu)體proc_info_list的__cpu_flush成員函數(shù)。回去查看arch/arm/mm/proc-arm920.S文件中struct proc_info_list結(jié)構(gòu)體的變量的定義,可以看到這個(gè)成員為:
b? __arm920_setup
也就是說,在設(shè)置好內(nèi)核臨時(shí)頁表之后調(diào)用了例程__arm920_setup,這個(gè)例程同樣在arch/arm/mm/proc-arm920.S中:
__arm920_setup:
mov?? r0, #0
mcr?? p15, 0, r0, c7, c7??? @ invalidate I,D caches on v4
mcr?? p15, 0, r0, c7, c10, 4????? @ drain write buffer on v4
#ifdef CONFIG_MMU
mcr?? p15, 0, r0, c8, c7??? @ invalidate I,D TLBs on v4
#endif
adr?? r5, arm920_crval
ldmia r5, {r5, r6}
mrc?? p15, 0, r0, c1, c0??? @ get control register v4
bic?? r0, r0, r5
orr?? r0, r0, r6
mov?? pc, lr
這一段首先使i,d?caches內(nèi)容無效,然后清除write?buffer,接著使TLB內(nèi)容無效。接下來加載變量arm920_crval的地址,我們看到arm920_crval變量的內(nèi)容為:
rm920_crval:
crval clear=0x00003f3f, mmuset=0x00003135, ucset=0x00001130
crval為一個(gè)宏,在arch/arm/mm/proc-macros.S中定義:
.macro crval, clear, mmuset, ucset
#ifdef CONFIG_MMU
.word \clear
.word \mmuset
#else
.word \clear
.word \ucset
#endif
.endm
其實(shí)也就是定義兩個(gè)變量而已。之后,在r0中,得到了我們想要往協(xié)處理器相應(yīng)寄存器中寫入的內(nèi)容。
之后的?__arm920_setup返回,mov? pc, lr,即是調(diào)用例程__enable_mmu,這個(gè)例程在文件arch/arm/kernel/head.S中:
__enable_mmu:
#ifdef CONFIG_ALIGNMENT_TRAP
orr?? r0, r0, #CR_A
#else
bic?? r0, r0, #CR_A
#endif
#ifdef CONFIG_CPU_DCACHE_DISABLE
bic?? r0, r0, #CR_C
#endif
#ifdef CONFIG_CPU_BPREDICT_DISABLE
bic?? r0, r0, #CR_Z
#endif
#ifdef CONFIG_CPU_ICACHE_DISABLE
bic?? r0, r0, #CR_I
#endif
mov?? r5, #(domain_val(DOMAIN_USER, DOMAIN_MANAGER) | \
domain_val(DOMAIN_KERNEL, DOMAIN_MANAGER) | \
domain_val(DOMAIN_TABLE, DOMAIN_MANAGER) | \
domain_val(DOMAIN_IO, DOMAIN_CLIENT))
mcr?? p15, 0, r5, c3, c0, 0??? @ load domain access register
mcr?? p15, 0, r4, c2, c0, 0??? @ load page table pointer
b? __turn_mmu_on
在這兒設(shè)置了頁目錄地址(r4寄存器中保存),然后設(shè)置domain的保護(hù),在前面建立頁表的例程中,注意到,頁表項(xiàng)的控制信息,是從struct proc_info_list結(jié)構(gòu)體的某字段中取的,其頁目錄項(xiàng)的 domain都是0,domain寄存器中的domain?0對應(yīng)的是0b11,表示訪問模式為manager,不受限制。在這里同時(shí)也完成r0的某些位的進(jìn)一步設(shè)置。
然后,__enable_mmu例程又調(diào)用了__turn_mmu_on,在同一個(gè)文件中定義:
__turn_mmu_on:
mov?? r0, r0
mcr?? p15, 0, r0, c1, c0, 0??? @ write control reg
mrc?? p15, 0, r3, c0, c0, 0??? @ read id reg
mov?? r3, r3
mov?? r3, r13
mov?? pc, r3
ENDPROC(__turn_mmu_on)
接下來寫控制寄存器:
mcr?p15,?0,?r0,?c1,?c0?,0
一切設(shè)置就此生效,到此算是完成了打開d,icache和mmu的工作。
注意:arm的d?cache必須和mmu一起打開,而i?cache可以單獨(dú)打開。其實(shí),cache和mmu的關(guān)系實(shí)在是緊密,每一個(gè)頁表項(xiàng)都有標(biāo)志標(biāo)示是否是cacheable的,可以說本來就是設(shè)計(jì)一起使用的
前面有提到過,r13中存放的其實(shí)是另外一個(gè)例程的地址,其值是變量__switch_data的第一個(gè)字段,即一個(gè)函數(shù)指針的值,__switch_data變量是在arch/arm/kernel/head-common.S中定義的:
__switch_data:
.long __mmap_switched
.long __data_loc???????? @ r4
.long _data?????????? @ r5
.long __bss_start??????? @ r6
.long _end??????????? @ r7
.long processor_id?????? @ r4
.long __machine_arch_type????? @ r5
.long __atags_pointer?????? @ r6
.long cr_alignment?????? @ r7
.long init_thread_union + THREAD_START_SP @ sp
前面的ldr r13 __switch_data,實(shí)際上也就是加載符號__mmap_switched的地址,實(shí)際上__mmap_switched是一個(gè)arch/arm/kernel/head-common.S中定義的例程。接著來看這個(gè)例程的定義,在arch/arm/kernel/head-common.S文件中:
__mmap_switched:
adr?? r3, __switch_data + 4
ldmia r3!, {r4, r5, r6, r7}
cmp?? r4, r5????????? @ Copy data segment if needed
1: cmpne r5, r6
ldrne fp, [r4], #4
strne fp, [r5], #4
bne?? 1b
mov?? fp, #0????????? @ Clear BSS (and zero fp)
1: cmp?? r6, r7
strcc fp, [r6],#4
bcc?? 1b
ldmia r3, {r4, r5, r6, r7, sp}
str?? r9, [r4]??????? @ Save processor ID
str?? r1, [r5]??????? @ Save machine type
str?? r2, [r6]??????? @ Save atags pointer
bic?? r4, r0, #CR_A???????? @ Clear 'A' bit
stmia r7, {r0, r4}?????? @ Save control register values
b? start_kernel
ENDPROC(__mmap_switched)
?
這個(gè)例程完成如下工作:
1、使r3指向__switch_data變量的第二個(gè)字段(從1開始計(jì)數(shù))。
2、執(zhí)行了一條加載指令,也就是在r4, r5, r6, r7寄存器中分別加載4個(gè)符號__data_loc,_data, __bss_start ,_end的地址,這四個(gè)符號都是在鏈接腳本arch/arm/kernel/vmlinux.lds.S中出現(xiàn)的,標(biāo)識了鏡像各個(gè)段的地址,我們應(yīng)該不難猜出他們所代表的段。
3、如果需要的話則復(fù)制數(shù)據(jù)段(數(shù)據(jù)段和BSS段是緊鄰的)。
4、初始化BSS段,全部清零,BSS是未初始化的全局變量區(qū)域。
5、又看到一條加載指令,同樣在一組寄存器中加載借個(gè)符號的地址,r4中為processor_id,r5中為__machine_arch_type, r6中為__atags_pointer, r7中為cr_alignment ,sp中為init_thread_union + THREAD_START_SP。
6、接著我們看到下面的幾條語句,則是用前面獲取的信息來初始化那些全局變量r9,機(jī)器號被保存到processor_id處;r1寄存器的值,機(jī)器號,被保存到變量__machine_arch_type中,其他的也一樣。
7、重新設(shè)置堆棧指針,指向init_task的堆棧。init_task是系統(tǒng)的第一個(gè)任務(wù),init_task的堆棧在task?structure的后8K,我們后面會看到。?
8、最后就要跳到C代碼的 start_kernel。?
?? b? start_kernel
到此為止,匯編部分的初始化代碼就結(jié)束了
O,My God.初始化代碼的匯編部分終于結(jié)束。從而進(jìn)入了與體系結(jié)構(gòu)無關(guān)的Linux內(nèi)核部分。start_kernel()會調(diào)用一系列初始化函數(shù)來設(shè)置中斷,執(zhí)行進(jìn)一步的內(nèi)存配置。
現(xiàn)在讓我們來回憶一下目前的系統(tǒng)狀態(tài):?
臨時(shí)頁表已經(jīng)建立,在0X30004000處,映射了映像文件大小空間,虛地址0XC000000被映射到0X30000000。CACHE,MMU 都已經(jīng)打開。堆棧用的是任務(wù)init_task的堆棧。?
如果以為到了c代碼可以松一口氣的話,就大錯(cuò)特措了,linux的c也不比匯編好懂多少,相反倒掩蓋了匯編的一些和機(jī)器相關(guān)的部分,有時(shí)候更難懂。其實(shí)作 為編寫操作系統(tǒng)的c代碼,只不過是匯編的另一種寫法,和機(jī)器代碼的聯(lián)系是很緊密的。另外,這些start_kernel()中調(diào)用的C函數(shù),每一個(gè)都具有舉足輕重的地位,它們中的許多都肩負(fù)著初始化內(nèi)核中的某個(gè)子系統(tǒng)的重要使命,而Linux內(nèi)核中每一個(gè)子系統(tǒng)都錯(cuò)綜復(fù)雜,牽涉到各種軟件、硬件的復(fù)雜算法,所以理解起來倒真的是挺困難的。?
start_kernel函數(shù)在?init/main.c中定義:
528 asmlinkage void __init start_kernel(void)
529 {
530?? char * command_line;
531?? extern struct kernel_param __start___param[], __stop___param[];
532
533?? smp_setup_processor_id();
534
535?? /*
536??? * Need to run as early as possible, to initialize the
537??? * lockdep hash:
538??? */
539?? lockdep_init();
540?? debug_objects_early_init();
541
542?? /*
543??? * Set up the the initial canary ASAP:
544??? */
545?? boot_init_stack_canary();
546
547?? cgroup_init_early();
548
549?? local_irq_disable();
550?? early_boot_irqs_off();
551?? early_init_irq_lock_class();
552
553 /*
554? * Interrupts are still disabled. Do necessary setups, then
555? * enable them
556? */
557?? lock_kernel();
558?? tick_init();
559?? boot_cpu_init();
560?? page_address_init();
561?? printk(KERN_NOTICE "%s", linux_banner);
562?? setup_arch(&command_line);
563?? mm_init_owner(&init_mm, &init_task);
564?? setup_command_line(command_line);
565?? setup_nr_cpu_ids();
566?? setup_per_cpu_areas();
567?? smp_prepare_boot_cpu(); /* arch-specific boot-cpu hooks */
568
569?? build_all_zonelists();
570?? page_alloc_init();
571
572 printk(KERN_NOTICE "Kernel command line: %s\n", boot_command_line);
573?? parse_early_param();
574 ??parse_args("Booting kernel", static_command_line,
575????????????? __start___param, __stop___param - __start___param,
576????????????? &unknown_bootoption);
577?? /*
578??? * These use large bootmem allocations and must precede
579??? * kmem_cache_init()
580??? */
581?? pidhash_init();
582?? vfs_caches_init_early();
583?? sort_main_extable();
584?? trap_init();
585?? mm_init();
586?? /*
587??? * Set up the scheduler prior starting any interrupts (such as the
588??? * timer interrupt). Full topology setup happens at smp_init()
589??? * time - but meanwhile we still have a functioning scheduler.
590?? */
591?? sched_init();
592?? /*
593??? * Disable preemption - early bootup scheduling is extremely
594??? * fragile until we cpu_idle() for the first time.
595??? */
596?? preempt_disable();
597?? if (!irqs_disabled()) {
598??????? printk(KERN_WARNING "start_kernel(): bug: interrupts were "
599 ???????????????????????????"enabled *very* early, fixing it\n");
600??????? local_irq_disable();
601?? }
602?? rcu_init();
603?? radix_tree_init();
604?? /* init some links before init_ISA_irqs() */
605?? early_irq_init();
606?? init_IRQ();
607?? prio_tree_init();
608?? init_timers();
609?? hrtimers_init();
610?? softirq_init();
611?? timekeeping_init();
612?? time_init();
613?? profile_init();
614?? if (!irqs_disabled())
615?????????? printk(KERN_CRIT "start_kernel(): bug: interrupts were "
616??????????????????????????? "enabled early\n");
617?? early_boot_irqs_on();
618?? local_irq_enable();
619
620?? /* Interrupts are enabled now so all GFP allocations are safe. */
621?? gfp_allowed_mask = __GFP_BITS_MASK;
622
623?? kmem_cache_init_late();
624
625?? /*
626??? * HACK ALERT! This is early. We're enabling the console before
627??? * we've done PCI setups etc, and console_init() must be aware of
628??? * this. But we do want output early, in case something goes wrong.
629??? */
630?? console_init();
631?? if (panic_later)
632?????????? panic(panic_later, panic_param);
633
634?? lockdep_info();
635
636?? /*
637??? * Need to run this when irqs are enabled, because it wants
638??? * to self-test [hard/soft]-irqs on/off lock inversion bugs
639??? * too:
640??? */
641?? locking_selftest();
642
643 #ifdef CONFIG_BLK_DEV_INITRD
644?? if (initrd_start && !initrd_below_start_ok &&
645?? page_to_pfn(virt_to_page((void *)initrd_start)) < min_low_pfn) {
646?? printk(KERN_CRIT "initrd overwritten (0x%08lx < 0x%08lx) - "
647?????????????? "disabling it.\n",
648?????????????? page_to_pfn(virt_to_page((void *)initrd_start)),
649?????????????? min_low_pfn);
650?????????????? initrd_start = 0;
651?? }
652 #endif
653?? page_cgroup_init();
654?? enable_debug_pagealloc();
655?? kmemtrace_init();
656?? kmemleak_init();
657?? debug_objects_mem_init();
658?? idr_init_cache();
659?? setup_per_cpu_pageset();
660?? numa_policy_init();
661?? if (late_time_init)
662?? late_time_init();
663?? sched_clock_init();
664?? calibrate_delay();
665?? pidmap_init();
666?? anon_vma_init();
667 #ifdef CONFIG_X86
668?? if (efi_enabled)
669?????????? efi_enter_virtual_mode();
670 #endif
671?? thread_info_cache_init();
672?? cred_init();
673?? fork_init(totalram_pages);
674?? proc_caches_init();
675?? buffer_init();
676?? key_init();
677?? security_init();
678?? vfs_caches_init(totalram_pages);
679?? signals_init();
680?? /* rootfs populating might need page-writeback */
681?? page_writeback_init();
682 #ifdef CONFIG_PROC_FS
683?? proc_root_init();
684 #endif
685?? cgroup_init();
686?? cpuset_init();
687?? taskstats_init_early();
688?? delayacct_init();
689
690?? check_bugs();
691
692?? acpi_early_init(); /* before LAPIC and SMP init */
693?? sfi_init_late();
694
695?? ftrace_init();
696
697?? /* Do the rest non-__init'ed, we're now alive */
698?? rest_init();
699 }
接著我們來近距離的觀察一下start_kernel函數(shù)中調(diào)用的這些重量級的函數(shù)。
首先來看setup_arch(&command_line)函數(shù),這個(gè)函數(shù)(對于我們的mini2440平臺來說)在arch/arm/kernel/setup.c中定義:
664 void __init setup_arch(char **cmdline_p)
665 {
666?? struct tag *tags = (struct tag *)&init_tags;
667?? struct machine_desc *mdesc;
668?? char *from = default_command_line;
669
670?? unwind_init();
671
672?? setup_processor();
673?? mdesc = setup_machine(machine_arch_type);
674?? machine_name = mdesc->name;
675
676?? if (mdesc->soft_reboot)
677????????? reboot_setup("s");
678
679?? if (__atags_pointer)
680?????????? tags = phys_to_virt(__atags_pointer);
681?? else if (mdesc->boot_params)
682?????????? tags = phys_to_virt(mdesc->boot_params);
683
684?? /*
685??? * If we have the old style parameters, convert them to
686??? * a tag list.
687??? */
688?? if (tags->hdr.tag != ATAG_CORE)
689?????????? convert_to_tag_list(tags);
690?? if (tags->hdr.tag != ATAG_CORE)
691?????????? tags = (struct tag *)&init_tags;
692
693?? if (mdesc->fixup)
694?????????? mdesc->fixup(mdesc, tags, &from, &meminfo);
695
696?? if (tags->hdr.tag == ATAG_CORE) {
697?????????? if (meminfo.nr_banks != 0)
698?????????????????? squash_mem_tags(tags);
699????????? save_atags(tags);
700????????? parse_tags(tags);
701?? }
702
703?? init_mm.start_code = (unsigned long) _text;
704?? init_mm.end_code?? = (unsigned long) _etext;
705?? init_mm.end_data?? = (unsigned long) _edata;
706?? init_mm.brk??????? = (unsigned long) _end;
707
708?? /* parse_early_param needs a boot_command_line */
709?? strlcpy(boot_command_line, from, COMMAND_LINE_SIZE);
710
711?? /* populate cmd_line too for later use, preserving boot_command_line */
712?? strlcpy(cmd_line, boot_command_line, COMMAND_LINE_SIZE);
713?? *cmdline_p = cmd_line;
714
715?? parse_early_param();
716
717?? paging_init(mdesc);
718?? request_standard_resources(&meminfo, mdesc);
719
720 #ifdef CONFIG_SMP
721 ??smp_init_cpus();
722 #endif
723
724?? cpu_init();
725?? tcm_init();
726
727?? /*
728??? * Set up various architecture-specific pointers
729??? */
730?? init_arch_irq = mdesc->init_irq;
731?? system_timer = mdesc->timer;
732?? init_machine = mdesc->init_machine;
733
734 #ifdef CONFIG_VT
735 #if defined(CONFIG_VGA_CONSOLE)
736?? conswitchp = &vga_con;
737 #elif defined(CONFIG_DUMMY_CONSOLE)
738?? conswitchp = &dummy_con;
739 #endif
740 #endif
741?? early_trap_init();
742 }
來看一些我們比較感興趣的地方:
1、666行,struct tag指針類型的局部變量指向了默認(rèn)的tag列表init_tags,該靜態(tài)變量在setup_arch()定義同文件的前面有如下定義:
636 /*
637? * This holds our defaults.
638? */
639 static struct init_tags {
640???????? struct tag_header hdr1;
641???????? struct tag_core?? core;
642??????? ?struct tag_header hdr2;
643???????? struct tag_mem32? mem;
644???????? struct tag_header hdr3;
645 } init_tags __initdata = {
646???????? { tag_size(tag_core), ATAG_CORE },
647???????? { 1, PAGE_SIZE, 0xff },
648???????? { tag_size(tag_mem32), ATAG_MEM },
649???????? { MEM_SIZE, PHYS_OFFSET },
650???????? { 0, ATAG_NONE }
651 };
第679行檢察__atags_pointer指針的有效性,這個(gè)指針是在前面,跳轉(zhuǎn)到start_kernel函數(shù)的匯編例程最后設(shè)置的幾個(gè)變量之一,用的是R2寄存器的值。如果bootloader通過R2傳遞了tag列表的話,自然是要使用bootloader穿的進(jìn)來的tag列表的。
2、第688行的字符指針類型的局部變量from指向了default_command_line靜態(tài)變量,這個(gè)變量同樣在前面有定義:
124 static char default_command_line[COMMAND_LINE_SIZE] __initdata = CONFIG_CMDLINE;
傳遞給內(nèi)核的命令行參數(shù),是可以在內(nèi)核配置的時(shí)候設(shè)置的。
3、第673行以machine_arch_type為參數(shù)調(diào)用了setup_machine()函數(shù),而這個(gè)函數(shù)的定義為:
369 static struct machine_desc * __init setup_machine(unsigned int nr)
370 {
371???????? struct machine_desc *list;
372
373???????? /*
374????????? * locate machine in the list of supported machines.
375????????? */
376???????? list = lookup_machine_type(nr);
377???????? if (!list) {
378???????????????? printk("Machine configuration botched (nr %d), "
379??????????????????????? " unable to continue.\n", nr);
380???????????????? while (1);
381???????? }
382
383???????? printk("Machine: %s\n", list->name);
384
385???????? return list;
386 }
在arch/arm/kernel/head-common.S文件中,我們看到了一個(gè)對于__lookup_machine_type例程的封裝的可被C語言程序調(diào)用的匯編語言編寫的函數(shù)lookup_machine_type(),接收機(jī)器號,查表,然后返回匹配的struct machine_desc結(jié)構(gòu)體的指針。在這里,對于我們的mini2440,返回的自然是arch/arm/mach-s3c2440/ mach-mini2440.c文件中定義的結(jié)構(gòu)體了:
MACHINE_START(MINI2440, "MINI2440")
/* Maintainer: Michel Pollet */
.phys_io = S3C2410_PA_UART,
.io_pg_offst = (((u32)S3C24XX_VA_UART) >> 18) & 0xfffc,
.boot_params = S3C2410_SDRAM_PA + 0x100,
.map_io???? = mini2440_map_io,
.init_machine?? = mini2440_init,
.init_irq = s3c24xx_init_irq,
.timer?? = &s3c24xx_timer,
MACHINE_END
然后,machine_desc結(jié)構(gòu)體的name成員的值被賦給全局變量machine_name。
第681行,若bootloader沒有傳遞tag列表給內(nèi)核,則檢測machine_desc結(jié)構(gòu)體的boot_params字段,看看特定的平臺是否傳遞了標(biāo)記列表。
第730、731、732行分別將machine_desc結(jié)構(gòu)體的init_irq、timer和init_machine成員值賦給了三個(gè)全局變量init_arch_irq、system_timer和init_machine,即是設(shè)置特定體系結(jié)構(gòu)的指針。初始化的后面階段自然會用到。
start_kernel()函數(shù)調(diào)用同文件下的rest_init(void)函數(shù),rest_init(void)函數(shù)調(diào)用 kernel_thread()函數(shù)以啟動第一個(gè)核心線程,該線程執(zhí)行kernel_init()函數(shù),而原執(zhí)行序列會調(diào)用cpu_idle(),等待調(diào)度。
作為核心線程的kernel_init()函數(shù)繼續(xù)完成一些設(shè)置,并在最后調(diào)用同文件下的init_post()函數(shù),而該函數(shù)掛在根文件系統(tǒng),打開/dev/console設(shè)備,重定向stdin、stdout和stderr到控制臺。之后,它搜索文件系統(tǒng)中的init程序(也可以由“init=”命令行參數(shù)指定init程序),并使用run_init_process()函數(shù)執(zhí)行init程序。(事實(shí)上,run_init_process()函數(shù)又調(diào)用了kernel_execve()來實(shí)際執(zhí)行程序)。搜索init程序的順序?yàn)?sbin/init、/etc/init、/bin/init、和/bin/sh。在嵌入式系統(tǒng)中,多數(shù)情況下,可以給內(nèi)核傳入一個(gè)簡單的shell腳本來啟動必需的嵌入式應(yīng)用程序。
至此,漫長的Linux內(nèi)核引導(dǎo)和啟動過程就結(jié)束了,而kernel_init()對應(yīng)的由rest_init(void)函數(shù)創(chuàng)建的第一個(gè)線程也進(jìn)入用戶模式。
?
評論