1. 程式人生 > >ARM Linux啟動流程-彙編第二階段

ARM Linux啟動流程-彙編第二階段

本文整理了ARM Linxu啟動流程的第二階段——start_kernel前啟動階段(彙編部分),核心版本為3.12.35。我以手上的樹莓派b(ARM11)為平臺示例來分析Linux核心在自解壓後到跳轉執行start_kernel之前所做的主要初始化工作:包括引數有效性驗證、建立初始頁表和MMU初始化等。

核心版本:Linux-3.12.35
分析檔案:arch/arm/kernel/head.S、head-common.S、proc-v6.S

單板:樹莓派b

在核心啟動時執行自解壓完成後,會跳轉到解壓後的地址處執行,在我的環境中就是地址0x00008000處,然後核心啟動並執行初始化。

首先給出你核心啟動的彙編部分的總流程如下:

核心啟動程式的入口:參見arch/arm/kernel/vmlinux.lds(由arch/arm/kernel/vmlinux.lds.S生成)。

arch/arm/kernel/vmlinux.lds:

[cpp] view plain copy print?
  1. ENTRY(stext)  
  2. jiffies = jiffies_64;  
  3. SECTIONS  
  4. {  
  5. ......  
  6.  . = 0xC0000000 + 0x00008000;  
  7.  .head.text : {  
  8.   _text = .;  
  9.   *(.head.text)  
  10.  }  
  11.  .text : { /* Real text segment     */
  12.   _stext = .; /* Text and read-only data    */
ENTRY(stext)
jiffies = jiffies_64;
SECTIONS
{
......
 . = 0xC0000000 + 0x00008000;
 .head.text : {
  _text = .;
  *(.head.text)
 }
 .text : { /* Real text segment		*/
  _stext = .; /* Text and read-only data	*/

此處的TEXT_OFFSET表示核心起始地址相對於RAM地址的偏移值,定義在arch/arm/Makefile中,值為0x00008000:

[cpp] view plain copy print?
  1. textofs-y   := 0x00008000  
  2. ......  
  3. # The byte offset of the kernel image in RAM from the start of RAM.
  4. TEXT_OFFSET := $(textofs-y)  
textofs-y	:= 0x00008000
......
# The byte offset of the kernel image in RAM from the start of RAM.
TEXT_OFFSET := $(textofs-y)

PAGE_OFFSET表示核心虛擬地址空間的其實地址,定義在arch/arm/include/asm/memory.h中:

[cpp] view plain copy print?
  1. #ifdef CONFIG_MMU
  2. /* 
  3.  * PAGE_OFFSET - the virtual address of the start of the kernel image 
  4.  * TASK_SIZE - the maximum size of a user space task. 
  5.  * TASK_UNMAPPED_BASE - the lower boundary of the mmap VM area 
  6.  */
  7. #define PAGE_OFFSET     UL(CONFIG_PAGE_OFFSET)
#ifdef CONFIG_MMU

/*
 * PAGE_OFFSET - the virtual address of the start of the kernel image
 * TASK_SIZE - the maximum size of a user space task.
 * TASK_UNMAPPED_BASE - the lower boundary of the mmap VM area
 */
#define PAGE_OFFSET		UL(CONFIG_PAGE_OFFSET)
CONFIG_PAGE_OFFSET定義在arch/arm/Kconfig中,採用預設值0xC0000000。
[cpp] view plain copy print?
  1. config PAGE_OFFSET  
  2.     hex  
  3.     default 0x40000000 if VMSPLIT_1G  
  4.     default 0x80000000 if VMSPLIT_2G  
  5.     default 0xC0000000  
config PAGE_OFFSET
	hex
	default 0x40000000 if VMSPLIT_1G
	default 0x80000000 if VMSPLIT_2G
	default 0xC0000000

所以,可以看出核心的連結地址採用的是虛擬地址,地址值為0xC0008000。

核心啟動程式的入口在linux/arch/arm/kernel/head.S中,head.S中定義了幾個比較重要的變數,在看分析程式前先來看一下:

[cpp] view plain copy print?
  1. /* 
  2.  * swapper_pg_dir is the virtual address of the initial page table. 
  3.  * We place the page tables 16K below KERNEL_RAM_VADDR.  Therefore, we must 
  4.  * make sure that KERNEL_RAM_VADDR is correctly set.  Currently, we expect 
  5.  * the least significant 16 bits to be 0x8000, but we could probably 
  6.  * relax this restriction to KERNEL_RAM_VADDR >= PAGE_OFFSET + 0x4000. 
  7.  */
  8. #define KERNEL_RAM_VADDR    (PAGE_OFFSET + TEXT_OFFSET)
  9. #if (KERNEL_RAM_VADDR & 0xffff) != 0x8000
  10. #error KERNEL_RAM_VADDR must start at 0xXXXX8000
  11. #endif
  12. #ifdef CONFIG_ARM_LPAE
  13.     /* LPAE requires an additional page for the PGD */
  14. #define PG_DIR_SIZE 0x5000
  15. #define PMD_ORDER   3
  16. #else
  17. #define PG_DIR_SIZE 0x4000
  18. #define PMD_ORDER   2
  19. #endif
  20.     .globl  swapper_pg_dir  
  21.     .equ    swapper_pg_dir, KERNEL_RAM_VADDR - PG_DIR_SIZE  
  22.     .macro  pgtbl, rd, phys  
  23.     add \rd, \phys, #TEXT_OFFSET - PG_DIR_SIZE  
  24.     .endm  
/*
 * swapper_pg_dir is the virtual address of the initial page table.
 * We place the page tables 16K below KERNEL_RAM_VADDR.  Therefore, we must
 * make sure that KERNEL_RAM_VADDR is correctly set.  Currently, we expect
 * the least significant 16 bits to be 0x8000, but we could probably
 * relax this restriction to KERNEL_RAM_VADDR >= PAGE_OFFSET + 0x4000.
 */
#define KERNEL_RAM_VADDR	(PAGE_OFFSET + TEXT_OFFSET)
#if (KERNEL_RAM_VADDR & 0xffff) != 0x8000
#error KERNEL_RAM_VADDR must start at 0xXXXX8000
#endif

#ifdef CONFIG_ARM_LPAE
	/* LPAE requires an additional page for the PGD */
#define PG_DIR_SIZE	0x5000
#define PMD_ORDER	3
#else
#define PG_DIR_SIZE	0x4000
#define PMD_ORDER	2
#endif

	.globl	swapper_pg_dir
	.equ	swapper_pg_dir, KERNEL_RAM_VADDR - PG_DIR_SIZE

	.macro	pgtbl, rd, phys
	add	\rd, \phys, #TEXT_OFFSET - PG_DIR_SIZE
	.endm

其中KERNEL_RAM_VADDR表示核心啟動地址的虛擬地址,即前面看到的連結地址0xC0008000,同時核心要求這個地址的第16位必須是0x8000。

然後由於沒有配置ARM LPAE,則採用一級對映結構,頁表的大小為16KB,頁大小為1MB。

最後swapper_pg_dir表示初始頁表的起始地址,這個值等於核心起始虛擬地址-頁表大小=0xC0004000(核心起始地址下16KB空間存放頁表)。虛擬地址空間如下圖:

需要說明一下:在我的環境中,核心在自解壓階段被解壓到了0x00008000地址處,由於核心入口連結地址採用的是虛擬地址0xC0008000,這兩個地址並不相同;並且此時MMU並沒有被使能,所以無法進行虛擬地址到實體地址的轉換,程式開始執行後在開啟MMU前的將使用位置無關碼。

在知道了核心的入口位置後,來看一下此時的裝置和暫存器的狀態:

[cpp] view plain copy print?
  1. /* 
  2.  * Kernel startup entry point. 
  3.  * --------------------------- 
  4.  * 
  5.  * This is normally called from the decompressor code.  The requirements 
  6.  * are: MMU = off, D-cache = off, I-cache = dont care, r0 = 0, 
  7.  * r1 = machine nr, r2 = atags or dtb pointer. 
  8.  * 
  9.  * This code is mostly position independent, so if you link the kernel at 
  10.  * 0xc0008000, you call this at __pa(0xc0008000). 
  11.  * 
  12.  * See linux/arch/arm/tools/mach-types for the complete list of machine 
  13.  * numbers for r1. 
  14.  * 
  15.  * We're trying to keep crap to a minimum; DO NOT add any machine specific 
  16.  * crap here - that's what the boot loader (or in extreme, well justified 
  17.  * circumstances, zImage) is for. 
  18.  */
  19.     .arm  
  20.     __HEAD  
  21. ENTRY(stext)  
/*
 * Kernel startup entry point.
 * ---------------------------
 *
 * This is normally called from the decompressor code.  The requirements
 * are: MMU = off, D-cache = off, I-cache = dont care, r0 = 0,
 * r1 = machine nr, r2 = atags or dtb pointer.
 *
 * This code is mostly position independent, so if you link the kernel at
 * 0xc0008000, you call this at __pa(0xc0008000).
 *
 * See linux/arch/arm/tools/mach-types for the complete list of machine
 * numbers for r1.
 *
 * We're trying to keep crap to a minimum; DO NOT add any machine specific
 * crap here - that's what the boot loader (or in extreme, well justified
 * circumstances, zImage) is for.
 */
	.arm

	__HEAD
ENTRY(stext)

註釋中說明了,此時的MMU關閉、D-cache關閉、r0 = 0、r1 = 機器碼、r2 = 啟動引數atags或dtb的地址(我的環境中使用的是atags),同時核心支援的機器碼被定義在了linux/arch/arm/tools/mach-types中。我樹莓派使用的是:

bcm2708                    MACH_BCM2708             BCM2708                            3138

 下面來逐行分析程式碼:

[cpp] view plain copy print?
  1.  THUMB( adr r9, BSYM(1f)    )   @ Kernel is always entered in ARM.  
  2.  THUMB( bx  r9      )   @ If this is a Thumb-2 kernel,  
  3.  THUMB( .thumb          )   @ switch to Thumb now.  
  4.  THUMB(1:           )  
  5. #ifdef CONFIG_ARM_VIRT_EXT
  6.     bl  __hyp_stub_install  
  7. #endif
  8.     @ ensure svc mode and all interrupts masked  
  9.     safe_svcmode_maskall r9  
  10.     mrc p15, 0, r9, c0, c0      @ get processor id  
  11.     bl  __lookup_processor_type     @ r5=procinfo r9=cpuid  
 THUMB(	adr	r9, BSYM(1f)	)	@ Kernel is always entered in ARM.
 THUMB(	bx	r9		)	@ If this is a Thumb-2 kernel,
 THUMB(	.thumb			)	@ switch to Thumb now.
 THUMB(1:			)

#ifdef CONFIG_ARM_VIRT_EXT
	bl	__hyp_stub_install
#endif
	@ ensure svc mode and all interrupts masked
	safe_svcmode_maskall r9

	mrc	p15, 0, r9, c0, c0		@ get processor id
	bl	__lookup_processor_type		@ r5=procinfo r9=cpuid

這裡的safe_svcmode_maskall是一個巨集,定義在arch/arm/include/asm/assembler.h中,它的作用就是確保ARM進入SVC工作模式並遮蔽所有的中斷(此時關閉中斷的原因是中斷向量表尚未建立,核心無能力響應中斷)。

然後獲取處理器ID儲存到r9暫存器中,接著跳轉到__lookup_processor_type尋找對應處理器ID的proc_info地址。__lookup_processor_type定義在arch/arm/kernel/head-common.S中:

[cpp] view plain copy print?
  1. /* 
  2.  * Read processor ID register (CP#15, CR0), and look up in the linker-built 
  3.  * supported processor list.  Note that we can't use the absolute addresses 
  4.  * for the __proc_info lists since we aren't running with the MMU on 
  5.  * (and therefore, we are not in the correct address space).  We have to 
  6.  * calculate the offset. 
  7.  * 
  8.  *  r9 = cpuid 
  9.  * Returns: 
  10.  *  r3, r4, r6 corrupted 
  11.  *  r5 = proc_info pointer in physical address space 
  12.  *  r9 = cpuid (preserved) 
  13.  */
  14. __lookup_processor_type:  
  15.     adr r3, __lookup_processor_type_data  
  16.     ldmia   r3, {r4 - r6}  
  17.     sub r3, r3, r4          @ get offset between virt&phys  
  18.     add r5, r5, r3          @ convert virt addresses to  
  19.     add r6, r6, r3          @ physical address space  
  20. 1:  ldmia   r5, {r3, r4}            @ value, mask  
  21.     and r4, r4, r9          @ mask wanted bits  
  22.     teq r3, r4  
  23.     beq 2f  
  24.     add r5, r5, #PROC_INFO_SZ       @ sizeof(proc_info_list)  
  25.     cmp r5, r6  
  26.     blo 1b  
  27.     mov r5, #0              @ unknown processor  
  28. 2:  mov pc, lr  
  29. ENDPROC(__lookup_processor_type)  
/*
 * Read processor ID register (CP#15, CR0), and look up in the linker-built
 * supported processor list.  Note that we can't use the absolute addresses
 * for the __proc_info lists since we aren't running with the MMU on
 * (and therefore, we are not in the correct address space).  We have to
 * calculate the offset.
 *
 *	r9 = cpuid
 * Returns:
 *	r3, r4, r6 corrupted
 *	r5 = proc_info pointer in physical address space
 *	r9 = cpuid (preserved)
 */
__lookup_processor_type:
	adr	r3, __lookup_processor_type_data
	ldmia	r3, {r4 - 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:	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)

首先獲取處理器相關資訊表的執行地址並儲存到r3暫存器中。核心將所有的處理器資訊都儲存在proc_info_list結構體表中,它的定義如下(asm/procinfo.h):

[cpp] view plain copy print?
  1. /* 
  2.  * Note!  struct processor is always defined if we're 
  3.  * using MULTI_CPU, otherwise this entry is unused, 
  4.  * but still exists. 
  5.  * 
  6.  * NOTE! The following structure is defined by assembly 
  7.  * language, NOT C code.  For more information, check: 
  8.  *  arch/arm/mm/proc-*.S and arch/arm/kernel/head.S 
  9.  */
  10. struct proc_info_list {  
  11.     unsigned int        cpu_val;  
  12.     unsigned int        cpu_mask;  
  13.     unsigned long       __cpu_mm_mmu_flags; /* used by head.S */
  14.     unsigned long       __cpu_io_mmu_flags; /* used by head.S */
  15.     unsigned long       __cpu_flush;        /* used by head.S */
  16.     constchar      *arch_name;  
  17.     constchar      *elf_name;  
  18.     unsigned int        elf_hwcap;  
  19.     constchar      *cpu_name;  
  20.     struct processor    *proc;  
  21.     struct cpu_tlb_fns  *tlb;  
  22.     struct cpu_user_fns *user;  
  23.     struct cpu_cache_fns    *cache;  
  24. };  
/*
 * Note!  struct processor is always defined if we're
 * using MULTI_CPU, otherwise this entry is unused,
 * but still exists.
 *
 * NOTE! The following structure is defined by assembly
 * language, NOT C code.  For more information, check:
 *  arch/arm/mm/proc-*.S and arch/arm/kernel/head.S
 */
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;
};

結構體中描述了CPU相關的資訊,其中__cpu_mm_mmu_flags、__cpu_io_mmu_flags和__cpu_flush這三個欄位將會在head.s中使用到。處理器相關資訊都被儲存在.init.proc.info段中:

[cpp] view plain copy print?
  1. /* 
  2.  * Look in <asm/procinfo.h> for information about the __proc_info structure. 
  3.  */
  4.     .align  2  
  5.     .type   __lookup_processor_type_data, %object  
  6. __lookup_processor_type_data:  
  7.     .long   .  
  8.     .long   __proc_info_begin  
  9.     .long   __proc_info_end  
  10.     .size   __lookup_processor_type_data, . - __lookup_processor_type_data  
/*
 * Look in <asm/procinfo.h> for information about the __proc_info structure.
 */
	.align	2
	.type	__lookup_processor_type_data, %object
__lookup_processor_type_data:
	.long	.
	.long	__proc_info_begin
	.long	__proc_info_end
	.size	__lookup_processor_type_data, . - __lookup_processor_type_data

vmlinux.lds:

[cpp] view plain copy print?
  1. .init.proc.info : {  
  2.  . = ALIGN(4); __proc_info_begin = .; *(.proc.info.init) __proc_info_end = .;  
  3. }  
 .init.proc.info : {
  . = ALIGN(4); __proc_info_begin = .; *(.proc.info.init) __proc_info_end = .;
 }

其中每種型別處理器的資訊定義在arch/arm/mm/proc-*.S下,例如我的環境定義在proc-v6.S中:

[cpp] view plain copy print?
  1. .section ".proc.info.init", #alloc, #execinstr  
  2. /* 
  3.  * Match any ARMv6 processor core. 
  4.  */
  5. .type   __v6_proc_info, #object  
  6. _v6_proc_info:  
  7. .long   0x0007b000  
  8. .long   0x0007f000  
  9. ALT_SMP(.long \  
  10.     PMD_TYPE_SECT | \  
  11.     PMD_SECT_AP_WRITE | \  
  12.     PMD_SECT_AP_READ | \  
  13.     PMD_FLAGS_SMP)  
  14. ALT_UP(.long \  
  15.     PMD_TYPE_SECT | \  
  16.     PMD_SECT_AP_WRITE | \  
  17.     PMD_SECT_AP_READ | \  
  18.     PMD_FLAGS_UP)  
  19. .long   PMD_TYPE_SECT | \  
  20.     PMD_SECT_XN | \  
  21.     PMD_SECT_AP_WRITE | \  
  22.     PMD_SECT_AP_READ  
  23. b   __v6_setup  
  24. .....  
	.section ".proc.info.init", #alloc, #execinstr

	/*
	 * Match any ARMv6 processor core.
	 */
	.type	__v6_proc_info, #object
__v6_proc_info:
	.long	0x0007b000
	.long	0x0007f000
	ALT_SMP(.long \
		PMD_TYPE_SECT | \
		PMD_SECT_AP_WRITE | \
		PMD_SECT_AP_READ | \
		PMD_FLAGS_SMP)
	ALT_UP(.long \
		PMD_TYPE_SECT | \
		PMD_SECT_AP_WRITE | \
		PMD_SECT_AP_READ | \
		PMD_FLAGS_UP)
	.long   PMD_TYPE_SECT | \
		PMD_SECT_XN | \
		PMD_SECT_AP_WRITE | \
		PMD_SECT_AP_READ
	b	__v6_setup
......

回到__lookup_processor_type程式中,程式接著在r4、r5和r6中儲存__lookup_processor_type_data、__proc_info_begin和__proc_info_end的連結地址(即虛擬地址),然後通過r3 = r3 – r4得到執行地址和連結地址之間的偏移值並將r5和r6中的地址值修正為__proc_info_begin和__proc_info_end的執行地址。

然後從proc_info_list結構中取出cpu_val和cpu_mask欄位的內容,和r9中儲存的處理器ID進行比較,若匹配上了則通過r5暫存器返回當前處理器的proc_info_list結構資訊執行地址,否則r5 = r5 + PROC_INFO_SZ(即將r5指向下一條處理器的proc_info_list結構提資訊)繼續進行匹配。若全部匹配失敗,則r5返回0。

[cpp] view plain copy print?
  1. movs    r10, r5             @ invalid processor (r5=0)?  
  2. THUMB( it   eq )        @ force fixup-able long branch encoding  
  3. beq __error_p           @ yes, error 'p'
	movs	r10, r5				@ invalid processor (r5=0)?
 THUMB( it	eq )		@ force fixup-able long branch encoding
	beq	__error_p			@ yes, error 'p'

回到外層函式後,這裡會先將返回值付給r10,然後判斷是否返回值是否為0,若為0表示沒有匹配到對應的處理器資訊,呼叫__error_p打印出錯資訊並進入死迴圈,核心啟動失敗。

[cpp] view plain copy print?
  1. #ifdef CONFIG_ARM_LPAE
  2.     mrc p15, 0, r3, c0, c1, 4       @ read ID_MMFR0  
  3.     and r3, r3, #0xf            @ extract VMSA support  
  4.     cmp r3, #5              @ long-descriptor translation table format?  
  5.  THUMB( it  lo )                @ force fixup-able long branch encoding  
  6.     blo __error_p           @ only classic page table format  
  7. #endif
#ifdef CONFIG_ARM_LPAE
	mrc	p15, 0, r3, c0, c1, 4		@ read ID_MMFR0
	and	r3, r3, #0xf			@ extract VMSA support
	cmp	r3, #5				@ long-descriptor translation table format?
 THUMB( it	lo )				@ force fixup-able long branch encoding
	blo	__error_p			@ only classic page table format
#endif

這裡ARM_LAPE表示大實體記憶體擴充套件,我的環境下並沒有配置該項,暫不考慮。

[cpp] view plain copy print?
  1. #ifndef CONFIG_XIP_KERNEL
  2.     adr r3, 2f  
  3.     ldmia   r3, {r4, r8}  
  4.     sub r4, r3, r4          @ (PHYS_OFFSET - PAGE_OFFSET)  
  5.     add r8, r8, r4          @ PHYS_OFFSET  
  6. #else
  7.     ldr r8, =PHYS_OFFSET        @ always constant in thiscase
  8. #endif
#ifndef CONFIG_XIP_KERNEL
	adr	r3, 2f
	ldmia	r3, {r4, r8}
	sub	r4, r3, r4			@ (PHYS_OFFSET - PAGE_OFFSET)
	add	r8, r8, r4			@ PHYS_OFFSET
#else
	ldr	r8, =PHYS_OFFSET		@ always constant in this case
#endif

這裡將計算起始RAM實體地址並儲存到r8中,計算的方法同前面獲取CPU資訊結構地址的方法類似,首先獲取標號為2處的執行地址和連結地址(通過反彙編檢視,我的環境分別是:0x00008070和0xC0008070),一減之後就得到了執行地址和實體地址的差值(0xC0000000),然後用這個差值加上PAGE_OFFSET(0xC0000000)即可得到實際實體記憶體的起始地址PHYS_OFFSET(0x00000000)。

現在來檢視反彙編程式碼,加深理解:

[cpp] view plain copy print?
  1. c0008040:   e28f3028    add r3, pc, #40 ; 0x28  
  2. c0008044:   e8930110    ldm r3, {r4, r8}  
  3. c0008048:   e0434004    sub r4, r3, r4  
  4. c000804c:   e0888004    add r8, r8, r4  
  5. ......  
  6. c0008070:   c0008070    andgt   r8, r0, r0, ror r0  
  7. c0008074:   c0000000    andgt   r0, r0, r0  
c0008040:	e28f3028 	add	r3, pc, #40	; 0x28
c0008044:	e8930110 	ldm	r3, {r4, r8}
c0008048:	e0434004 	sub	r4, r3, r4
c000804c:	e0888004 	add	r8, r8, r4
......
c0008070:	c0008070 	andgt	r8, r0, r0, ror r0
c0008074:	c0000000 	andgt	r0, r0, r0

這裡r3 = 0x00008040 + 0x8 + 0x28 = 0x00008070,r4 =0xC0008070,r8 = 0xC0000000,在經過偏移處理後,r8的值就變成了0x00000000,即物理RAM首地址在記憶體地址空間中的偏移PAGE_OFFSET。(這裡有一點疑問,如果我這裡核心的執行地址並不是在0x00008000,那這個計算出道的物理RAM首地址不是就不正確了?

[cpp] view plain copy print?
  1. /* 
  2.  * r1 = machine no, r2 = atags or dtb, 
  3.  * r8 = phys_offset, r9 = cpuid, r10 = procinfo 
  4.  */
  5. bl  __vet_atags  
	/*
	 * r1 = machine no, r2 = atags or dtb,
	 * r8 = phys_offset, r9 = cpuid, r10 = procinfo
	 */
	bl	__vet_atags

現在來確認一下暫存器中儲存內容的含義:

r1:機器碼

r2:atag或者dtb的地址

r8:實體記憶體地址偏移

r9:獲取到的CPU ID

r10:處理器資訊結構地址

然後呼叫__vet_atags來驗證r2中地址值得有效性

[cpp] view plain copy print?
  1. /* Determine validity of the r2 atags pointer.  The heuristic requires 
  2.  * that the pointer be aligned, in the first 16k of physical RAM and 
  3.  * that the ATAG_CORE marker is first and present.  If CONFIG_OF_FLATTREE 
  4.  * is selected, then it will also accept a dtb pointer.  Future revisions 
  5.  * of this function may be more lenient with the physical address and 
  6.  * may also be able to move the ATAGS block if necessary. 
  7.  * 
  8.  * Returns: 
  9.  *  r2 either valid atags pointer, valid dtb pointer, or zero 
  10.  *  r5, r6 corrupted 
  11.  */
  12. __vet_atags:  
  13.     tst r2, #0x3            @ aligned?  
  14.     bne 1f  
  15.     ldr r5, [r2, #0]  
  16. #ifdef CONFIG_OF_FLATTREE
  17.     ldr r6, =OF_DT_MAGIC        @ is it a DTB?  
  18.     cmp r5, r6  
  19.     beq 2f  
  20. #endif
  21.     cmp r5, #ATAG_CORE_SIZE     @ is first tag ATAG_CORE?  
  22.     cmpne   r5, #ATAG_CORE_SIZE_EMPTY  
  23.     bne 1f  
  24.     ldr r5, [r2, #4]  
  25.     ldr r6, =ATAG_CORE  
  26.     cmp r5, r6  
  27.     bne 1f  
  28. 2:  mov pc, lr              @ atag/dtb pointer is ok  
  29. 1:  mov r2, #0  
  30.     mov pc, lr  
  31. ENDPROC(__vet_atags)  
/* Determine validity of the r2 atags pointer.  The heuristic requires
 * that the pointer be aligned, in the first 16k of physical RAM and
 * that the ATAG_CORE marker is first and present.  If CONFIG_OF_FLATTREE
 * is selected, then it will also accept a dtb pointer.  Future revisions
 * of this function may be more lenient with the physical address and
 * may also be able to move the ATAGS block if necessary.
 *
 * Returns:
 *  r2 either valid atags pointer, valid dtb pointer, or zero
 *  r5, r6 corrupted
 */
__vet_atags:
	tst	r2, #0x3			@ aligned?
	bne	1f

	ldr	r5, [r2, #0]
#ifdef CONFIG_OF_FLATTREE
	ldr	r6, =OF_DT_MAGIC		@ is it a DTB?
	cmp	r5, r6
	beq	2f
#endif
	cmp	r5, #ATAG_CORE_SIZE		@ is first tag ATAG_CORE?
	cmpne	r5, #ATAG_CORE_SIZE_EMPTY
	bne	1f
	ldr	r5, [r2, #4]
	ldr	r6, =ATAG_CORE
	cmp	r5, r6
	bne	1f

2:	mov	pc, lr				@ atag/dtb pointer is ok

1:	mov	r2, #0
	mov	pc, lr
ENDPROC(__vet_atags)

首先驗證是否4位元組地址對齊,若不對齊則直接將r2內容清空並返回。接著讀取r2地址處的內容到r5暫存器中,這裡若配置了CONFIG_OF_FLATTREE就會判斷是否是DTB,我的環境中並沒有配置。

然後進行atag的驗證,若是atag,則r2地址處的內容將儲存tag_header中的size值(arch/arm/include/uapi/asm/setup.h),同時核心也要求atag資訊的第一項必須是ATAT_CORE型別的項

[cpp] view plain copy print?
  1. struct tag_header {  
  2.     __u32 size;  
  3.     __u32 tag;  
  4. };  
  5. ......  
  6. struct tag_core {  
  7.     __u32 flags;        /* bit 0 = read-only */
  8.     __u32 pagesize;  
  9.     __u32 rootdev;  
  10. };  
struct tag_header {
	__u32 size;
	__u32 tag;
};

......

struct tag_core {
	__u32 flags;		/* bit 0 = read-only */
	__u32 pagesize;
	__u32 rootdev;
};

該CORE項的size值為sizeof(struct tag_header) + sizeof(struct tag_core) >> 2,正好等於ATAG_CORE_SIZE:

[cpp] view plain copy print?
  1. #define ATAG_CORE_SIZE ((2*4 + 3*4) >> 2)
#define ATAG_CORE_SIZE ((2*4 + 3*4) >> 2)

比較完size值後就將地址值偏移4位元組讀取tag值,比較是否等於ATAG_CORE,若是則驗證通過則跳轉到標號2處直接返回。

[cpp] view plain copy print?
  1. #ifdef CONFIG_SMP_ON_UP
  2.     bl  __fixup_smp  
  3. #endif
  4. #ifdef CONFIG_ARM_PATCH_PHYS_VIRT
  5.     bl  __fixup_pv_table  
  6. #endif
  7.     bl  __create_page_tables  
#ifdef CONFIG_SMP_ON_UP
	bl	__fixup_smp
#endif
#ifdef CONFIG_ARM_PATCH_PHYS_VIRT
	bl	__fixup_pv_table
#endif
	bl	__create_page_tables

然後我這裡沒有配置CONFIG_SMP_ON_UP和CONFIG_ARM_PATCH_PHYS_VIRT選項(他們的核心配置解釋分別為Allowbooting SMP kernel on uniprocessor systems和Patch physical tovirtual translations at runtime),接下來就要跳轉到__create_page_tables中建立初始頁表了。

[cpp] view plain copy print?
  1. /* 
  2.  * Setup the initial page tables.  We only setup the barest 
  3.  * amount which are required to get the kernel running, which 
  4.  * generally means mapping in the kernel code. 
  5.  * 
  6.  * r8 = phys_offset, r9 = cpuid, r10 = procinfo 
  7.  * 
  8.  * Returns: 
  9.  *  r0, r3, r5-r7 corrupted 
  10.  *  r4 = page table (see ARCH_PGD_SHIFT in asm/memory.h) 
  11.  */
  12. __create_page_tables:  
  13.     pgtbl   r4, r8              @ page table address  
/*
 * Setup the initial page tables.  We only setup the barest
 * amount which are required to get the kernel running, which
 * generally means mapping in the kernel code.
 *
 * r8 = phys_offset, r9 = cpuid, r10 = procinfo
 *
 * Returns:
 *  r0, r3, r5-r7 corrupted
 *  r4 = page table (see ARCH_PGD_SHIFT in asm/memory.h)
 */
__create_page_tables:
	pgtbl	r4, r8				@ page table address

這裡的註釋中說明了,建立初始頁表的過程只會建立核心程式碼部分地址的頁表。

這裡的pgtbl  r4, r8表示獲取存放頁表首地址的執行時地址(實體地址)到r4中去,它在反彙編中被翻譯成:

[cpp] view