1.概述:
在内核运行之前需要系统引导程序(Bootloader)完成加载内核和一些辅助性的工作,然后跳转到内核代码的起始地址并执行。本文先分析了Bootloader的初始化工作,接着从内核镜像的起始地址进行分析。整个arm linux内核的启动可分为三个阶段:第一阶段主要是进行cpu和体系结构的检查、cpu本身的初始化以及页表的建立等;第二阶段主要是对系统中的一些基础设施进行初始化;最后则是更高层次的初始化,如根设备和外部设备的初始化。
第一阶段的初始化是从内核入口(ENTRY(stext))开始到start_kernel前结束。这一阶段的代码在/arch/arm/head_armv.S中。
2.Bootloader
2.1简介
本处介绍主要来自内核源代码下的Documentation/arm/Booting文件,适合于arm linux 2.4.18-rmk6及以上版本。
Bootloader主要作用是初始化一些必要的设备,然后调用内核,同时传递参数给内核。主要完成如下工作:
1. 建立和初始化RAM。
2. 初始化一个串口。
3. 检测机器的系统结构。
4. 建立内核的tagged list。
5. 调用内核镜像。
2.2功能详细介绍
1.建立和初始化RAM。
要求:必须
功能:探测所有的RAM位置和大小,并对RAM进行初始化。
2.初始化一个串口。
要求:可选,建议
功能:Bootloader应该初始化并启动一个串口。这可以让内核的串口驱动自动探测哪个串口作为内核的控制台。另外也可以通过给内核传递“console=”参数完成此工作。
3.检测机器的系统结构。
要求:必须
功能:Bootloader应该通过某种方法探测机器类型,最后传递给内核一个MACH_TYPE_xxx值,这些值参看linux/arch/arm/tools/mach-types。
4.建立内核的tagged list。
要求:必须
功能:Bootloader必须创建和初始化内核的tagged list。一个合法的tagged list开始于ATAG_CORE 并结束于ATAG_NONE。ATAG_CORE tag可以为空。一个空的ATAG_CORE tag的size字段设为“2”(0x00000002)。ATAG_NONE 的size字段必须设为“0”。tagged list可以有任意多的tag。Bootloader必须至少传递系统内存的大小和位置,以及根文件系统的位置,一个最小化的tagged list应该像如下:
+-----------+
base -> | ATAG_CORE | |
+-----------+ |
| ATAG_MEM | | increasing address
+-----------+ |
| ATAG_NONE | |
+-----------+ v
tagged list应该放在内核解压时和initrd的”bootp”程序都不会覆盖的内存区域。建议放在RAM的起始的16K大小的地方。
5.调用内核镜像。
要求:必须
功能:可以从flash调用内核,也可以从系统RAM中调用内核。对于后者需要注意,内核使用内核镜像以下的16K内存作为页表,建议把内核起始放在RAM的32K处。无论是哪种方法,如下条件必须满足:
- CPU register settings
r0 = 0,
r1 = machine type number discovered in (3) above.
r2 = physical address of tagged list in system RAM.
- CPU mode
All forms of interrupts must be disabled (IRQs and FIQs)
The CPU must be in SVC mode. (A special exception exists for Angel)
- Caches, MMUs
The MMU must be off.
Instruction cache may be on or off.
Data cache must be off.
- The boot loader is expected to call the kernel image by jumping
directly to the first instruction of the kernel image.
2.3 Skyeye相应说明
因为Skyeye暂时没有Bootloader,所以以上一些设置必须由Skyeye在初始化的时候自己来完成,如r1的设置等。
3.Head_armv.S分析
3.1 说明
这个文件是arch/arm/kernel/head-armv.S,用汇编代码完成,是内核最先执行的一个文件。这一段汇编代码的主要作用,是检查cpu id,architecture number,初始化页表、cpu、bbs等操作,并跳到start_kernel函数。它在执行前,处理器的状态应满足:
l r0 - should be 0
l r1 - unique architecture number
l MMU - off
l I-cache - on or off
l D-cache – off
3.2 流程
3.3 代码详细注释
(略去一些条件编译的代码)
-------------------------------------------------------------------------------------------------------------------
/*
* We place the page tables 16K below TEXTADDR. Therefore, we must make sure
* that TEXTADDR is correctly set. Currently, we expect the least significant
* "short" to be 0x8000, but we could probably relax this restriction to
* TEXTADDR > PAGE_OFFSET + 0x4000
*
* Note that swapper_pg_dir is the virtual address of the page tables, and
* pgtbl gives us a position-independent reference to these tables. We can
* do this because stext == TEXTADDR
*
* swapper_pg_dir, pgtbl and krnladr are all closely related.
*/
#if (TEXTADDR & 0xffff) != 0x8000
#error TEXTADDR must start at 0xXXXX8000
#endif
.globl SYMBOL_NAME(swapper_pg_dir)
.equ SYMBOL_NAME(swapper_pg_dir), TEXTADDR - 0x4000
.macro pgtbl, reg, rambase
adr \reg, stext
sub \reg, \reg, #0x4000
.endm
/*
* Since the page table is closely related to the kernel start address, we
* can convert the page table base address to the base address of the section
* containing both.
*/
.macro krnladr, rd, pgtable, rambase
bic \rd, \pgtable, #0x000ff000
.endm
/*
* Kernel startup entry point.
*
* The rules are:
* r0 - should be 0
* r1 - unique architecture number
* MMU - off
* I-cache - on or off
* D-cache - off
*
* See linux/arch/arm/tools/mach-types for the complete list of numbers
* for r1.
*/
.section ".text.init",#alloc,#execinstr
.type stext, #function
ENTRY(stext) //内核入口点
mov r12, r0 //r0=0,r12=0
mov r0, #F_BIT | I_BIT | MODE_SVC@ make sure svc mode//程序状态,禁止FIQ、IRQ,设定Supervisor模式。0b11010011
msr cpsr_c, r0 @ and all irqs disabled//置当前程序状态寄存器
bl __lookup_processor_type//跳转到判断cpu类型,查找运行的cpu的id值,和
//此linux编译支持的id值,是否有相等
teq r10, #0 @ invalid processor?//没有则跳到__error
moveq r0, #'p' @ yes, error 'p'
beq __error
bl __lookup_architecture_type//跳转到判断体系类型,看r1寄存器的
//architecture number值是否支持。
teq r7, #0 @ invalid architecture? //不支持,跳到出错
moveq r0, #'a' @ yes, error 'a'
beq __error
bl __create_page_tables//创建核心页表
adr lr, __ret @ return address//lr=0xc0028054
add pc, r10, #12 @ initialise processor//r10:pointer to processor structure
//(__arm720_proc_info)
//r10+12:__arm720_setup;见
//__arm720_proc_info(proc-arm720.S)
『__arm720_setup: mov r0, #0
mcr p15, 0, r0, c7, c7, 0 @ invalidate caches
mcr p15, 0, r0, c8, c7, 0 @ flush TLB (v4)
mcr p15, 0, r4, c2, c0 @ load page table pointer
//cp15寄存器1(ttb)=0xc0024000
mov r0, #0x1f @ Domains 0, 1 = client
mcr p15, 0, r0, c3, c0 @ load domain access register
mrc p15, 0, r0, c1, c0 @ get control register//r0=0x70
bic r0, r0, #0x0e00 @ ..V. ..RS BLDP WCAM//bit[11:9]=0
r0=0x00000070
orr r0, r0, #0x2100 @ .... .... .111 .... (old) //r0=0x00002170
orr r0, r0, #0x003d @ ..1. ..01 ..11 1101 (new) //r0=0x0000217d
其中S LDPWC M位置1。
(详见cp15寄存器1说明)
__switch_data: .long __mmap_switched
.long SYMBOL_NAME(__bss_start)
.long SYMBOL_NAME(_end)
.long SYMBOL_NAME(processor_id)
.long SYMBOL_NAME(__machine_arch_type)
.long SYMBOL_NAME(cr_alignment)
.long SYMBOL_NAME(init_task_union)+8192
.type __ret, %function
__ret: ldr lr, __switch_data
mcr p15, 0, r0, c1, c0//更新控制寄存器cp15寄存器1=0x0000217d
mov r0, r0
mov r0, r0
mov r0, r0
mov pc, lr//__switch_data
/*
* This code follows on after the page
* table switch and jump above.
*
* r0 = processor control register
* r1 = machine ID
* r9 = processor ID
*/
.align 5
__mmap_switched://把sp指针指向init_task_union+8192(include/linux/sched.h)处,即第
//一个进程的task_struct和系统堆栈的地址;清空BSS段;保存processor ID
//和machine type,到全局变量processor_id和__machine_arch_type,这些值
//以后要用到;r0为"A"置位的control register 值,r2为"A"清空的
//control register 值,即对齐检查(Alignment fault checking)位,并保
//存到cr_alignment,和cr_no_alignment(在文件entry-armv.S中)。最
//后跳转到start_kernel(init/main.c)
adr r3, __switch_data + 4//__bss_start
ldmia r3, {r4, r5, r6, r7, r8, sp}@ r2 = compat//r2=0xc0000000
@ sp = stack pointer
//r4=0xc00c04e0;__bss_start
//r5=0xc00e02a8;_end
//r6=0xc00c0934;processor_id
//r7=0xc00c0930;__machine_arch_type
//r8=0xc00bcb88;cr_alignment
//sp=0xc00bc000;(init_task_union)+8192
mov fp, #0 @ Clear BSS (and zero fp)
1: cmp r4, r5
strcc fp, [r4],#4
bcc 1b
str r9, [r6] @ Save processor ID
str r1, [r7] @ Save machine type
#ifdef CONFIG_ALIGNMENT_TRAP
orr r0, r0, #2 @ ...........A.
#endif
bic r2, r0, #2 @ Clear 'A' bit//r0=0x217d
stmia r8, {r0, r2} @ Save control register values
b SYMBOL_NAME(start_kernel)//跳转到start_kernel
/*
* 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.
*
* We only map in 4MB of RAM, which should be sufficient in
* all cases.
*
* r5 = physical address of start of RAM
* r6 = physical IO address
* r7 = byte offset into page tables for IO
* r8 = page table flags
*/
__create_page_tables:
pgtbl r4, r5 @ page table address//调用宏pgtbl,r4=0xc0024000:页表
基址
/*
* Clear the 16K level 1 swapper page table
*/
mov r0, r4//r0=0xc0024000
mov r3, #0
add r2, r0, #0x4000//r2=0xc0028000
1: str r3, [r0], #4
str r3, [r0], #4
str r3, [r0], #4
str r3, [r0], #4
teq r0, r2
bne 1b //将地址0xc0024000~0xc0028000清0
/*
* Create identity mapping for first MB of kernel to
* cater for the MMU enable. This identity mapping
* will be removed by paging_init()
*/
krnladr r2, r4, r5 @ start of kernel//r2=0xc0000000;r4=0xc0024000
add r3, r8, r2 @ flags + kernel base//flags,r8=0xc1e;r3=0xc0000c1e
str r3, [r4, r2, lsr #18] @ identity mapping//addr:0xc0027000;value:0xc0000c1e
/*
* Now setup the pagetables for our kernel direct
* mapped region. We round TEXTADDR down to the
* nearest megabyte boundary.
*/
add r0, r4, #(TEXTADDR & 0xff000000) >> 18 @ start of kernel//r0=0xc0027000
bic r2, r3, #0x00f00000 //r2=0xc0000c1e
str r2, [r0] @ PAGE_OFFSET + 0MB
add r0, r0, #(TEXTADDR & 0x00f00000) >> 18
str r3, [r0], #4 @ KERNEL + 0MB
add r3, r3, #1 << 20
str r3, [r0], #4 @ KERNEL + 1MB
add r3, r3, #1 << 20
str r3, [r0], #4 @ KERNEL + 2MB
add r3, r3, #1 << 20
str r3, [r0], #4 @ KERNEL + 3MB
//核心页表:
//addr 一级描述符值
//0xc0027000 0xc0000c1e
//0xc0027004 0xc0100c1e
//0xc0027008 0xc0200c1e
//0xc002700c 0xc0300c1e r0=0xc0027010
/*
* Ensure that the first section of RAM is present.
* we assume that:
* 1. the RAM is aligned to a 32MB boundary
* 2. the kernel is executing in the same 32MB chunk
* as the start of RAM.
*/
bic r0, r0, #0x01f00000 >> 18 @ round down//r0=0xc0027000
and r2, r5, #0xfe000000 @ round down//r2=0xc0000000
add r3, r8, r2 @ flags + rambase//r3=0xc0000c1e
str r3, [r0]
bic r8, r8, #0x0c @ turn off cacheable//r8=0xc12
@ and bufferable bits
mov pc, lr
/*
* 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.
*
* Returns:
* r5, r6, r7 corrupted
* r8 = page table flags
* r9 = processor ID
* r10 = pointer to processor structure
*/
__lookup_processor_type://判断cpu类型
adr r5, 2f //取标号2的地址
ldmia r5, {r7, r9, r10} //r7:__proc_info_end;r9:__proc_info_begin;r10:r5
sub r5, r5, r10 @ convert addresses //r5=0??
add r7, r7, r5 @ to our address space
add r10, r9, r5 //r10:__proc_info_begin
mrc p15, 0, r9, c0, c0 @ get processor id //读取cp15寄存器0中cpu id至r9。在
此版本是0x41807200
1: ldmia r10, {r5, r6, r8} @ value, mask, mmuflags //读取arm linux中cpu信息
// r5,id:0x41807200;r6,mask:
//0xffffff00;r8,mmuflags即一级描
//述符:0xc1e
and r6, r6, r9 @ mask wanted bits //屏蔽cpu id的低8位
teq r5, r6 //寄存器0的cpu id与arm linux中cpu id比较
moveq pc, lr //相同则返回
add r10, r10, #36 @ sizeof(proc_info_list)//否则寻找下一块proc_info
cmp r10, r7
blt 1b
mov r10, #0 @ unknown processor //没有匹配信息,r10=0
mov pc, lr
/*
* Look in include/asm-arm/procinfo.h and arch/arm/kernel/arch.[ch] for
* more information about the __proc_info and __arch_info structures.
*/
2: .long __proc_info_end
.long __proc_info_begin
.long 2b
.long __arch_info_begin
.long __arch_info_end
/*
* Lookup machine architecture in the linker-build list of architectures.
* Note that we can't use the absolute addresses for the __arch_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.
*
* r1 = machine architecture number
* Returns:
* r2, r3, r4 corrupted
* r5 = physical start address of RAM
* r6 = physical address of IO
* r7 = byte offset into page tables for IO
*/
__lookup_architecture_type://判断体系类型
adr r4, 2b//取上面标号2的地址
ldmia r4, {r2, r3, r5, r6, r7} @ throw away r2, r3//r5:r4;r6:__arch_info_begin;
r7:__arch_info_end
sub r5, r4, r5 @ convert addresses//r5=0
add r4, r6, r5 @ to our address space//r4:__arch_info_begin;
add r7, r7, r5
1: ldr r5, [r4] @ get machine type
teq r5, r1//r1是machine type 号,此为91
beq 2f
add r4, r4, #SIZEOF_MACHINE_DESC//不匹配,查找下一个arch_info
cmp r4, r7
blt 1b
mov r7, #0 @ unknown architecture
mov pc, lr
2: ldmib r4, {r5, r6, r7} @ found, get results//r5,ram物理起始地址:
0xc0000000;r6,io地址:0x8000000;
r7,io在页表的偏移:0x3fc0
mov pc, lr //返回
欢迎光临 电子技术论坛_中国专业的电子工程师学习交流社区-中电网技术论坛 (http://bbs.eccn.com/) | Powered by Discuz! 7.0.0 |