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?- ENTRY(stext)
- jiffies = jiffies_64;
- SECTIONS
- {
- ......
- . = 0xC0000000 + 0x00008000;
- .head.text : {
- _text = .;
- *(.head.text)
- }
- .text : { /* Real text segment */
- _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:
- textofs-y := 0x00008000
- ......
- # The byte offset of the kernel image in RAM from the start of RAM.
- 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?- #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)
#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?
- config PAGE_OFFSET
- hex
- default 0x40000000 if VMSPLIT_1G
- default 0x80000000 if VMSPLIT_2G
- 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?- /*
- * 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
/*
* 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?- /*
- * 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)
/*
* 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?- 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
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?- /*
- * 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)
/*
* 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?- /*
- * 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 */
- constchar *arch_name;
- constchar *elf_name;
- unsigned int elf_hwcap;
- constchar *cpu_name;
- struct processor *proc;
- struct cpu_tlb_fns *tlb;
- struct cpu_user_fns *user;
- struct cpu_cache_fns *cache;
- };
/*
* 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?- /*
- * 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
/*
* 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?- .init.proc.info : {
- . = ALIGN(4); __proc_info_begin = .; *(.proc.info.init) __proc_info_end = .;
- }
.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?- .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
- .....
.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?
- movs r10, r5 @ invalid processor (r5=0)?
- THUMB( it eq ) @ force fixup-able long branch encoding
- 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?- #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
#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?- #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 thiscase
- #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?- 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
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?- /*
- * r1 = machine no, r2 = atags or dtb,
- * r8 = phys_offset, r9 = cpuid, r10 = procinfo
- */
- 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?- /* 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)
/* 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?- struct tag_header {
- __u32 size;
- __u32 tag;
- };
- ......
- struct tag_core {
- __u32 flags; /* bit 0 = read-only */
- __u32 pagesize;
- __u32 rootdev;
- };
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?- #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?- #ifdef CONFIG_SMP_ON_UP
- bl __fixup_smp
- #endif
- #ifdef CONFIG_ARM_PATCH_PHYS_VIRT
- bl __fixup_pv_table
- #endif
- 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?- /*
- * 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
/*
* 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