Arm linux启动分析.docx
《Arm linux启动分析.docx》由会员分享,可在线阅读,更多相关《Arm linux启动分析.docx(17页珍藏版)》请在冰豆网上搜索。
Armlinux启动分析
Armlinux启动分析
(1)
下周准备做linux启动的技术讲座,在这里我慢慢整理下自己的材料,这次我写的是Image的启动过程,也即使zImage解压缩结束后的启动代码,这时候的代码开始地址仍然是0x30008000,下面我结合代码来讲吧:
Image的启动代码是在/arch/arm/kernel/head.S中的:
/*
* linux/arch/arm/kernel/head.S
* Kernelstartupcodeforall32-bitCPUs
*/
/*内核启动入口点
*Kernelstartupentrypoint.
*这里通常在解压后直接调用。
*处理器基本状态要求:
*MMU关闭,D-cach关闭,I-cache不用关系;
*r0=0,r1=系统号(machinenumber)
*这段代码几乎是位置无关的。
*如果链接内核在0xc0008000,调用的地址为相应的物理地址__pa(0xc0008000)。
*r1的系统号参考arch/arm/tools/mach-types文件的列表。
*尽量不要在这里添加系统号相关的代码,那应该放在bootloader的代码中。
*保持这里的代码的整洁。
*/
__INIT
.type stext,%function
/*――――――――――――――――――――――――――――――――――――――――――――――
这个地方就是kernel的入口点
――――――――――――――――――――――――――――――――――――――――――――――*/
ENTRY(stext)
msr cpsr_c,#PSR_F_BIT|PSR_I_BIT|MODE_SVC@ensuresvcmode
@andirqsdisabled
/*――――――――――――――――――――――――――――――――――――――――――――――
调用__lookup_processor_type检查现在运行的cpu的ID值和linux编译支持的
id值是否相等。
――――――――――――――――――――――――――――――――――――――――――――――*/
bl __lookup_processor_type @r5=procinfor9=cpuid
/*――――――――――――――――――――――――――――――――――――――――――――――
从该函数返回后,寄存器内容如下:
R9 =cpuID
R5=pointertoprocessorstructure
详细的内容请看__lookup_processor_type的分析*/
.type __lookup_processor_type,%function
__lookup_processor_type:
/*――――――――――――――――――――――――――――――――――――――――――――――
把标号2的地址送给r3,3f=lable3forward
――――――――――――――――――――――――――――――――――――――――――――――*/
adr r3,3f
/*――――――――――――――――――――――――――――――――――――――――――――――
把r3指向内存的地址的内容赋值给r5,r6,r9
所以,参照标号3处的声明,我们可以知道:
__proc_info_begin r5
__proc_info_end r6
3b r9
__proc_info_end和__proc_info_begin这两个标号都是在
/linux/arch/arm/vmlinux.ld这个脚本中定义的。
在连接的时候,ld会把相应cpu信息
proc_info放到这两个标号之间。
__proc_info_begin=.;
*(.proc.info)
__proc_info_end=.;
――――――――――――――――――――――――――――――――――――――――――――――*/
ldmda r3,{r5,r6,r9} @ldmda弹栈顺序是从右到左,[r3]->r9,[r3-4]->r6,[r3-8]->r5
/*r3=标号3的加载地址地址,r9=标号3的连接地址,r3是根据pc值确定的,r9是链接阶段就确定的是链接地址*/
sub r3,r3,r9 @getoffsetbetweenvirt&phys
//r3=加载地址和连接地址的差值
//现在,r5=__proc_info_begin的加载地址,即在RAM中的地址
add r5,r5,r3 @convertvirtaddressesto
add r6,r6,r3 @physicaladdressspace
mrc p15,0,r9,c0,c0 @getprocessorid协处理器指令获取cpuid号r9=0x41807202(sep4020)
/*――――――――――――――――――――――――――――――――――――――――――――――
在本例中,r5 = _arm720_proc_info这个标记定义在
linux/arch/arm/mm/proc-arm720.S
__arm720_proc_info:
.long 0x41807200 r3=cpu_value
.long 0xffffff00 r4=cpu_mask
.long 0x00000c1e mmuflags,一级段描述符
b __arm720_setup
.
.
――――――――――――――――――――――――――――――――――――――――――――――*/
//ldmia弹栈顺序是从左到右,[r5]->r3,[r5+4]->r4,即低地址的内容放到低编号的寄存器,高地址的内容放到高编号的寄存器,指令结束后r5的指依然为_arm720_proc_info
1:
ldmia r5,{r3,r4} @value,mask
and r4,r4,r9 @maskwantedbits
//将r9屏上0xffffff00看sep4020是否是arm720t的内核
teq r3,r4
beq 2f @若是arm720t内核则直接跳转到标签2
/*――――――――――――――――――――――――――――――――――――――――――――――
proc_info_list定义在linux/include/asm-arm/procinfo.h
structproc_info_list{
unsignedint cpu_val;
unsignedint cpu_mask;
unsignedlong __cpu_mmu_flags; //usedbyhead.S
unsignedlong __cpu_flush; //usedbyhead.S
constchar *arch_name;
constchar *elf_name;
unsignedint elf_hwcap;
constchar *cpu_name;
structprocessor *proc;
structcpu_tlb_fns *tlb;
structcpu_user_fns *user;
structcpu_cache_fns *cache;
};
每一项都是4个字节,所以sizeof(proc_info_list)=48byte
――――――――――――――――――――――――――――――――――――――――――――――*/
add r5,r5,#PROC_INFO_SZ @sizeof(proc_info_list)=48
cmp r5,r6
blo 1b
mov r5,#0 @unknownprocessor
2:
mov pc,lr
/*
*ThisprovidesaC-APIversionoftheabovefunction.
*/
ENTRY(lookup_processor_type)
stmfd sp!
{r4-r6,r9,lr}
bl __lookup_processor_type
mov r0,r5
ldmfd sp!
{r4-r6,r9,pc}
/*
*Lookininclude/asm-arm/procinfo.handarch/arm/kernel/arch.[ch]for
*moreinformationaboutthe__proc_infoand__arch_infostructures.
*/
.long __proc_info_begin
.long __proc_info_end
3:
.long .
.long __arch_info_begin
.long __arch_info_end
/*―――――――――――――从__lookup_processor_type返回―――――――――――――――――――――――――――――――――*/
movs r10,r5 @是有效720t核吗 (r5=0)?
beq __error_p @yes,error'p'
/*――――――――――――――――――――――――――――――――――――――――――――――
__lookup_machine_type通过R1寄存器,判断体系类型,R1=machine
architecturenumber
――――――――――――――――――――――――――――――――――――――――――――――*/
bl __lookup_machine_type @r5=machinfo
/*――――――――――――――――――――――――――――――――――――――――――――――*/
/* r1=machinearchitecturenumber
*Returns:
* r3,r4,r6corrupted
* r5=mach_infopointerinphysicaladdressspace
*/
.type __lookup_machine_type,%function
__lookup_machine_type:
adr r3,3b
/*――――――――――――――――――――――――――――――――――――――――――――――
把r3指向内存的地址的内容赋值给r4,r5,r6
所以,参照标号3处的声明,我们可以知道:
3b r4
__arch_info_begin r5
__arch_info_end r6
__arch_info_end和__arch_info_begin这两个标号都是在
/linux/arch/arm/vmlinux.ld这个脚本中定义的。
在连接的时候,ld会把相应体系架构
arch_info放到这两个标号之间。
__arch_info_begin=.;
*(.arch.info.init)
__arch_info_end=.;
――――――――――――――――――――――――――――――――――――――――――――――*/
ldmia r3,{r4,r5,r6}
sub r3,r3,r4 @getoffsetbetweenvirt&phys
//r5=__arch_info_begin的加载地址
add r5,r5,r3 @convertvirtaddressesto
add r6,r6,r3 @physicaladdressspace
/*――――――――――――――――――――――――――――――――――――――――――――――
__arch_info_begin和__arch_info_end 的类型都是struct machine_desc。
其实
就是指向一个machine_desc结构首尾的两个地址标号。
structmachine_desc定义在linux/include/asm-arm/mach/arch.h中
structmachine_desc{
/*
*Note!
Thefirstfourelementsareused
*byassemblercodeinhead.S
*/
unsignedint nr; /*architecturenumber */
unsignedint__deprecatedphys_ram; /*startofphysicalram*/
unsignedint phys_io; /*startofphysicalio */
unsignedint io_pg_offst; /*byteoffsetforio
*pagetabeentry */
constchar *name; /*architecturename */
unsignedlong boot_params; /*taggedlist */
unsignedint video_start; /*startofvideoRAM */
unsignedint video_end; /*endofvideoRAM */
unsignedint reserve_lp0:
1; /*neverhaslp0 */
unsignedint reserve_lp1:
1; /*neverhaslp1 */
unsignedint reserve_lp2:
1; /*neverhaslp2 */
unsignedint soft_reboot:
1; /*softreboot */
void (*fixup)(structmachine_desc*,
structtag*,char**,
structmeminfo*);
void (*map_io)(void);/*IOmappingfunction */
void (*init_irq)(void);
structsys_timer *timer; /*systemticktimer */
void (*init_machine)(void);
};
而对于我们的SEP4020其真正的定义是在/arch/arm/mach-sep4020/4020.c中
MACHINE_START(GFD4020,"4020board")
.phys_io =0x10000000,
.io_pg_offst =((0xe0000000)>>18)&0xfffc,
.boot_params =0x30000100,
.fixup =fixup_gfd4020,
.map_io =sep4020_map_io,
.init_irq = sep4020_init_irq,
.init_machine =sep4020_init,
.timer =&sep4020_timer,
MACHINE_END
看到这里,我们就不难明白下边这条指令了,structmachine_desc中第一个就是
nr,即architecturenumber
r3=MACH_TYPE_GFD4020
――――――――――――――――――――――――――――――――――――――――――――――*/
1:
ldr r3,[r5,#MACHINFO_TYPE] @getmachinetype,MACHINFO_TYPE=0
//r1是由解压缩程序/arch/arm/boot/compressed/head.S最后传过来的,或者是uboot传过来的体系结构号
teq r3,r1 @matchesloadernumber?
beq 2f @found
add r5,r5,#SIZEOF_MACHINE_DESC @nextmachine_desc
cmp r5,r6
blo 1b
mov r5,#0 @unknownmachine
2:
mov pc,lr
/*―――――――――――――――――从__lookup_machine_type返回―――――――――――――――――――――――――――――*/
movs r8,r5 @invalidmachine(r5=0)?
是不是我们的SEP4020系统结构
beq __error_a @yes,error'a'
/*――――――――――――――――――――――――――――――――――――――――――――――
设置mmu之前,设置临时内核页表
――――――――――――――――――――――――――――――――――――――――――――――*/
bl __create_page_tables
/*――――――――――――――――――――――――――――――――――――――――――――――
/* 我们在这里只映射内核启动的临时页表
*Setuptheinitialpagetables. Weonlysetupthebarest
*amountwhicharerequiredtogetthekernelrunning,which
*generallymeansmappinginthekernelcode.
*
*r8 =machinfo 体系结构信息
*r9 =cpuid cpu的ID
*r10=procinfo cpu信息
*
*Returns:
* r0,r3,r6,r7corrupted
* r4=physicalpagetableaddress
*/
.type __create_page_tables,%function
__create_page_tables:
/*――――――――――――――――――――――――――――――――――――――――――――――*/
//Pageoffset:
3GB 内核页表的偏移在/inculde/asm/memory.h
#definePAGE_OFFSET UL(0xc0000000)
#ifndef__virt_to_phys
#define__virt_to_phys(x) ((x)-PAGE_OFFSET+PHYS_OFFSET)
#define__phys_to_virt(x) ((x)-PHYS_OFFSET+PAGE_OFFSET)
#endif
而这其中的PHYS_OFFSET则是我们需要在我们的SEP4020的定义自己的主存ram的基址的物理地址,我们是在/include/asm-arm/arch-sep4020/memory.h中定义的
#definePHYS_OFFSET UL(0x30000000)
/*TEXT_OFFSET是在在arch/arm/Makefile第140行,有
TEXT_OFFSET:
=$(textofs-y)
第90行有
textofs-y:
=0x00008000
所以TEXT_OFFSET:
=0x00008000
在153行有exportTEXT_OFFSET将此变量输出。
*/
#defineKERNEL_RAM_ADDR (PAGE_OFFSET+TEXT_OFFSET) @其中TEXT_OFFSET=0x8000
//swapper_pg_dir是放启动时的临时页表的页表基址(虚地址)
.globl swapper_pg_dir
.equ swapper_pg_dir,KERNEL_RAM_ADDR-0x4000
//这个宏就是根据内核ram首址(虚拟地址)计算出我们内核页表的页表基址(物理地址)
.macro pgtbl,rd
ldr \rd,=(__virt_to_phys(KERNEL_RAM_ADDR-0x4000))
.endm
――――――――――――――――――――――――――――――――――――――――――――――*/
pgtbl r4 @pagetableaddress
//这样r4=内核页表的页表基址(物理地址)
/*
*Clearthe16Klevel1swapperpagetable
*/
mov r0,r4
mov r3,#0
//r6=内核的KERNEL_RAM_ADDR
add r6,r0,#0x4000
//首先对16k的一级页表内容清0
1:
str r3,[r0],#4
str r3,[r0],#4
str r3,[r0],#4
str r3,[r0],#4
teq r0,r6
bne 1b
//PROCINFO_MMUFLAGS=8;这样r7=0x00000c1e mmuflags,一级段描述符,在proc-arm720.S中定义
ldr r7,[r10,#PROCINFO_MMUFLAGS