我分析的linux 版本是linux3.14.77
编译脚本是:arch/arm/kernel/vmlinux.lds
根据顶层Makefile可知,最开始编译的文件是arch/arm/kernel/head.S,所以这个文件是最开始执行的:
所有arm架构的芯片都是这个入口开始执行:
arch/arm/kernel/head.S:
80 ENTRY(stext)
81 ARM_BE8(setend be ) @ ensure we are in BE8 mode
82
83 THUMB( adr r9, BSYM(1f) ) @ Kernel is always entered in ARM.
84 THUMB( bx r9 ) @ If this is a Thumb-2 kernel,
85 THUMB( .thumb ) @ switch to Thumb now.
86 THUMB(1: )
.....
94 mrc p15, 0, r9, c0, c0 @ get processor id
95 bl __lookup_processor_type @ r5=procinfo r9=cpuid
96 movs r10, r5 @ invalid processor (r5=0)?
97 THUMB( it eq ) @ force fixup-able long branch encoding
98 beq __error_p @ yes, error 'p'
94行从c0寄存器中读取cpuid,并存于r9中。95行根据cpuid获取对应的proinfo结构体地址
arch/arm/kernel/head-common.S:
139 /*
140 * Read processor ID register (CP#15, CR0), and look up in the linker-built
141 * supported processor list. Note that we can't use the absolute addresses
142 * for the __proc_info lists since we aren't running with the MMU on
143 * (and therefore, we are not in the correct address space). We have to
144 * calculate the offset.
145 *
146 * r9 = cpuid
147 * Returns:
148 * r3, r4, r6 corrupted
149 * r5 = proc_info pointer in physical address space
150 * r9 = cpuid (preserved)
151 */
152 __lookup_processor_type:
153 adr r3, __lookup_processor_type_data
154 ldmia r3, {r4 - r6}
155 sub r3, r3, r4 @ get offset between virt&phys
156 add r5, r5, r3 @ convert virt addresses to
157 add r6, r6, r3 @ physical address space
158 1: ldmia r5, {r3, r4} @ value, mask
159 and r4, r4, r9 @ mask wanted bits
160 teq r3, r4
161 beq 2f
162 add r5, r5, #PROC_INFO_SZ @ sizeof(proc_info_list)
163 cmp r5, r6
164 blo 1b
165 mov r5, #0 @ unknown processor
166 2: mov pc, lr
167 ENDPROC(__lookup_processor_type)
...
169 /*
170 * Look in <asm/procinfo.h> for information about the __proc_info structure.
171 */
172 .align 2
173 .type __lookup_processor_type_data, %object
174 __lookup_processor_type_data:
175 .long .
176 .long __proc_info_begin
177 .long __proc_info_end
178 .size __lookup_processor_type_data, . - __lookup_processor_type_data
__proc_info structure定义在/arch/arm/include/asm/procinfo.h:
/*
* 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;
};
根据以上注释,可以得知,通过cpuid,获取cpu特有的信息(cpuinfo),在153执行后,r3=175行的运行物理地址,154行运行后,r4=175行的虚拟地址,r5= _proc_info_begin,r6=_proc_info_end,所以在155行:r3-r4可知是物理地址与虚拟地址的差值,156是得到_proc_info_begin的物理地址(指针),157是得到__proc_info_end的物理地址。
158行一些的内容就好理解了:获取结构的value和mask变量值,然后将mask和cpuid进行与操作,将结果和value比较,如果相等,说明找到的所要的cpuinfo,那么就返回,否则就继续查找。
问题一:proc_info_begin,_proc_info_end变量值是哪里来的?
可以从arch/arm/kernel/vmlinux.lds中有一些内容:
. = ALIGN(4); __proc_info_begin = .; KEEP(*(.proc.info.init)) __proc_info_end = .;
所以编译时从脚本获取到的。
问题二:
各种cpuinfo是在哪里定义的呢?
主要是在arch/arm/mm/proc-*文件里。
如arch/arm/mm/proc-v7.S 文件有如下内容
_
.section ".proc.info.init", #alloc, #execinstr
/*
* Standard v7 proc info content
*/
.macro __v7_proc initfunc, mm_mmuflags = 0, io_mmuflags = 0, hwcaps = 0, proc_fns = v7_processor_functions
ALT_SMP(.long PMD_TYPE_SECT | PMD_SECT_AP_WRITE | PMD_SECT_AP_READ | \
PMD_SECT_AF | PMD_FLAGS_SMP | \mm_mmuflags)
ALT_UP(.long PMD_TYPE_SECT | PMD_SECT_AP_WRITE | PMD_SECT_AP_READ | \
PMD_SECT_AF | PMD_FLAGS_UP | \mm_mmuflags)
.long PMD_TYPE_SECT | PMD_SECT_AP_WRITE | \
PMD_SECT_AP_READ | PMD_SECT_AF | \io_mmuflags
W(b) \initfunc
.long cpu_arch_name
.long cpu_elf_name
.long HWCAP_SWP | HWCAP_HALF | HWCAP_THUMB | HWCAP_FAST_MULT | \
HWCAP_EDSP | HWCAP_TLS | \hwcaps
.long cpu_v7_name
.long \proc_fns
.long v7wbi_tlb_fns
.long v6_user_fns
.long v7_cache_fns
.endm
/****以上是个宏,主要应用与下面,看下面的__v7_proc就知道了*********/
#ifndef CONFIG_ARM_LPAE
/*
* ARM Ltd. Cortex A5 processor.
*/
.type __v7_ca5mp_proc_info, #object
__v7_ca5mp_proc_info:
.long 0x410fc050
.long 0xff0ffff0
__v7_proc __v7_ca5mp_setup
.size __v7_ca5mp_proc_info, . - __v7_ca5mp_proc_info
继续跟踪,在__lookup_processor_type返回后下一条语句是:
movs r10, r5,//r10=r5,
而继续下可以看到如下注释:
117 /*
118 * r1 = machine no, r2 = atags or dtb,
119 * r8 = phys_offset, r9 = cpuid, r10 = procinfo
120 */
问题三:
根据以上信息r1和r2已经有相应的值了,那它们是如何得到的(我们从入口开始跟踪,并没有看到r1和r2的赋值),
在文件最上面有如下注释:
59 /*
60 * Kernel startup entry point.
61 * ---------------------------
62 *
63 * This is normally called from the decompressor code. The requirements
64 * are: MMU = off, D-cache = off, I-cache = dont care, r0 = 0,
65 * r1 = machine nr, r2 = atags or dtb pointer.
66 *
67 * This code is mostly position independent, so if you link the kernel at
68 * 0xc0008000, you call this at __pa(0xc0008000).
69 *
70 * See linux/arch/arm/tools/mach-types for the complete list of machine
71 * numbers for r1.
72 *
73 * We're trying to keep crap to a minimum; DO NOT add any machine specific
74 * crap here - that's what the boot loader (or in extreme, well justified
75 * circumstances, zImage) is for.
76 */
可知:在内核解压缩程序最终跳转到linux内核入口点之前,必须满足的条件是r1和r2已经有相应的值了,这说明是在解压缩程序里面进行赋值的或者可能是uboot阶段进行赋值的
跟踪继续,在到start_kernel函数之前的内容简述如下:
__create_page_tables->__enable_mmu->__turn_mmu_on->__mmap_switched
上述内容看名称就大概就知道意思了。创建页表和开启MMU,最后跳转到__mmap_switched,这些都还是汇编阶段。
70 /*
71 * The following fragment of code is executed with the MMU on in MMU mode,
72 * and uses absolute addresses; this is not position independent.
73 *
74 * r0 = cp#15 control register
75 * r1 = machine ID
76 * r2 = atags/dtb pointer
77 * r9 = processor ID
78 */
79 __INIT
80 __mmap_switched:
81 adr r3, __mmap_switched_data
82
83 ldmia r3!, {r4, r5, r6, r7}
84 cmp r4, r5 @ Copy data segment if needed
85 1: cmpne r5, r6
86 ldrne fp, [r4], #4
87 strne fp, [r5], #4
88 bne 1b
89
90 mov fp, #0 @ Clear BSS (and zero fp)
91 1: cmp r6, r7
92 strcc fp, [r6],#4
93 bcc 1b
94
95 ARM( ldmia r3, {r4, r5, r6, r7, sp})
96 THUMB( ldmia r3, {r4, r5, r6, r7} )
97 THUMB( ldr sp, [r3, #16] )
98 str r9, [r4] @ Save processor ID
99 str r1, [r5] @ Save machine type
100 str r2, [r6] @ Save atags pointer
101 cmp r7, #0
102 bicne r4, r0, #CR_A @ Clear 'A' bit
103 stmneia r7, {r0, r4} @ Save control register values
104 b start_kernel
105 ENDPROC(__mmap_switched)
106
107 .align 2
108 .type __mmap_switched_data, %object
109 __mmap_switched_data:
110 .long __data_loc @ r4
111 .long _sdata @ r5
112 .long __bss_start @ r6
113 .long _end @ r7
114 .long processor_id @ r4
115 .long __machine_arch_type @ r5
116 .long __atags_pointer @ r6
117 #ifdef CONFIG_CPU_CP15
118 .long cr_alignment @ r7
119 #else
120 .long 0 @ r7
121 #endif
122 .long init_thread_union + THREAD_START_SP @ sp
123 .size __mmap_switched_data, . - __mmap_switched_data
这个函数__mmap_switched主要做了的事情是:复制数据段内容以及将bss清零,然后将proid,machid 以及atags/dtb pointer分别保存在变量processor_id,__machine_arch_type,__atags_pointer,中,而这些变量定义在/arch/arm/kernel/setup.c:
82 unsigned int processor_id;
83 EXPORT_SYMBOL(processor_id);
84 unsigned int __machine_arch_type __read_mostly;
85 EXPORT_SYMBOL(__machine_arch_type);
86 unsigned int cacheid __read_mostly;
87 EXPORT_SYMBOL(cacheid);
88
89 unsigned int __atags_pointer __initdata;
以上内容是最后一个汇编函数的执行,之后就跳转到C函数(start_kernel)执行了