S3C2410 Linux 2.6.35.7啟動分析
1. 依據arch/arm/kernel/vmlinux.lds 生成linux核心源碼根目錄下的vmlinux,這個vmlinux屬于未壓縮,帶調試資訊、符号表的最初的核心,大小約23MB;
指令:arm-linux-gnu-ld -o vmlinux -T arch/arm/kernel/vmlinux.lds
arch/arm/kernel/head.o
init/built-in.o
--start-group
arch/arm/mach-s3c2410/built-in.o
kernel/built-in.o
mm/built-in.o
fs/built-in.o
ipc/built-in.o
drivers/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 -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 -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 zreladdr=0x30008000 params_phys=0x30000100 -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 -S arch/arm/boot/compressed/vmlinux arch/arm/boot/zImage
7. 将arch/arm/boot/zImage添加64Bytes的相關資訊打包為arch/arm/boot/uImage大小約1.5MB;
指令: ./mkimage -A arm -O linux -T kernel -C none -a 0x30008000 -e 0x30008000 -n 'Linux-2.6.35.7' -d arch/arm/boot/zImage arch/arm/boot/uImage
核心啟動分析:
本文着重分析S3C2410 linux-2.6.35.7 核心啟動的詳細過程,主要包括: zImage 解壓縮階段、 vmlinux 啟動彙編階段、 startkernel 到建立第一個程序階段三個部分,一般将其稱為 linux 核心啟動一、二、三階段,本文也将采用這種表達方式。對于 zImage 之前的啟動過程,本文不做表述,可參考前面正亮講得 “ u-boot的啟動過程分析”。
本文中涉及到的術語約定如下:
基本核心映像:即核心編譯過程中最終在核心源代碼根目錄下生成的 vmlinux 映像檔案,并不包含任何核心解壓縮和重定位代碼;
zImage 核心映像:包含了核心piggy.o及解壓縮和重定位代碼,通常是目标闆 bootloader 加載的對象;
zImage 下載下傳位址:即 bootloader 将 zImage 下載下傳到目标闆記憶體的某個位址或者 nand read 将 zImage 讀到記憶體的某個位址;
zImage 加載位址:由 Linux 的 bootloader 完成的将 zImage 搬移到目标闆記憶體的某個位置所對應的位址值,預設值 0x30008000 。
1、 Linux 核心啟動第一階段:核心解壓縮和重定位
該階段是從 u-boot 引導進入核心執行的第一階段,我們知道 u-boot 引導核心啟動的最後一步是:通過一個函數指針 thekernel()帶三個參數跳轉到核心( zImage )入口點開始執行,此時, u-boot 的任務已經完成,控制權完全交給核心( zImage )。
稍作解釋,在 u-boot 的檔案arch\arm\lib\bootm.c(uboot-2010.9)中定義了 thekernel, 并在 do_bootm_linux 的最後執行 thekernel.
定義如下:void (*theKernel)(int zero, int arch, uint params);
theKernel = (void (*)(int, int, uint))ntohl(hdr->ih_ep);
//hdr->ih_ep----Entry Point Address uImage 中指定的核心入口點,這裡是 0x30008000 。
theKernel (0, bd->bi_arch_number, bd->bi_boot_params);
其中第二個參數為機器 ID, 第三參數為 u-boot 傳遞給核心參數存放在記憶體中的首位址,此處是 0x30000100 。
由上述 zImage 的生成過程我們可以知道,第一階段運作的核心映像實際就是arch/arm/boot/compressed/vmlinux,而這一階段所涉及的檔案也隻有三個:
(1)arch/arm/boot/compressed/vmlinux.lds
(2)arch/arm/boot/compressed/head.S
(3)arch/arm/boot/compressed/misc.c
下面的圖是使用64MRAM時,通常的記憶體分布圖:
下面我們的分析集中在 arch/arm/boot/compressed/head.S, 适當參考 vmlinux.lds 。
從linux/arch/arm/boot/compressed/vmlinux.lds檔案可以看出head.S的入口位址為ENTRY(_start),也就是head.S彙編檔案的_start标号開始的第一條指令。
下面從head.S中得_start 标号開始分析。(有些指令不影響初始化,暫時略去不分析)
代碼位置在/arch/arm/boot/compressed/head.S中:
start:
.type start,#function
.rept 8
mov r0, r0
.endr
b 1f @ 跳轉到後面的标号1處
.word 0x016f2818 @ 輔助引導程式的幻數,用來判斷鏡像是否是zImage
.word start @ 加載運作zImage的絕對位址,start表示賦的初值
.word _edata @ zImage結尾位址,_edata是在vmlinux.lds.S中定義的,表示init,text,data三個段的結束位置
1: mov r7, r1 @ save architecture ID 儲存體系結構ID 用r1儲存
mov r8, r2 @ save atags pointer 儲存r2寄存器 參數清單,r0始終為0
mrs r2, cpsr @ get current mode 得到目前模式
tst r2, #3 @ not user?,tst實際上是相與,判斷是否處于使用者模式
bne not_angel @ 如果不是處于使用者模式,就跳轉到not_angel标号處
mov r0, #0x17 @ angel_SWIreason_EnterSVC,向SWI中傳遞參數
swi 0x123456 @ angel_SWI_ARM這個是讓使用者空間進入SVC空間
not_angel:
mrs r2, cpsr @ turn off interrupts to 讀出cpsr寄存器的值放到r2中
orr r2, r2, #0xc0 @ prevent angel from running關閉中斷
msr cpsr_c, r2 @ 把r2的值從新寫回到cpsr中
.text
adr r0, LC0
ldmia r0, {r1, r2, r3, r4, r5, r6, ip, sp}
@r0是運作時位址,而r1則是連結時位址,而它們兩都是表示LC0表的起始位置,這樣他們兩的差則是運作和連結的偏移量,糾正了這個偏移量才可以運作與”位址相關的代碼“
subs r0, r0, r1 @ calculate the delta offset 計算偏移量,并放入r0中
beq not_relocated @ if delta is zero, we are running at the address we were linked at.
@ 如果為0,則不用重定位了,直接跳轉到标号not_relocated處執行
add r5, r5, r0
add r6, r6, r0
add ip, ip, r0
add r2, r2, r0
add r3, r3, r0
add sp, sp, r0
1: ldr r1, [r6, #0] @ relocate entries in the GOT
add r1, r1, r0 @ table. This fixes up the
str r1, [r6], #4 @ C references.
cmp r6, ip
blo 1b
not_relocated: mov r0, #0
1: str r0, [r2], #4 @ clear bss 清除bss段
str r0, [r2], #4
str r0, [r2], #4
str r0, [r2], #4
cmp r2, r3
blo 1b
bl cache_on
@ 這裡的 r1,r2 之間的空間為解壓縮核心程式所使用,也是傳遞給 decompress_kernel 的第二和第三的參數
mov r1, sp @ malloc space above stack
add r2, sp, #0x10000 @ 64k max解壓縮的緩沖區
@下面程式的意義就是保證解壓位址和目前程式的位址不重疊。上面配置設定了64KB的空間來做解壓時的資料緩存。
(2)zImage 的起始位址大于 vmlinux 的目标起始位址加上 vmlinux 大小( 4M )的位址,是以将 zImage 直接解壓到 vmlinux 的目标位址
add r0, r4, #4096*1024 @ 4MB largest kernel size
cmp r0, r5
bls wont_overwrite
@ 前兩種方案通常都不成立,不會跳轉到wont_overwrite标号處,會繼續走如下分支,其解壓後的記憶體配置設定示意圖如下:
mov r5, r2 @ decompress after malloc space
mov r0, r5
mov r3, r7
bl decompress_kernel
@ decompress_kernel共有4個參數,解壓的核心位址、緩存區首位址、緩存區尾位址、和晶片ID,傳回解壓縮代碼的長度。
decompress_kernel(ulg output_start, ulg free_mem_ptr_p, ulg free_mem_ptr_end_p,
int arch_id)
{
output_data = (uch *)output_start;
free_mem_ptr = free_mem_ptr_p;
free_mem_ptr_end = free_mem_ptr_end_p;
__machine_arch_type = arch_id;
arch_decomp_setup();
makecrc();
putstr("Uncompressing Linux...");
gunzip();
putstr(" done, booting the kernel.\n");
return output_ptr;
}
add r0, r0, #127 + 128
bic r0, r0, #127 @ align the kernel length對齊核心長度
@ 完成了解壓縮之後,由于核心沒有解壓到正确的位址,最後必須通過代碼搬移來搬到指定的位址0x30008000。搬運過程中有
@ 可能會覆寫掉現在運作的重定位代碼,是以必須将這段代碼搬運到安全的地方,
@ 這裡搬運到的位址是解壓縮了的代碼的後面r5+r0的位置。
add r1, r5, r0 @ end of decompressed kernel 解壓核心的結束位址
adr r2, reloc_start
ldr r3, LC1 @ LC1: .word reloc_end - reloc_start 表示reloc_start段代碼的大小
add r3, r2, r3
1: ldmia r2!, {r9 - r14} @ copy relocation code
stmia r1!, {r9 - r14}
ldmia r2!, {r9 - r14}
stmia r1!, {r9 - r14}
cmp r2, r3
blo 1b
bl cache_clean_flush @清 cache
ARM(add pc, r5, r0) @ call relocation code 跳轉到重定位代碼開始執行
@ 在此處會調用重定位代碼reloc_start來将Image 的代碼從緩沖區r5幫運到最終的目的地r4:0x30008000處
reloc_start: add r9, r5, r0 @r9中存放的是臨時解壓核心的末尾位址
sub r9, r9, #128 @ 不拷貝堆棧
mov r1, r4 @r1中存放的是目的位址0x30008000
1:
.rept 4
ldmia r5!, {r0, r2, r3, r10 - r14} @ relocate kernel
stmia r1!, {r0, r2, r3, r10 - r14}
.endr
cmp r5, r9
blo 1b
mov sp, r1
add sp, sp, #128 @ relocate the stack
call_kernel: bl cache_clean_flush @清除cache
bl cache_off @關閉cache
mov r0, #0 @ must be zero
mov r1, r7 @ restore architecture number
mov r2, r8 @ restore atags pointer
@ 這裡就是最終我們從zImage跳轉到Image的偉大一跳了,跳之前準備好r0,r1,r2
mov pc, r4 @ call kernel
到此kernel的第一階段zImage 解壓縮階段已經執行完。
第二階段的代碼是從\arch\arm\kernel\head.S開始的。
核心啟動第二階段主要完成的工作有,cpu ID檢查,machine ID(也就是開發闆ID)檢查,建立初始化頁表,設定C代碼運作環境,跳轉到核心第一個真正的C函數startkernel開始執行。
這一階段涉及到兩個重要的結構體:
(1) 一個是struct proc_info_list 主要描述CPU相關的資訊,定義在檔案arch\arm\include\asm\procinfo.h中,與其相關的函數及變量在檔案arch/arm/mm/proc_arm920.S中被定義和指派。
(2) 另一個結構體是描述開發闆或者說機器資訊的結構體struct machine_desc,定義在\arch\arm\include\asm\mach\arch.h檔案中,其函數的定義和變量的指派在闆極相關檔案arch/arm/mach-s3c2410/mach-smdk2410.c中實作,這也是核心移植非常重要的一個檔案。
該階段一般由前面的解壓縮代碼調用,進入該階段要求:
MMU = off, D-cache = off, I-cache = dont care,r0 = 0, r1 = machine id.
所有的機器ID清單儲存在arch/arm/tools/mach-types 檔案中,在編譯時會将這些機器ID按照統一的格式連結到基本核心映像檔案vmlinux的__arch_info_begin和__arch_info_end之間的段中。存儲格式定義在include/asm-arm/mach/arch.h檔案中的結構體struct machine_desc {}。這兩個結構體的内容最終會被連接配接到基本核心映像vmlinux中的兩個段内,分别是*(.proc.info.init)和*(.arch.info.init),可以參考下面的連接配接腳本。
連結腳本:arch/arm/kernel/vmlinux.lds
*****************************連結腳本**************************************
SECTIONS
{
. = TEXTADDR;
.init : {
_stext = .;
_sinittext = .;
*(.init.text)
_einittext = .;
__proc_info_begin = .;
*(.proc.info.init)
__proc_info_end = .;
__arch_info_begin = .;
*(.arch.info.init)
__arch_info_end = .;
__tagtable_begin = .;
*(.taglist.init)
__tagtable_end = .;
. = ALIGN(16);
__setup_start = .;
*(.init.setup)
__setup_end = .;
__early_begin = .;
*(.early_param.init)
__early_end = .;
__initcall_start = .;
*(.initcall1.init)
*(.initcall2.init)
*(.initcall3.init)
*(.initcall4.init)
*(.initcall5.init)
*(.initcall6.init)
*(.initcall7.init)
__initcall_end = .;
__con_initcall_start = .;
*(.con_initcall.init)
__con_initcall_end = .;
__security_initcall_start = .;
*(.security_initcall.init)
__security_initcall_end = .;
. = ALIGN(32);
__initramfs_start = .;
usr/built-in.o(.init.ramfs)
__initramfs_end = .;
. = ALIGN(64);
__per_cpu_start = .;
*(.data.percpu)
__per_cpu_end = .;
#ifndef CONFIG_XIP_KERNEL
__init_begin = _stext;
*(.init.data)
. = ALIGN(4096);
__init_end = .;
#endif
}
*****************************連結腳本**************************************
下面開始代碼\arch\arm\kernel\head.S的注釋:
開始分析前先看下一點基礎知識:
1. kernel運作的史前時期和記憶體布局
在arm平台下,zImage.bin壓縮鏡像是由bootloader加載到實體記憶體,然後跳到zImage.bin裡一段程式,它專門于将被壓縮的kernel解壓縮到KERNEL_RAM_PADDR開始的一段記憶體中,接着跳進真正的kernel去執行。該kernel的執行起點是stext函數,定義于arch/arm/kernel/head.S。此時記憶體的布局如下圖所示
在開發闆3c2410中,SDRAM連接配接到記憶體控制器的Bank6中,它的開始記憶體位址是0x30000000,大小為64M,即0x20000000。 ARM Linux kernel将SDRAM的開始位址定義為PHYS_OFFSET。經bootloader加載kernel并由自解壓部分代碼運作後,最終kernel被放置到KERNEL_RAM_PADDR(=PHYS_OFFSET + TEXT_OFFSET,即0x30008000)位址上的一段記憶體,經此放置後,kernel代碼以後均不會被移動。
在進入kernel代碼前,即bootloader和自解壓縮階段,ARM未開啟MMU功能。是以kernel啟動代碼一個重要功能是設定好相應的頁表,并開啟MMU功能。為了支援MMU功能,kernel鏡像中的所有符号,包括代碼段和資料段的符号,在連結時都生成了它在開啟MMU時,所在實體記憶體位址映射到的虛拟記憶體位址。
以arm kernel第一個符号(函數)stext為例,在編譯連結,它生成的虛拟位址是0xc0008000,而放置它的實體位址為0x30008000(還記得這是PHYS_OFFSET+TEXT_OFFSET嗎?)。實際上這個變換可以利用簡單的公式進行表示:va = pa – PHYS_OFFSET + PAGE_OFFSET。Arm linux最終的kernel空間的頁表,就是按照這個關系來建立。
之是以較早提及arm linux 的記憶體映射,原因是在進入kernel代碼,裡面所有符号位址值為清一色的0xCXXXXXXX位址,而此時ARM未開啟MMU功能,故在執行stext函數第一條執行時,它的PC值就是stext所在的記憶體位址(即實體位址,0x30008000)。是以,下面有些代碼,需要使用位址無關技術。
__HEAD /*該宏定義了下面的代碼位于".head.text"段内*/
.type stext, %function
ENTRY(stext)
setmode PSR_F_BIT | PSR_I_BIT | SVC_MODE, r9 @ ensure svc mode and irqs disabled 進入超級權限模式,關中斷
mrc p15, 0, r9, c0, c0 @ get processor id 取出cpu id
bl __lookup_processor_type @ r5=procinfo r9=cpuid
__lookup_processor_type函數的具體解析開始(\arch\arm\kernel\ head-common.S)
在講解該程式段之前先來看一些相關知識,核心所支援的每一種CPU 類型都由結構體proc_info_list來描述。
該結構體在檔案arch/arm/include/asm/procinfo.h 中定義:
struct proc_info_list {
unsigned int cpu_val;
unsigned int cpu_mask;
unsigned long __cpu_mm_mmu_flags;
unsigned long __cpu_io_mmu_flags;
unsigned long __cpu_flush;
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;
};
對于 arm920 來說,其對應結構體在檔案 linux/arch/arm/mm/proc-arm920.S 中初始化。
.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
…………………………………
.section ".proc.info.init"表明了該結構在編譯後存放的位置。在連結檔案 arch/arm/kernel/vmlinux.lds 中:
SECTIONS
{
#ifdef CONFIG_XIP_KERNEL
. = XIP_VIRT_ADDR(CONFIG_XIP_PHYS_ADDR);
#else
. = PAGE_OFFSET + TEXT_OFFSET;
#endif
.text.head : {
_stext = .;
_sinittext = .;
*(.text.head)
}
.init : {
INIT_TEXT
_einittext = .;
__proc_info_begin = .;
*(.proc.info.init)
__proc_info_end = .;
__arch_info_begin = .;
*(.arch.info.init)
__arch_info_end = .;
__tagtable_begin = .;
*(.taglist.init)
__tagtable_end = .;
………………………………
}
所有CPU類型對應的被初始化的 proc_info_list結構體都放在 __proc_info_begin和__proc_info_end之間。
/ *
* r9 = cpuid
* Returns:
* r5 = proc_info pointer in physical address space
* r9 = cpuid (preserved)
*/
__lookup_processor_type:
adr r3, 3f @r3存儲的是标号 3 的實體位址(由于沒有啟用 mmu ,是以目前肯定是實體位址)
ldmia r3, {r5 - r7} @ R5=__proc_info_begin,r6=__proc_info_end,r7=标号4處的虛拟位址,即4: .long . 處的位址
add r3, r3, #8 @ 得到4處的實體位址,剛好是跳過兩條指令
sub r3, r3, r7 @ get offset between virt&phys得到虛拟位址和實體位址之間的offset
add r5, r5, r3 @ convert virt addresses to
add r6, r6, r3 @ physical address space
1: ldmia r5, {r3, r4} @ value, mask r3= cpu_val , r4= cpu_mask
and r4, r4, r9 @ mask wanted bits;r9 中存放的是先前讀出的 processor ID ,此處屏蔽不需要的位
teq r3, r4 @ 檢視代碼和CPU 硬體是否比對( 比如想在arm920t上運作為cortex-a8編譯的核心?不讓)
beq 2f @ 如果相等則跳轉到标号2處,執行傳回指令
add r5, r5, #PROC_INFO_SZ @ sizeof(proc_info_list結構的長度,在這等于48)如果沒找到, 跳到下一個proc_info_list 處
cmp r5, r6 @ 判斷是不是到了該段的結尾
blo 1b @ 如果沒有,繼續跳到标号1處,查找下一個
mov r5, #0 @ unknown processor ,如果到了結尾,沒找到比對的,就把0指派給r5,然後傳回
2: mov pc, lr @ 找到後傳回,r5指向找到的結構體
ENDPROC(__lookup_processor_type)
.align 2
3: .long __proc_info_begin
.long __proc_info_end
4: .long . @“.”表示目前這行代碼編譯連接配接後的虛拟位址
.long __arch_info_begin
.long __arch_info_end
__lookup_processor_type函數的具體解析結束(\arch\arm\kernel\ head-common.S)
movs r10, r5 @ invalid processor (r5=0)?
beq __error_p @ yes, error 'p'
__lookup_machine_type函數的具體解析開始(\arch\arm\kernel\ head-common.S)
每一個CPU 平台都可能有其不一樣的結構體,描述這個平台的結構體是 machine_desc 。
這個結構體在檔案arch/arm/include/asm/mach/arch.h 中定義:
struct machine_desc {
unsigned int nr;
unsigned int phys_io;
………………………………
};
對于平台smdk2410 來說其對應 machine_desc 結構在檔案linux/arch/arm/mach-s3c2410/mach-smdk2410.c中初始化:
MACHINE_START(SMDK2410, "SMDK2410")
.phys_io = S3C2410_PA_UART,
.io_pg_offst = (((u32)S3C24XX_VA_UART) >> 18) & 0xfffc,
.boot_params = S3C2410_SDRAM_PA + 0x100,
.map_io = smdk2410_map_io,
.init_irq = s3c24xx_init_irq,
.init_machine = smdk2410_init,
.timer = &s3c24xx_timer,
MACHINE_END
對于宏MACHINE_START 在檔案 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 /
};
__attribute__((__section__(".arch.info.init")))表明該結構體在并以後存放的位置。
在連結檔案 連結腳本檔案 arch/arm/kernel/vmlinux.lds 中
SECTIONS
{
#ifdef CONFIG_XIP_KERNEL
. = XIP_VIRT_ADDR(CONFIG_XIP_PHYS_ADDR);
#else
. = PAGE_OFFSET + TEXT_OFFSET;
#endif
.text.head : {
_stext = .;
_sinittext = .;
*(.text.head)
}
.init : {
INIT_TEXT
_einittext = .;
__proc_info_begin = .;
*(.proc.info.init)
__proc_info_end = .;
__arch_info_begin = .;
*(.arch.info.init)
__arch_info_end = .;
………………………………
}
在__arch_info_begin和 __arch_info_end之間存放了linux核心所支援的所有平台對應的 machine_desc 結構體。
__lookup_machine_type:
adr r3, 4b @ 把标号4處的位址放到r3寄存器裡面
ldmia r3, {r4, r5, r6} @ R 4 = 标号4處的虛拟位址 ,r 5 = __arch_info_begin ,r 6= __arch_info_end
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?把取到的machine id和從uboot中傳過來的machine id(存放r1中)相比較
beq 2f @ found 如果相等,則跳到标号2處,傳回
add r5, r5, #SIZEOF_MACHINE_DESC@ next machine_desc 沒有找到,則繼續找下一個,加上該結構體的長度
cmp r5, r6 @ 判斷是否已經到該段的末尾
blo 1b @ 如果沒有,則跳轉到标号1處,繼續查找
mov r5, #0 @ unknown machine 如果已經到末尾,并且沒找到,則傳回值r5寄存器指派為0
2: mov pc, lr @ 傳回原函數,且r5作為傳回值
ENDPROC(__lookup_machine_type)
.align 2
3: .long __proc_info_begin
.long __proc_info_end
4: .long . @“.”表示目前這行代碼編譯連接配接後的虛拟位址
.long __arch_info_begin
.long __arch_info_end
__lookup_machine_type函數的具體解析結束(\arch\arm\kernel\ head-common.S)
movs r8, r5 @ invalid machine (r5=0)?
beq __error_a @ yes, error 'a'
bl __vet_atags
__vet_atags函數的具體解析開始(\arch\arm\kernel\ head-common.S)
關于參數連結清單:
核心參數連結清單的格式和說明可以從核心源代碼目錄樹中的\arch\arm\include\asm\setup.h中找到,參數連結清單必須以ATAG_CORE開始,以ATAG_NONE結束。這裡的 ATAG_CORE,ATAG_NONE是各個參數的标記,本身是一個32 位值,例如: ATAG_CORE=0x54410001 。 其它的參數标記還包括: ATAG_MEM32 , ATAG_INITRD , ATAG_RAMDISK , ATAG_COMDLINE 等。每個參數标記就代表一個參數結構體,由各個參數結構體構成了參數連結清單。參數結構體的定義如下:
struct tag {
struct tag_header hdr;
union {
struct tag_core core;
struct tag_mem32 mem;
struct tag_videotext videotext;
struct tag_ramdisk ramdisk;
struct tag_initrd initrd;
struct tag_serialnr serialnr;
struct tag_revision revision;
struct tag_videolfb videolfb;
struct tag_cmdline cmdline;
struct tag_acorn acorn;
struct tag_memclk memclk;
} u;
};
參數結構體包括兩個部分,一個是 tag_header 結構體 , 一個是 u 聯合體。
tag_header結構體的定義如下:
struct tag_header {
u32 size;
u32 tag;
};
其中 size :表示整個 tag 結構體的大小 ( 用字的個數來表示,而不是位元組的個數 ) ,等于tag_header的大小加上 u 聯合體的大小,例如,參數結構體 ATAG_CORE 的size=(sizeof(tag->tag_header)+sizeof(tag->u.core))>>2,一般通過函數 tag_size(struct * tag_xxx) 來獲得每個參數結構體的 size 。其中 tag :表示整個 tag 結構體的标記,如: ATAG_CORE 等。
__vet_atags:
tst r2, #0x3 @ aligned? r2指向該參數連結清單的起始位置,此處判斷它是否字對齊
bne 1f @ 如果沒有對齊,跳到标号1處直接傳回,并且把r2的值指派為0,作為傳回值
ldr r5, [r2, #0] @ is first tag ATAG_CORE? 擷取第一個 tag 結構的 size
cmp r5, #ATAG_CORE_SIZE @ 判斷該 tag 的長度是否合法
cmpne r5, #ATAG_CORE_SIZE_EMPTY
bne 1f @ 如果不合法,異常傳回
ldr r5, [r2, #4] @ 擷取第一個 tag 結構體的标記
ldr r6, =ATAG_CORE @ 取出标記ATAG_CORE的内容
cmp r5, r6 @ 判斷該标記是否等于ATAG_CORE
bne 1f @ 如果不等,異常傳回
mov pc, lr @ atag pointer is ok,如果都相等,則正常傳回
1: mov r2, #0 @ 異常傳回值
mov pc, lr @ 異常傳回
ENDPROC(__vet_atags)
__vet_atags函數的具體解析結束(\arch\arm\kernel\ head-common.S)
/*建立核心初始化頁表*/
bl __create_page_tables
__create_page_tables函數的具體解析開始(\arch\arm\kernel\ head.S)
#define KERNEL_RAM_PADDR (PHYS_OFFSET + TEXT_OFFSET)
.macro pgtbl, rd
ldr\rd, =(KERNEL_RAM_PADDR - 0x4000)
.endm
其中:PHYS_OFFSET在arch/arm/mach-s3c2410/include/mach/memory.h定義,為UL(0x30000000),而TEXT_OFFSET在arch/arm/Makefile中定義,為核心鏡像在記憶體中到記憶體開始位置的偏移(位元組),為$(textofs-y) textofs-y也在檔案arch/arm/Makefile中定義,為textofs-y := 0x00008000,r4 = 30004000為臨時頁表的起始位址,首先即是初始化16K的頁表,高12位虛拟位址為頁表索引,每個頁表索引占4個位元組,是以為4K*4 = 16K,大頁表,每一個頁表項,映射1MB虛拟位址.
__create_page_tables:
pgtbl r4 @ r4中存放的為頁表的基位址,最終該位址會寫入cp15的寄存器c2,這個值必須是 16K 對齊的
mov r0, r4 @ 把頁表的基位址存放到r0中
mov r3, #0 @ 把r3清0
add r6, r0, #0x4000 @ r6指向16K的末尾
1: str r3, [r0], #4 @ 把16K的頁表空間清0
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
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
add r0, r4, #(KERNEL_START & 0xff000000) >> 18 @ r0 = 0x30007000 r0存放的是轉換表的起始位置
str r3, [r0, #(KERNEL_START & 0x00f00000) >> 18]! @ r3存放的是核心鏡像代碼段的起始位址
ldr r6, =(KERNEL_END - 1) @ 擷取核心的尾部虛拟位址存于r6中
add r0, r0, #4 @ 第一個位址條目存放在 0x30007004 處,以後依次遞增
add r6, r4, r6, lsr #18 @ 計算最後一個位址條目存放的位置
1: cmp r0, r6 @ 填充這之間的位址條目
add r3, r3, #1 << 20
strls r3, [r0], #4
bls 1b
…………………………………
…………………………………………
add r0, r4, #PAGE_OFFSET >> 18
orr r6, r7, #(PHYS_OFFSET & 0xff000000) @ r6= 0x30000c1e
.if (PHYS_OFFSET & 0x00f00000)
orr r6, r6, #(PHYS_OFFSET & 0x00f00000)
.endif
str r6, [r0] @ 将0x30000c1e 存于0x30007000處。
………………………
………………………………
mov pc, lr @子程式傳回
ENDPROC(__create_page_tables)
__create_page_tables函數的具體解析結束(\arch\arm\kernel\ head.S)
ldr r13, __switch_data @ address to jump to after mmu has been enabled
adr lr, BSYM(__enable_mmu) @ return (PIC) address
__enable_mmu函數的具體解析開始(\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 //禁止資料 cache
#endif
#ifdef CONFIG_CPU_BPREDICT_DISABLE
bic r0, r0, #CR_Z
#endif
#ifdef CONFIG_CPU_ICACHE_DISABLE
bic r0, r0, #CR_I //禁止指令 cache
#endif //配置相應的通路權限并存入 r5 中
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 //将通路權限寫入協處理器
mcr p15, 0, r4, c2, c0, 0 //将頁表基位址寫入基址寄存器 C2 , 0X30004000
b __turn_mmu_on //跳轉到程式段去打開 MMU
ENDPROC(__enable_mmu)
檔案linux/arch/arm/kernel/head.S 中
__turn_mmu_on:
mov r0, r0
mcr p15, 0, r0, c1, c0, 0 //打開 MMU 同時打開 cache 等。
mrc p15, 0, r3, c0, c0, 0 @ read id reg 讀取 id 寄存器
mov r3, r3
mov r3, r3 //兩個空操作,等待前面所取的指令得以執行。
mov pc, r13 //程式跳轉
ENDPROC(__turn_mmu_on)
__enable_mmu函數的具體解析結束(\arch\arm\kernel\ head.S)
ARM( add pc, r10, #PROCINFO_INITFUNC )
__arm920_setup函數的具體解析開始(\arch\arm\mm\ proc-arm920.S)
在上面程式段.section ".text.head", "ax" 的最後有這樣幾行:
add pc, r10, #PROCINFO_INITFUNC
R10中存放的是在函數 __lookup_processor_type 中成功比對的結構體 proc_info_list。對于arm920 來說在檔案 linux/arch/arm/mm/proc-arm920.S 中有:
.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
………………………………
add pc, r10, #PROCINFO_INITFUNC的意思跳到函數 __arm920_setup去執行。
.type __arm920_setup, #function //表明這是一個函數
__arm920_setup:
mov r0, #0 //設定 r0 為 0 。
mcr p15, 0, r0, c7, c7 //使資料 cahche, 指令 cache 無效。
mcr p15, 0, r0, c7, c10, 4 //使 write buffer 無效。
#ifdef CONFIG_MMU
mcr p15, 0, r0, c8, c7 //使資料 TLB, 指令 TLB 無效。
#endif
adr r5, arm920_crval //擷取 arm920_crval 的位址,并存入 r5 。
ldmia r5, {r5, r6} //擷取 arm920_crval 位址處的連續 8 位元組分别存入 r5,r6 。
mrc p15, 0, r0, c1, c0 //擷取 CP15 下控制寄存器的值,并存入 r0 。
bic r0, r0, r5 //通過檢視 arm920_crval 的值可知該行是清除 r0 中相關位,為以後對這些位的指派做準備
orr r0, r0, r6 //設定 r0 中的相關位,即為 mmu 做相應設定。
mov pc, lr //上面有操作 adr lr, __enable_mmu ,此處将跳到程式段 __enable_mmu 處。
.size __arm920_setup, . - __arm920_setup
.type arm920_crval, #object
arm920_crval:
crval clear=0x00003f3f, mmuset=0x00003135, ucset=0x00001130
__arm920_setup函數的具體解析結束(\arch\arm\mm\ proc-arm920.S)
ENDPROC(stext)
接着往下分析linux/arch/arm/kernel/head-common.S中:
.type __switch_data, %object @定義__switch_data為一個對象
__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
********************************** vmlinux.lds開始*******************************************
SECTIONS
{
……………………
#ifdef CONFIG_XIP_KERNEL
__data_loc = ALIGN(4);
. = PAGE_OFFSET + TEXT_OFFSET;
#else
. = ALIGN(THREAD_SIZE);
__data_loc = .;
#endif
.data : AT(__data_loc) { //此處資料存儲在上面__data_loc處。
_data = .;
*(.data.init_task)
…………………………
.bss : {
__bss_start = .;
*(.bss)
*(COMMON)
_end = .;
}
………………………………
}
init_thread_union 是 init程序的基位址.在 arch/arm/kernel/init_task.c 中:
union thread_union init_thread_union __attribute__((__section__(".init.task"))) = { INIT_THREAD_INFO(init_task) };
對照 vmlnux.lds.S 中,我們可以知道init task是存放在 .data 段的開始8k, 并且是THREAD_SIZE(8k)對齊的 */
********************************** vmlinux.lds結束*******************************************
__mmap_switched:
adr r3, __switch_data + 4
ldmia r3!, {r4, r5, r6, r7}
……………………
………………………………
mov fp, #0 @ 清除bss段
1: cmp r6, r7
strcc fp, [r6],#4
bcc 1b
ARM( ldmia r3, {r4, r5, r6, r7, sp})
str r9, [r4] @ Save processor ID 儲存處理器id到processor_id所在的位址中
str r1, [r5] @ Save machine type 儲存machine id到__machine_arch_type中
str r2, [r6] @ Save atags pointer 儲存參數清單首位址到__atags_pointer中
bic r4, r0, #CR_A @ Clear 'A' bit
stmia r7, {r0, r4} @ Save control register values
b start_kernel @程式跳轉到函數 start_kernel 進入 C 語言部分。
ENDPROC(__mmap_switched)
到處我們的啟動的第二階段分析完畢。