分类: LINUX
2011-02-22 21:31:06
Author: jimmy.li
Date:
2007-06-08
-----------------------
head-armv.S主支分析
head-armv.S是解压后(或未压缩)的内核最先执行的一个文件,这个文件位于arch/arm/kernel/head-armv.S,在与这个文件同目录下还有一个文件head-armo.S与head-armv.S很相似,但从arch/arm/下的Makefile中可以看到区别在哪里:
ifeq
($(CONFIG_CPU_26),y) PROCESSOR := armo ifeq
($(CONFIG_ROM_KERNEL),y)
DATAADDR = 0x02080000
TEXTADDR = 0x03800000
LDSCRIPT =
arch/arm/vmlinux-armo-rom.lds.in
else TEXTADDR = 0x02080000
LDSCRIPT =
arch/arm/vmlinux-armo.lds.in
endif endif ifeq ($(CONFIG_CPU_32),y) PROCESSOR = armv TEXTADDR = 0xC0008000 LDSCRIPT =
arch/arm/vmlinux-armv.lds.in endif …… HEAD
:=arch/arm/kernel/head-$(PROCESSOR).o \
arch/arm/kernel/init_task.o |
闲话少说,在进入分析head-armv.S之前,交待一下我所分析的内核版本号以及硬件平台,内核是
开篇说到,head-armv.S是进入内核最先执行的文件,为什么呢?内核可执行文件由许多链接在一起的对象文件组成。对象文件有许多节,如文本、数据、init 数据、bass 等等。这些对象文件都是由一个称为link script的文件链接并装入的。这个link script的功能是将输入对象文件的各节映射到输出文件中;换句话说,它将所有输入对象文件都链接到单一的可执行文件中,将该可执行文件的各节装入到指定地址处。vmlinux-armv.lds就是链接内核用到的link script,它位于arch/arm/目录下,你可能注意到了同目录下还有一vmlinux-armv.lds.in文件,这两文件可是有关系的,答案就在arch/arm/Makefile里。
ifeq
($(CONFIG_CPU_32),y) /* 对于pxa
270来说这里是True
*/ PROCESSOR =
armv TEXTADDR =
0xC0008000 LDSCRIPT =
arch/arm/vmlinux-armv.lds.in endif arch/arm/vmlinux.lds: arch/arm/Makefile $(LDSCRIPT)
\ $(wildcard
include/config/cpu/32.h) \ $(wildcard
include/config/cpu/26.h) \ $(wildcard
include/config/arch/*.h) @echo
' Generating
$@' @sed
's/TEXTADDR/$(TEXTADDR)/;s/DATAADDR/$(DATAADDR)/' $(LDSCRIPT)
>$@ |
从这个Makefile中我们可以看到,实际上arch/arm/vmlinux-armv.lds.in就是arch/arm/vmlinux-armv.lds是一个蓝本,在make的时候vmlinux-armv.lds是由sed命令来替换vmlinux-armv.lds.in文件中的TEXTADDR, DATAADDR为特定的值而生成的。TEXTADDR是内核Image的映像地址,也是内核Image所处的虚拟地址,它在系统内核空间(3G~4G)的起始位置,通常是0xC0000000(这相应于物理内存开始的地方)+32K的位置,也就是0xC0008000处;在内核映像之前的16K空间用来存放内核的页目录表,这也就是为什么TEXTADDR要把系统放置在0xC0008000的缘故,它必须留出足够的物理空间来存放页表。
接下来就来真正看一下vmlinux-armv.lds里面的内容:
OUTPUT_ARCH(arm) ENTRY(stext) SECTIONS { . =
0xC0008000; .init : { /* Init code and
data*/ _stext =
.; __init_begin =
.; *(.text.init) __proc_info_begin =
.;
*(.proc.info)
__proc_info_end =
.; __arch_info_begin =
.;
*(.arch.info) __arch_info_end =
.; …… } …… } |
ENTRY(stext),就是说明了最先执行的第一条指令是从stext开始,而这个stext就是位于head-armv.S当中,它被定义于放置于.text.init section,而且.text.init section在vmlinux.lds文件中也是被放置于输出文件的起始位置。
/* arch/arm/kernel/head-armv.S
*/ 93 .section
".text.init",#alloc,#execinstr
94 .type stext, #function
95 ENTRY(stext) //内核入口点
96 /*
97 mov r0,
#0
98 mov r1,
#300
99 add r1, r1,
#4
100 */
101 mov r12, r0
//保护r0, r0=0,
r12=0 …. /* 这中间的都是与XScale平台无关的code */ 186 mov
r0, #F_BIT | I_BIT | MODE_SVC @
make sure svc mode 187 msr cpsr_c, r0 @ and all irqs
disabled 188 bl __lookup_processor_type 189 teq r10, #0 @ invalid
processor? 190 moveq r0, #'p' @ yes, error
'p' 191 beq __error 192 bl
__lookup_architecture_type 193 teq r7, #0 @ invalid
architecture? 194 moveq r0, #'a' @ yes, error
'a' 195 beq __error 196 bl
__create_page_tables 197 adr lr, __ret @ return
address 198 add pc, r10, #12 @ initialise
processor 199 @ (return control reg) |
在程序注释中有一段对于入口点的说明,说这个入口点一般是在内核自解压缩代码中被调用(关于内核的自解压缩,我将在以后的文章中进行分析),在进入这个入口点前,须满足以下条件:MMU=off,D-Cache=off,I-Cache=don’t care,r0 =0,r1=machine number (see arch/arm/tools/mach-types.h)。
Line93, 定义一个section,名为.text.init,#alloc表示section is allocatable, #execinstr表示section is executable。从前面vmlinux-armv.lds文件中我们可以看到,这个"text.init" section会放到0xC0008000(在arch/arm/Makefile中TEXTADDR指定)这个起始位置。
Line95, 这里的ENTRY其实是一个宏,这个宏位于linux/linkage.h中,在head-armv.S中就包含了这个头文件。
/* linux/linkage.h
*/ #define SYMBOL_NAME(X)
X #ifdef
__STDC__ #define SYMBOL_NAME_LABEL(X)
X##: #else #define SYMBOL_NAME_LABEL(X)
X/**/: #endif #ifdef
__arm__ #define __ALIGN .align
0 #define __ALIGN_STR ".align
0" #else … #endif … #define ALIGN
__ALIGN #define ALIGN_STR
__ALIGN_STR #define ENTRY(name)
\
.globl SYMBOL_NAME(name); \
ALIGN; \
SYMBOL_NAME_LABEL(name) |
line186~187: r0=0b11010011,用于设置当前程序状态寄存器,以禁止FIQ, IRQ,进入supervisor模式。
Line188:跳转到__lookup_processor_type,读取运行的cpu的ID值,判断此ID值是否被内核所支持,如果不支持,返回时r10为0。
Line189~191: 如果是无效的processor,则跳转到__error。
Line192: 跳转到__lookup_architecture_type,看r1寄存器的architecture number值是否支持,如果不支持,则返回时r7=0。Bootloader引导linux kernel时,会传递给r1为machine number。
Line193~195: 如果是无效的(不支持的)体系类型(r7=0),则跳转到__error。
Line196:创建核心页表。
Line197: 将标号__ret的地址放入 lr 寄存器。__ret标号处相关源码如下:
/* arch/arm/kernel/head-armv.S
*/ 216 .type __ret, %function
217 __ret: ldr
lr, __switch_data
218 mcr p15, 0, r0, c1,
c0
219 mrc p15, 0, r0, c1, c0, 0 @ read it
back.
220 mov r0, r0
221 mov r0, r0
222 mov pc,
lr |
也就是说,在将来某个时候有可能会调用mov pc, lr语句(实际上会在arch/arm/mm/proc-xscale.S文件中__xscale_setup函数的line942调用,这在后面会讲到)会跳转到line217执行ldr lr, __switch_data。
Line198:r10+12->pc,也即程序跳转到r10+12的地址处执行,此时的r10存储的是在执行__lookup_processor_type函数后得到的当前处理器信息、是一个proc_info_list的结构体信息,对于我们的pxa270平台,r10指向的就是arch/arm/mm/proc-xscale.S文件中line1099处那个位置,也就是说从line1099~1112的内容就是一个proc_info_list的结构体实例。
/* arch/arm/mm/proc-xscale.S
*/ 1097 .type
__bva0_proc_info,#object
1098 __bva0_proc_info:
1099 .long 0x69054110 @ Bulverde A0: 0x69054110, A1 :
0x69054111.
1100 .long 0xfffffff0 @ and this is the CPU id
mask.
1101 #if CACHE_WRITE_THROUGH
1102 .long 0x
1103 #else
1104 .long 0x
1105 #endif
1106 b __xscale_setup
1107 .long cpu_arch_name
1108 .long cpu_elf_name
1109 .long
HWCAP_SWP|HWCAP_HALF|HWCAP_THUMB|HWCAP_FAST_MULT|HWCAP_EDSP|HWCAP_XSCALE
1110 .long cpu_bva0_info
1111 .long
xscale_processor_functions 1112 .size __bva0_proc_info, . - __bva0_proc_info |
而r10+12就是line1106(b __xscale_setup)。__xscale_setup函数是实际的CPU的设置子程序,它主要是操作协处理器,设置页表目录项基地址,对CACHE和BUFFER的控制位进行一些操作(关于__xscale_setup的源码分析放在后面的分支分析中)。也就是说line198执行的结果是跳转到arch/arm/mm/proc-xscale.S的line1106。(关于该行源码建议读者先分析完__lookup_processor_type函数再看。__xscale_setup函数返回程序会跳转到line217。
Line217:将__switch_data位置处的值放入lr(注意这里是指令ldr,不是把标号__switch_data的地址放入lr),将来在line222时会跳转到__swith_data所指向的地址。__swith_data标号相关源码如下:
/* arch/arm/kernel/head-armv.S
*/ 201 .type __switch_data,
%object
202 __switch_data: .long __mmap_switched
203 .long
SYMBOL_NAME(__bss_start)
204 .long SYMBOL_NAME(_end)
205 .long
SYMBOL_NAME(processor_id)
206 .long
SYMBOL_NAME(__machine_arch_type)
207 .long
SYMBOL_NAME(cr_alignment)
208 .long
SYMBOL_NAME(init_task_union)+8192 |
Line222:因为line217将lr的值存储为__swith_data标号处的值,即__mmap_switched标号的地址,则此行执行的结果是跳转到__mmap_switched函数(line233)处。而__mmap_switched函数相关源码如下:
/* arch/arm/kernel/head-armv.S
*/ 224 /*
225 * The following fragment of
code is executed with the MMU on, and uses
226 * absolute addresses; this is
not position independent.
227 *
228 * r0 =
processor control register
229 * r1 =
machine ID
230 * r9 =
processor ID
231 */
232 .align 5
233 __mmap_switched:
234 #ifdef CONFIG_XIP_KERNEL //
对于pxa270 此处为false,故code略 …… 243 #endif
244
245 adr r3, __switch_data +
4
246 ldmia r3, {r4, r5, r6, r7, r8, sp}@ r2 =
compat
247
@ sp = stack
pointer
248
249 mov fp, #0 @ Clear BSS (and zero
fp)
250 1: cmp r4, r5
251 strcc fp, [r4],#4
252 bcc 1b 253
254 str r9, [r6] @ Save processor
ID
255 str r1, [r7] @ Save machine
type
256 #ifdef CONFIG_ALIGNMENT_TRAP
257 orr r0, r0, #2 @ ...........A.
258 #endif
259 bic r2, r0, #2 @ Clear 'A'
bit
260 stmia r8, {r0, r2} @ Save control register
values
261 b
SYMBOL_NAME(start_kernel) |
Line233~261:这段代码的作用主要是在进入C函数前先做一些变量的初始化和保存工作。首先清空BSS区域,然后保存处理器ID和机器类型到各自变量地址,接着保存cr_alignment,最后跳转到init/main.c中的start_kernel函数运行。
再来具体分析一下这里面的代码,line245~246,它的结果就是r4
=__bss_start, r5=_end, r6=processor_id,
r7=__machine_arch_type, r8=cr_alignment, sp= init_task_union+8192,这些寄存器存储的都是变量的地址。
再看一下line254,r9之前存储的是在__lookup_processor_type获取的processor
id,这里把process id放置在r6指向的内存,r6此时由于在line246被赋予了processor_id变量的指针,所以这里把process id
保存在变量processor_id中(processor_id在arch/arm/kernel/setup.c中被定义)。
同样,line255,r1之前存储的是在kernel
booting前由bootloader传递过来的machine number,在这里把machine
number放置到r7所指向的内存,r7在line246被赋予了指向__machine_arch_type变量,所以这里把machine
number保存在变量__machine_arch_type中(__machine_arch_type同样也定义在arch/arm/kernel/setup.c中)。