linux内核启动第一阶段分析
http://blog.csdn.net/aaronychen/article/details/2838341
本文的很多内容是参考了网上某位大侠的文章写的<<>>,有些东西是直接从他那copy过来的。
本文从kernel的第一条指令开始分析,一直分析到进入start_kernel()函数,也就是kernel启动的汇编部分,我们把它称之为第一部分,以后有时间在把启动的第二部分在分析一下。当前以linux-3.0内核版本来分析,本文中所有的代码前面都会加上行号以便于讲解。
由于启动部分有一些代码是平台相关的,虽然大部分的平台所的功能都比较类似,但是为了更好的对code进行说明,对于平台相关的代码,我们选择smdk2410平台, CPU是s3c2410(arm核是arm920T)进行分析。
另外,本文是以未压缩的kernel来分析的.对于内核解压缩部分的code,在 arch/arm/boot/compressed中,本文不做讨论。
一. 启动
通常从系统上电执行的boot loader的代码,而要从boot loader跳转到linux kernel的第一条指令处执行需要一些特定的条件。关于对boot loader的分析请看我的另一篇文档u-boot源码分析。
这里讨论下进入到linux kernel时必须具备的一些条件,这一般是boot loader在跳转到kernel之前要完成的:
1. CPU必须处于SVC(supervisor)模式,并且IRQ和FIQ中断都是禁止的;
2. MMU(内存单元)必须是关闭的, 此时地址就是物理地址;
3. 数据cache(Data cache)必须是关闭的
4. 指令cache(Instruction cache)可以是打开的,也可以是关闭的,这个没有强制要求;
5. CPU 通用寄存器0 (r0)必须是 0;
6. CPU 通用寄存器1 (r1)必须是 ARM Linux machine type (关于machine type, 我们后面会有讲解)
7. CPU 通用寄存器2 (r2) 必须是 kernel parameter list 的物理地址(parameter list 是由boot loader传递给kernel,用来描述设备信息属性的列表)。
更详细的关于启动arm linux之前要做哪些准备工作可以参考,“Booting ARM Linux"文档
二. starting kernel
首先,我们先对几个重要的宏进行说明(我们针对有MMU的情况):
TEXT_OFFSET 内核在RAM中的起始位置相对于RAM起始地址偏移。值为0x00008000
./arch/arm/Makefile
118 textofs-y := 0x00008000
222 TEXT_OFFSET := $(textofs-y)
PAGE_OFFSE 内核镜像起始虚拟地址。值为0xC0000000
/arch/arm/configs/s3c2410_defconfig
CONFIG_PAGE_OFFSET=0xC0000000
./arch/arm/include/asm/memory.h
34 #define PAGE_OFFSET UL(CONFIG_PAGE_OFFSET)
PHYS_OFFSET RAM启始物理地址,对于2410来说值为0x30000000,RAM接在片选6上
arch/arm/mach-s3c2410/include/mach/memory.h
from arch/arm/mach-rpc/include/mach/memory.h
#define PHYS_OFFSET UL(0x30000000)
KERNEL_RAM_VADDR 内核在RAM中的虚拟地址。值为0xC0008000
KERNEL_RAM_PADDR 内核在RAM中的物理地址。值为0x30008000
arch/arm/kernel/head.S
29 #define KERNEL_RAM_VADDR (PAGE_OFFSET + TEXT_OFFSET)
30 #define KERNEL_RAM_PADDR (PHYS_OFFSET + TEXT_OFFSET)
PLAT_PHYS_OFFSET arch/arm/mach-s3c2410/include/mach/memory.h 值为0x30000000
arm linux boot的主线可以概括为以下几个步骤:
1. 确定 processor type
2. 确定 machine type
3.检查参数合法性
4. 创建页表
5. 调用平台特定的__cpu_flush函数 (在struct proc_info_list中)
6. 开启mmu
7. 切换数据
最终跳转到start_kernel (在__switch_data的结束的时候,调用了 b start_kernel)
内核的入口是stext,这是在arch/arm/kernel/vmlinux.lds.S中定义的:
31 ENTRY(stext)
对于vmlinux.lds.S,这是ld script文件,此文件的格式和汇编及C程序都不同,本文不对ld script作过多的介绍,只对内核中用到的内容进行讲解,关于ld的详细内容可以参考ld.info
这里的ENTRY(stext) 表示程序的入口是在符号stext.
而符号stext是在arch/arm/kernel/head.S中定义的:
下面我们将arm linux boot的主要代码列出来进行一个概括的介绍,然后,我们会逐个的进行详细的讲解.
在arch/arm/kernel/head.S中 77 - 101 行,是arm linux boot的主代码:
arch/arm/kernel/head.S
77 __HEAD
78 ENTRY(stext)
79 setmode PSR_F_BIT | PSR_I_BIT | SVC_MODE, r9 @ 确保进入管理(svc)模式
80 @ 并且禁止中断
81 mrc p15, 0, r9, c0, c0 @ 读取CPU ID,存入r9寄存器
82 bl __lookup_processor_type @ 调用函数,输入参数r9=cpuid,
@ 返回值r5=procinfo
83 movs r10, r5 @ 如果不支持当前CPU,则返回 (r5=0),/*判断如果r10的值为0,跳转到出错处理,*/
84 beq __error_p @ yes, error 'p'
//查询machine ID并检查合法性
85 bl __lookup_machine_type @ r5=machinfo
86 movs r8, r5 @ invalid machine (r5=0)?
87 beq __error_a @ yes, error 'a'
88 bl __vet_atags //检查bootloader传入的参数列表atags的合法性
89 bl __create_page_tables //创建初始页表
90
91 /*
92 * The following calls CPU specific code in a position independent
93 * manner. See arch/arm/mm/proc-*.S for details. r10 = base of
94 * xxx_proc_info structure selected by __lookup_machine_type
95 * above. On return, the CPU will be ready for the MMU to be
96 * turned on, and r0 will hold the CPU control register value.
97 */
98 ldr r13, __switch_data @ address to jump to after //将列表__switch_data存到r13中后面会跳到该列表出
99 @ mmu has been enabled
100 adr lr, BSYM(__enable_mmu) @ return (PIC) address//将程序段__enable_mmu的地址存到lr中。此命令将导致程序段__arm920_setup的执行,后面会将到。
101 ARM( add pc, r10, #PROCINFO_INITFUNC )//r10中存放的基地址是从__lookup_processor_type中得到的,如上面movs r10, r5
102 THUMB( add r12, r10, #PROCINFO_INITFUNC )
103 THUMB( mov pc, r12 )
104 ENDPROC(stext)
76 __error_p:
77 #ifdef CONFIG_DEBUG_LL
78 adr r0, str_p1
79 bl printascii
80 mov r0, r9
81 bl printhex8
82 adr r0, str_p2
83 bl printascii
84 b __error
85 str_p1: .asciz "\nError: unrecognized/unsupported processor variant (0x"
86 str_p2: .asciz ").\n"
87 .align
88 #endif
89 ENDPROC(__error_p)
91 __error_a:
92 #ifdef CONFIG_DEBUG_LL
93 mov r4, r1 @ preserve machine ID
94 adr r0, str_a1
95 bl printascii
96 mov r0, r4
97 bl printhex8
98 adr r0, str_a2
99 bl printascii
100 adr r3, 4f
101 ldmia r3, {r4, r5, r6} @ get machine desc list
102 sub r4, r3, r4 @ get offset between virt&phys
103 add r5, r5, r4 @ convert virt addresses to
104 add r6, r6, r4 @ physical address space
105 1: ldr r0, [r5, #MACHINFO_TYPE] @ get machine type
106 bl printhex8
107 mov r0, #'\t'
108 bl printch
109 ldr r0, [r5, #MACHINFO_NAME] @ get machine name
110 add r0, r0, r4
111 bl printascii
112 mov r0, #'\n'
113 bl printch
114 add r5, r5, #SIZEOF_MACHINE_DESC @ next machine_desc
115 cmp r5, r6
116 blo 1b
117 adr r0, str_a3
118 bl printascii
119 b __error
120 ENDPROC(__error_a)
121
122 str_a1: .asciz "\nError: unrecognized/unsupported machine ID (r1 = 0x"
123 str_a2: .asciz ").\n\nAvailable machine support:\n\nID (hex)\tNAME\n"
124 str_a3: .asciz "\nPlease check your kernel config and/or bootloader.\n"
125 .align
126 #endif
128 __error:
129 #ifdef CONFIG_ARCH_RPC
130 /*
131 * Turn the screen red on a error - RiscPC only.
132 */
133 mov r0, #0x02000000
134 mov r3, #0x11
135 orr r3, r3, r3, lsl #8
136 orr r3, r3, r3, lsl #16
137 str r3, [r0], #4
138 str r3, [r0], #4
139 str r3, [r0], #4
140 str r3, [r0], #4
141 #endif
142 1: mov r0, r0
143 b 1b
144 ENDPROC(__error)
__lookup_processor_type
---------------------------------------------------------------
在讲解lookup_processor_type程序段之前先来看一些相关知识。
内核做支持的每一种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; /* 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;
};
对于arm920来说,其对应结构体在文件 linux/arch/arm/mm/proc-arm920.S中初始化。
442 .align
443
444 .section ".proc.info.init", #alloc, #execinstr
445
446 .type __arm920_proc_info,#object
447 __arm920_proc_info:
448 .long 0x41009200
449 .long 0xff00fff0
450 .long PMD_TYPE_SECT | \
451 PMD_SECT_BUFFERABLE | \
452 PMD_SECT_CACHEABLE | \
453 PMD_BIT4 | \
454 PMD_SECT_AP_WRITE | \
455 PMD_SECT_AP_READ
456 .long PMD_TYPE_SECT | \
457 PMD_BIT4 | \
458 PMD_SECT_AP_WRITE | \
459 PMD_SECT_AP_READ
460 b __arm920_setup
461 .long cpu_arch_name
462 .long cpu_elf_name
463 .long HWCAP_SWP | HWCAP_HALF | HWCAP_THUMB
464 .long cpu_arm920_name
465 .long arm920_processor_functions
466 .long v4wbi_tlb_fns
467 .long v4wb_user_fns
468 #ifndef CONFIG_CPU_DCACHE_WRITETHROUGH
469 .long arm920_cache_fns
470 #else
471 .long v4wt_cache_fns
472 #endif
473 .size __arm920_proc_info, . - __arm920_proc_info
.section ".proc.info.init"表明了该结构在编译后存放的位置。
在链接文件arch/arm/kernel/vmlinux.lds.S中,编译后它生成arch/arm/kernel/vmlinux.lds文件
20 SECTIONS
21 {
22 #ifdef CONFIG_XIP_KERNEL
23 . = XIP_VIRT_ADDR(CONFIG_XIP_PHYS_ADDR);
24 #else
25 . = PAGE_OFFSET + TEXT_OFFSET;
26 #endif
27
28 .init : { /* Init code and data */
29 _stext = .;
30 _sinittext = .;
31 HEAD_TEXT
32 INIT_TEXT
33 _einittext = .;
34 __proc_info_begin = .;
35 *(.proc.info.init)
36 __proc_info_end = .;
37 __arch_info_begin = .;
38 *(.arch.info.init)
39 __arch_info_end = .;
40 __tagtable_begin = .;
41 *(.taglist.init)
42 __tagtable_end = .;
所有CPU类型对应的被初始化的proc_info_list结构体都放在__proc_info_begin和__proc_info_end之间。
现在再来分析lookup_processor_type
147 /*
148 * Read processor ID register (CP#15, CR0), and look up in the linker-built
149 * supported processor list. Note that we can't use the absolute addresses
150 * for the __proc_info lists since we aren't running with the MMU on
151 * (and therefore, we are not in the correct address space). We have to
152 * calculate the offset.
153 *
154 * r9 = cpuid
155 * Returns:
156 * r3, r4, r6 corrupted
157 * r5 = proc_info pointer in physical address space
158 * r9 = cpuid (preserved)
159 */
160 __lookup_processor_type:
161 adr r3, 3f //r3存储的是标号3的物理地址(由于没有启用mmu,所以当前肯定是物理地址)
162 ldmia r3, {r5 - r7} //R5=__proc_info_begin,r6=__proc_info_end,r7=标号3处的虚拟地址。
163 add r3, r3, #8
164 sub r3, r3, r7 //得到虚拟地址和物理地址之间的offset
165 add r5, r5, r3 //利用offset,将r5和r6中保存的虚拟地址转变为物理地址
166 add r6, r6, r3 @ physical address space
167 1: ldmia r5, {r3, r4} //r3=cpu_val,r4= cpu_mask,
168 and r4, r4, r9 //r9中存放的是先前读出的processor ID,此处屏蔽不需要的位。
//查看代码和CPU硬件是否匹配(比如想在arm920t上运行为cortex-a8编译的内核?不让!)
169 teq r3, r4
170 beq 2f //如果匹配成功就返回
//PROC_INFO_SZ (proc_info_list结构的长度,在这等于48),跳到下一个proc_info_list处
171 add r5, r5, #PROC_INFO_SZ @ sizeof(proc_info_list)
//判断是否已经到了结构体proc_info_list存放区域的末尾__proc_info_end,
172 cmp r5, r6
173 blo 1b
//如果没有匹配成功就将r5清零,如果匹配成功r5中放的是该CPU类型对应的结构体//proc_info_list的基地址。
174 mov r5, #0 @ unknown processor
175 2: mov pc, lr //子程序返回。
176 ENDPROC(__lookup_processor_type)
177
178 /*
179 * This provides a C-API version of the above function.
180 */
181 ENTRY(lookup_processor_type)
182 stmfd sp!, {r4 - r7, r9, lr}
183 mov r9, r0
184 bl __lookup_processor_type
185 mov r0, r5
186 ldmfd sp!, {r4 - r7, r9, pc}
187 ENDPROC(lookup_processor_type)
188
189 /*
190 * Look in
and arch/arm/kernel/arch.[ch] for
191 * more information about the __proc_info and __arch_info structures.
192 */
193 .align 2
194 3: .long __proc_info_begin
195 .long __proc_info_end
196 4: .long .
197 .long __arch_info_begin
198 .long __arch_info_end
__lookup_machine_type
---------------------------------------------------------------
每一个CPU平台都可能有其不一样的结构体,描述这个平台的结构体是machine_desc。
这个结构体在文件arch/arm/include/asm/mach/arch.h中定义:
17 struct machine_desc {
18 /*
19 * Note! The first four elements are used
20 * by assembler code in head.S, head-common.S
21 */
22 unsigned int nr; /* architecture number */
23 unsigned int nr_irqs; /* number of IRQs */
24 unsigned int phys_io; /* start of physical io */
25 unsigned int io_pg_offst; /* byte offset for io
26 * page tabe entry */
27
28 const char *name; /* architecture name */
29 unsigned long boot_params; /* tagged list */
30
31 unsigned int video_start; /* start of video RAM */
32 unsigned int video_end; /* end of video RAM */
33
34 unsigned int reserve_lp0 :1; /* never has lp0 */
35 unsigned int reserve_lp1 :1; /* never has lp1 */
36 unsigned int reserve_lp2 :1; /* never has lp2 */
37 unsigned int soft_reboot :1; /* soft reboot */
38 void (*fixup)(struct machine_desc *,
39 struct tag *, char **,
40 struct meminfo *);
41 void (*reserve)(void);/* reserve mem blocks */
42 void (*map_io)(void);/* IO mapping function */
43 void (*init_irq)(void);
44 struct sys_timer *timer; /* system tick timer */
45 void (*init_machine)(void);
46 };
47
对于平台smdk2410来说其对应machine_desc结构在文件
linux/arch/arm/mach-s3c2410/mach-smdk2410.c中初始化:
MACHINE_START(SMDK2410, "SMDK2410") /* @TODO: request a new identifier and switch
* to SMDK2410 */
/* Maintainer: Jonas Dietsche */
.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中定义:
48 /*
49 * Set of macros to define architecture features. This is built into
50 * a table by the linker.
51 */
52 #define MACHINE_START(_type,_name) \
53 static const struct machine_desc __mach_desc_##_type \
54 __used \
55 __attribute__((__section__(".arch.info.init"))) = { \
56 .nr = MACH_TYPE_##_type, \
57 .name = _name,
58
59 #define MACHINE_END \
60 };
61
62 #endif
__attribute__((__section__(".arch.info.init")))表明该结构体在并以后存放的位置。
在链接文件arch/arm/kernel/vmlinux.lds.S中,编译后它生成arch/arm/kernel/vmlinux.lds文件
20 SECTIONS
21 {
22 #ifdef CONFIG_XIP_KERNEL
23 . = XIP_VIRT_ADDR(CONFIG_XIP_PHYS_ADDR);
24 #else
25 . = PAGE_OFFSET + TEXT_OFFSET;
26 #endif
27
28 .init : { /* Init code and data */
29 _stext = .;
30 _sinittext = .;
31 HEAD_TEXT
32 INIT_TEXT
33 _einittext = .;
34 __proc_info_begin = .;
35 *(.proc.info.init)
36 __proc_info_end = .;
37 __arch_info_begin = .;
38 *(.arch.info.init)
39 __arch_info_end = .;
40 __tagtable_begin = .;
41 *(.taglist.init)
42 __tagtable_end = .;
在__arch_info_begin和 __arch_info_end之间存放了linux内核所支持的所有平台对应的machine_desc结构体。
现在再来看__lookup_machine_type
200 /*
201 * Lookup machine architecture in the linker-build list of architectures.
202 * Note that we can't use the absolute addresses for the __arch_info
203 * lists since we aren't running with the MMU on (and therefore, we are
204 * not in the correct address space). We have to calculate the offset.
205 *
206 * r1 = machine architecture number
207 * Returns:
208 * r3, r4, r6 corrupted
209 * r5 = mach_info pointer in physical address space
210 */
211 __lookup_machine_type:
212 adr r3, 4b //r3存储的是标号3的物理地址
//R4=标号3处的虚拟地址,r5=__arch_info_begin,r6=__arch_info_end。
213 ldmia r3, {r4, r5, r6}
214 sub r3, r3, r4 @ get offset between virt&phys
215 add r5, r5, r3 @ convert virt addresses to
216 add r6, r6, r3 @ physical address space
//读取machine_desc结构的nr参数,对于smdk2410来说该值是MACH_TYPE_SMDK2410。
//这个值在文件linux/arch/arm/tools/mach-types中:
//smdk2410 ARCH_SMDK2410 SMDK2410 193
217 1: ldr r3, [r5, #MACHINFO_TYPE] @ get machine type
218 teq r3, r1 //r1就是bootloader传递过来的机器码,就是上面的平台编号。
219 beq 2f //如果匹配成功就返回
220 add r5, r5, #SIZEOF_MACHINE_DESC //没匹配成功就跳到下一个结构体machine_desc
221 cmp r5, r6
222 blo 1b
223 mov r5, #0 //如果匹配失败就将r5清零。
224 2: mov pc, lr //子程序返回。
225 ENDPROC(__lookup_machine_type)
226
227 /*
228 * This provides a C-API version of the above function.
229 */
230 ENTRY(lookup_machine_type)
231 stmfd sp!, {r4 - r6, lr}
232 mov r1, r0
233 bl __lookup_machine_type
234 mov r0, r5
235 ldmfd sp!, {r4 - r6, pc}
236 ENDPROC(lookup_machine_type)
__vet_atags 检查bootloader传入的参数列表atags的合法性
---------------------------------------------------------------
关于参数链表:
内核参数链表的格式和说明可以从内核源代码目录树中的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 等。每个参数标记就代表一个参数结构体,由各个参数结构体构成了参
数链表。参数结构体的定义如下:
146 struct tag {
147 struct tag_header hdr;
148 union {
149 struct tag_core core;
150 struct tag_mem32 mem;
151 struct tag_videotext videotext;
152 struct tag_ramdisk ramdisk;
153 struct tag_initrd initrd;
154 struct tag_serialnr serialnr;
155 struct tag_revision revision;
156 struct tag_videolfb videolfb;
157 struct tag_cmdline cmdline;
158
159 /*
160 * Acorn specific
161 */
162 struct tag_acorn acorn;
163
164 /*
165 * DC21285 specific
166 */
167 struct tag_memclk memclk;
168 } u;
169 };
参数结构体包括两个部分,一个是 tag_header结构体,一个是u联合体。
tag_header结构体的定义如下:
24 struct tag_header {
25 __u32 size;
26 __u32 tag;
27 };
28
其中 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等。
238 /* Determine validity of the r2 atags pointer. The heuristic requires
239 * that the pointer be aligned, in the first 16k of physical RAM and
240 * that the ATAG_CORE marker is first and present. Future revisions
241 * of this function may be more lenient with the physical address and
242 * may also be able to move the ATAGS block if necessary.
243 *
244 * r8 = machinfo
245 *
246 * Returns:
247 * r2 either valid atags pointer, or zero
248 * r5, r6 corrupted
249 */
250 __vet_atags:
251 tst r2, #0x3 //r2指向该参数链表的起始位置,此处判断它是否字对齐
252 bne 1f
253
254 ldr r5, [r2, #0] //获取第一个tag结构的size
//#define ATAG_CORE_SIZE ((2*4 + 3*4) >> 2) 判断该tag的长度是否合法
255 cmp r5, #ATAG_CORE_SIZE
256 cmpne r5, #ATAG_CORE_SIZE_EMPTY
257 bne 1f
258 ldr r5, [r2, #4] //获取第一个tag结构体的标记,
259 ldr r6, =ATAG_CORE
260 cmp r5, r6 //判断第一个tag结构体的标记是不是ATAG_CORE
261 bne 1f
262
263 mov pc, lr //正常退出
264
265 1: mov r2, #0 //参数连表不正确
266 mov pc, lr
267 ENDPROC(__vet_atags)
arch/arm/kernel/head.S
206 /*
207 * Setup the initial page tables. We only setup the barest
208 * amount which are required to get the kernel running, which
209 * generally means mapping in the kernel code.
210 *
211 * r8 = machinfo
212 * r9 = cpuid
213 * r10 = procinfo
214 *
215 * Returns:
216 * r0, r3, r6, r7 corrupted
217 * r4 = physical page table address
218 */
219 __create_page_tables:
220 pgtbl r4 @ page table address
//r4 = 0x30004000这是转换表的物理基地址,最终将写入CP15的寄存器2,C2。这个值必须是16K对齐的。
//为内核代码存储区域创建页表,首先将内核起始地址-0x4000到内核起始地址之间的16K 存储器清0,将创建的页表存于此处。
221
222 /*
223 * Clear the 16K level 1 swapper page table
224 */
225 mov r0, r4
226 mov r3, #0
227 add r6, r0, #0x4000
228 1: str r3, [r0], #4
229 str r3, [r0], #4
230 str r3, [r0], #4
231 str r3, [r0], #4
232 teq r0, r6
233 bne 1b
//从proc_info_list结构中获取字段__cpu_mm_mmu_flags,该字段包含了存储空间访问权限 等。此处指令执行之后r7=0x00000c1e
234
235 ldr r7, [r10, #PROCINFO_MM_MMUFLAGS] @ mm_mmuflags
236
237 /*
238 * Create identity mapping for first MB of kernel to
239 * cater for the MMU enable. This identity mapping
240 * will be removed by paging_init(). We use our current program
241 * counter to determine corresponding section base address.
242 */
243 mov r6, pc
/*
此处建立一个物理地址到物理地址的平板映射,这个映射将在函数paging_init(). 被清除。
r6 = 0x300 r3 = 0x30000c1e [0x30004c00]=0x30000c1e
*/
244 mov r6, r6, lsr #20 @ start of kernel section
245 orr r3, r7, r6, lsl #20 @ flags + kernel base
246 str r3, [r4, r6, lsl #2] @ identity mapping //字对齐
247
/*
MMU是通过C2中基地址(高18位)与虚拟地址的高12位组合成物理地址,在转换表中查找地址条目。R4中存放的就是这个基地址0x30004000。下面通过两次获取虚拟地址
KERNEL_START的高12位。KERNEL_START是内核存放的起始地址,为0X30008000。
*/
248 /*
249 * Now setup the pagetables for our kernel direct
250 * mapped region.
251 */
252 add r0, r4, #(KERNEL_START & 0xff000000) >> 18 // r0 = 0x30007000
253 str r3, [r0, #(KERNEL_START & 0x00f00000) >> 18]! // r0 存放的是转换表的起始位置
254 ldr r6, =(KERNEL_END - 1) //获取内核的尾部虚拟地址存于r6中
255 add r0, r0, #4 //第一个地址条目存放在0x30007004处,以后一次递增
256 add r6, r4, r6, lsr #18 //计算最后一个地址条目存放的位置
257 1: cmp r0, r6 //填充这之间的地址条目
258 add r3, r3, #1 << 20 //每一个地址条目代表了1MB空间的地址映射。物理地址将从 0x30100000 开始映射。0X30000000开始的1MB空间将在下面映射。
259 strls r3, [r0], #4
260 bls 1b
261
//如果是XIP就进行以下映射,这只是将内核代码存储的空间重新映射,
262 #ifdef CONFIG_XIP_KERNEL
263 /*
264 * Map some ram to cover our .data and .bss areas.
265 */
266 orr r3, r7, #(KERNEL_RAM_PADDR & 0xff000000)
267 .if (KERNEL_RAM_PADDR & 0x00f00000)
268 orr r3, r3, #(KERNEL_RAM_PADDR & 0x00f00000)
269 .endif
270 add r0, r4, #(KERNEL_RAM_VADDR & 0xff000000) >> 18
271 str r3, [r0, #(KERNEL_RAM_VADDR & 0x00f00000) >> 18]!
272 ldr r6, =(_end - 1)
273 add r0, r0, #4
274 add r6, r4, r6, lsr #18
275 1: cmp r0, r6
276 add r3, r3, #1 << 20
277 strls r3, [r0], #4
278 bls 1b
279 #endif
280
281 /*
282 * Then map first 1MB of ram in case it contains our boot params.
283 */
/*
映射0X30000000开始的1MB空间。
PAGE_OFFSET = 0XC0000000,PHYS_OFFSET = 0X30000000,
*/
//r0 = 0x30007000,上面是从0x30007004开始存放地址条目的。
284 add r0, r4, #PAGE_OFFSET >> 18
285 orr r6, r7, #(PHYS_OFFSET & 0xff000000)
286 .if (PHYS_OFFSET & 0x00f00000)
287 orr r6, r6, #(PHYS_OFFSET & 0x00f00000)
288 .endif
289 str r6, [r0] //将0x30000c1e存于0x30007000处。
290
291 #ifdef CONFIG_DEBUG_LL //下面是为了调试而做的相关映射,跳过。
292 ldr r7, [r10, #PROCINFO_IO_MMUFLAGS] @ io_mmuflags
293 /*
294 * Map in IO space for serial debugging.
295 * This allows debug messages to be output
296 * via a serial console before paging_init.
297 */
298 ldr r3, [r8, #MACHINFO_PGOFFIO]
299 add r0, r4, r3
300 rsb r3, r3, #0x4000 @ PTRS_PER_PGD*sizeof(long)
301 cmp r3, #0x0800 @ limit to 512MB
302 movhi r3, #0x0800
303 add r6, r0, r3
304 ldr r3, [r8, #MACHINFO_PHYSIO]
305 orr r3, r3, r7
306 1: str r3, [r0], #4
307 add r3, r3, #1 << 20
308 teq r0, r6
309 bne 1b
310 #if defined(CONFIG_ARCH_NETWINDER) || defined(CONFIG_ARCH_CATS)
311 /*
312 * If we're using the NetWinder or CATS, we also need to map
313 * in the 16550-type serial port for the debug messages
314 */
315 add r0, r4, #0xff000000 >> 18
316 orr r3, r7, #0x7c000000
317 str r3, [r0]
318 #endif
319 #ifdef CONFIG_ARCH_RPC
320 /*
321 * Map in screen at 0x02000000 & SCREEN2_BASE
322 * Similar reasons here - for debug. This is
323 * only for Acorn RiscPC architectures.
324 */
325 add r0, r4, #0x02000000 >> 18
326 orr r3, r7, #0x02000000
327 str r3, [r0]
328 add r0, r4, #0xd8000000 >> 18
329 str r3, [r0]
330 #endif
331 #endif
332 mov pc, lr //子程序返回。
333 ENDPROC(__create_page_tables)
334 .ltorg
335
336 #include "head-common.S"
__arm920_setup
---------------------------------------------------------------
回到arch/arm/kernel/head.S中有这么几条
ldr r13, __switch_data @ address to jump to after
@ mmu has been enabled
adr lr, __enable_mmu @ return (PIC) address
add pc, r10, #PROCINFO_INITFUNC
R10中存放的是在函数__lookup_processor_type中成功匹配的结构体proc_info_list。
对于arm920来说在文件arch/arm/mm/proc-arm920.S中有:
444 .section ".proc.info.init", #alloc, #execinstr
445
446 .type __arm920_proc_info,#object
447 __arm920_proc_info:
448 .long 0x41009200
449 .long 0xff00fff0
450 .long PMD_TYPE_SECT | \
451 PMD_SECT_BUFFERABLE | \
452 PMD_SECT_CACHEABLE | \
453 PMD_BIT4 | \
454 PMD_SECT_AP_WRITE | \
455 PMD_SECT_AP_READ
456 .long PMD_TYPE_SECT | \
457 PMD_BIT4 | \
458 PMD_SECT_AP_WRITE | \
459 PMD_SECT_AP_READ
460 b __arm920_setup
所以add pc, r10, #PROCINFO_INITFUNC的意思跳到函数__arm920_setup去执行。
文件arch/arm/mm/proc-arm920.S中
380 .type __arm920_setup, #function //表明这是一个函数
381 __arm920_setup:
382 mov r0, #0
383 mcr p15, 0, r0, c7, c7 //使数据cahche, 指令cache无效。
384 mcr p15, 0, r0, c7, c10, 4 //使write buffer无效。
385 #ifdef CONFIG_MMU
386 mcr p15, 0, r0, c8, c7 //使数据TLB,指令TLB无效。
387 #endif
388 adr r5, arm920_crval //获取arm920_crval的地址,并存入r5。
389 ldmia r5, {r5, r6} //获取arm920_crval地址处的连续8字节分别存入r5,r6。
390 mrc p15, 0, r0, c1, c0 //获取CP15下控制寄存器的值,并存入r0。
//通过查看arm920_crval的值可知该行是清除r0中相关位,为以后对这些位的赋值做准备
391 bic r0, r0, r5
392 orr r0, r0, r6 //设置r0中的相关位,即为mmu做相应设置。
393 mov pc, lr //上面有操作adr lr, __enable_mmu 此处将跳到程序段__enable_mmu处。
394 .size __arm920_setup, . - __arm920_setup
arch/arm/kernel/head.S
160 __enable_mmu:
161 #ifdef CONFIG_ALIGNMENT_TRAP
162 orr r0, r0, #CR_A //使能地址对齐错误检测
163 #else
164 bic r0, r0, #CR_A
165 #endif
166 #ifdef CONFIG_CPU_DCACHE_DISABLE
167 bic r0, r0, #CR_C //禁止数据cache
168 #endif
169 #ifdef CONFIG_CPU_BPREDICT_DISABLE
170 bic r0, r0, #CR_Z
171 #endif
172 #ifdef CONFIG_CPU_ICACHE_DISABLE
173 bic r0, r0, #CR_I //禁止指令cache
174 #endif
//配置相应的访问权限并存入r5中
175 mov r5, #(domain_val(DOMAIN_USER, DOMAIN_MANAGER) | \
176 domain_val(DOMAIN_KERNEL, DOMAIN_MANAGER) | \
177 domain_val(DOMAIN_TABLE, DOMAIN_MANAGER) | \
178 domain_val(DOMAIN_IO, DOMAIN_CLIENT))
179 mcr p15, 0, r5, c3, c0, 0 @ load domain access register//将访问权限写入协处理器
180 mcr p15, 0, r4, c2, c0, 0 @ load page table pointer//将页表基地址写入基址寄存器C2,0X30004000
181 b __turn_mmu_on //跳转到程序段去打开MMU
182 ENDPROC(__enable_mmu)
183
184 /*
185 * Enable the MMU. This completely changes the structure of the visible
186 * memory space. You will not be able to trace execution through this.
187 * If you have an enquiry about this, *please* check the linux-arm-kernel
188 * mailing list archives BEFORE sending another post to the list.
189 *
190 * r0 = cp#15 control register
191 * r13 = *virtual* address to jump to upon completion
192 *
193 * other registers depend on the function called upon completion
194 */
195 .align 5
196 __turn_mmu_on:
197 mov r0, r0
198 mcr p15, 0, r0, c1, c0, 0 @ write control reg//打开MMU同时打开cache等。
199 mrc p15, 0, r3, c0, c0, 0 @ read id reg//读取id寄存器
200 mov r3, r3 //两个空操作,等待前面所取的指令得以执行。
201 mov r3, r13
202 mov pc, r3 //程序跳转,如下面解释。
203 ENDPROC(__turn_mmu_on)
在前面有过这样的指令操作ldr r13, __switch_data ,
mov pc, r13 就是将跳转到__switch_data处。
arch/arm/kernel/head-common.S
14 #define ATAG_CORE 0x54410001
15 #define ATAG_CORE_SIZE ((2*4 + 3*4) >> 2)
16 #define ATAG_CORE_SIZE_EMPTY ((2*4) >> 2)
17
18 .align 2
19 .type __switch_data, %object //定义一个对象
20 __switch_data:
21 .long __mmap_switched //由此可知上面程序将跳转到该程序段处。
22 .long __data_loc @ r4
23 .long _data @ r5
24 .long __bss_start @ r6
25 .long _end @ r7
26 .long processor_id @ r4
27 .long __machine_arch_type @ r5
28 .long __atags_pointer @ r6
29 .long cr_alignment @ r7
30 .long init_thread_union + THREAD_START_SP @ sp
31
/*
__data_loc 是数据存放的位置
_data 是数据开始的位置
__bss_start 是bss开始的位置
_end 是bss结束的位置, 也是内核结束的位置
.data 段,后面的AT(__data_loc) 的意思是这部分的内容是在__data_loc中存储的(要注意,储存的位置和链接的位置是可以不相同的).
这几个字段在文件arch/arm/kernel/vmlinux.lds.S中指定位置如下:
20 SECTIONS
21 {
。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
134 .data : AT(__data_loc) { //此处数据存储在上面__data_loc处。
135 _data = .; /* address in memory */
136 _sdata = .;
137
138 /*
139 * first, the init task union, aligned
140 * to an 8192 byte boundary.
141 */
142 INIT_TASK_DATA(THREAD_SIZE)
。。。。。。。。。。。。。。。。。。。。。。。。。。。
231
232 BSS_SECTION(0, 0, 0)
233 _end = .;
234
235 STABS_DEBUG
236 .comment 0 : { *(.comment) }
237
238 /* Default discards */
239 DISCARDS
240 }
init_thread_union 是 init进程的基地址. 在 arch/arm/kernel/init_task.c 中:
27 union thread_union init_thread_union __init_task_data =
28 { INIT_THREAD_INFO(init_task) };
对照 vmlnux.lds.S 中,我们可以知道init task是存放在 .data 段的开始8k, 并且是THREAD_SIZE(8k)对齐的
*/
32 /*
33 * The following fragment of code is executed with the MMU on in MMU mode,
34 * and uses absolute addresses; this is not position independent.
35 *
36 * r0 = cp#15 control register
37 * r1 = machine ID
38 * r2 = atags pointer
39 * r9 = processor ID
40 */
41 __mmap_switched:
42 adr r3, __switch_data + 4
43
44 ldmia r3!, {r4, r5, r6, r7}
45 cmp r4, r5 @ Copy data segment if needed
46 1: cmpne r5, r6 //将 __data_loc处数据搬移到_data处
47 ldrne fp, [r4], #4
48 strne fp, [r5], #4
49 bne 1b
50
51 mov fp, #0 @ Clear BSS (and zero fp)//清除BSS段内容
52 1: cmp r6, r7
53 strcc fp, [r6],#4
54 bcc 1b
55
56 ARM( ldmia r3, {r4, r5, r6, r7, sp})
57 THUMB( ldmia r3, {r4, r5, r6, r7} )
58 THUMB( ldr sp, [r3, #16] )
59 str r9, [r4] @ Save processor ID
60 str r1, [r5] @ Save machine type
61 str r2, [r6] @ Save atags pointer
62 bic r4, r0, #CR_A @ Clear 'A' bit
63 stmia r7, {r0, r4} @ Save control register values
64 b start_kernel //程序跳转到函数start_kernel进入C语言部分。
65 ENDPROC(__mmap_switched)