嵌入式 arm平台kernel启动第二阶段分析
第二阶段的代码是从archarmkernelhead.S开始的。
本文引用地址:https://www.eepw.com.cn/article/201611/317682.htm内核启动第二阶段主要完成的工作有,cpuID检查,machineID(也就是开发板ID)检查,创建初始化页表,设置C代码运行环境,跳转到内核第一个真正的C函数startkernel开始执行。
这一阶段涉及到两个重要的结构体:
(1)一个是structproc_info_list主要描述CPU相关的信息,定义在文件archarmincludeasmprocinfo.h中,与其相关的函数及变量在文件arch/arm/mm/proc_arm920.S中被定义和赋值。
(2)另一个结构体是描述开发板或者说机器信息的结构体structmachine_desc,定义在archarmincludeasmmacharch.h文件中,其函数的定义和变量的赋值在板极相关文件arch/arm/mach-s3c2410/mach-smdk2410.c中实现,这也是内核移植非常重要的一个文件。
该阶段一般由前面的解压缩代码调用,进入该阶段要求:
MMU=off,D-cache=off,I-cache=dontcare,r0=0,r1=machineid.
所有的机器ID列表保存在arch/arm/tools/mach-types文件中,在编译时会将这些机器ID按照统一的格式链接到基本内核映像文件vmlinux的__arch_info_begin和__arch_info_end之间的段中。存储格式定义在include/asm-arm/mach/arch.h文件中的结构体structmachine_desc{}。这两个结构体的内容最终会被连接到基本内核映像vmlinux中的两个段内,分别是*(.proc.info.init)和*(.arch.info.init),可以参考下面的连接脚本。
链接脚本:arch/arm/kernel/vmlinux.lds
*链接脚本
SECTIONS
{
.=TEXTADDR;
.init:{/*初始化代码段*/
_stext=.;
_sinittext=.;
*(.init.text)
_einittext=.;
__proc_info_begin=.;
*(.proc.info.init)
__proc_info_end=.;
__arch_info_begin=.;
*(.arch.info.init)
__arch_info_end=.;
__tagtable_begin=.;
*(.taglist.init)
__tagtable_end=.;
.=ALIGN(16);
__setup_start=.;
*(.init.setup)
__setup_end=.;
__early_begin=.;
*(.early_param.init)
__early_end=.;
__initcall_start=.;
*(.initcall1.init)
*(.initcall2.init)
*(.initcall3.init)
*(.initcall4.init)
*(.initcall5.init)
*(.initcall6.init)
*(.initcall7.init)
__initcall_end=.;
__con_initcall_start=.;
*(.con_initcall.init)
__con_initcall_end=.;
__security_initcall_start=.;
*(.security_initcall.init)
__security_initcall_end=.;
.=ALIGN(32);
__initramfs_start=.;
usr/built-in.o(.init.ramfs)
__initramfs_end=.;
.=ALIGN(64);
__per_cpu_start=.;
*(.data.percpu)
__per_cpu_end=.;
#ifndefCONFIG_XIP_KERNEL
__init_begin=_stext;
*(.init.data)
.=ALIGN(4096);
__init_end=.;
#endif
}
*链接脚本
下面开始代码archarmkernelhead.S的注释:
开始分析前先看下一点基础知识:
1.kernel运行的史前时期和内存布局在arm平台下,zImage.bin压缩镜像是由bootloader加载到物理内存,然后跳到zImage.bin里一段程序,它专门于将被压缩的kernel解压缩到KERNEL_RAM_PADDR开始的一段内存中,接着跳进真正的kernel去执行。该kernel的执行起点是stext函数,定义于arch/arm/kernel/head.S。此时内存的布局如下图所示
在开发板3c2410中,SDRAM连接到内存控制器的Bank6中,它的开始内存地址是0x30000000,大小为64M,即0x20000000。ARMLinuxkernel将SDRAM的开始地址定义为PHYS_OFFSET。经bootloader加载kernel并由自解压部分代码运行后,最终kernel被放置到KERNEL_RAM_PADDR(=PHYS_OFFSET+TEXT_OFFSET,即0x30008000)地址上的一段内存,经此放置后,kernel代码以后均不会被移动。
在进入kernel代码前,即bootloader和自解压缩阶段,ARM未开启MMU功能。因此kernel启动代码一个重要功能是设置好相应的页表,并开启MMU功能。为了支持MMU功能,kernel镜像中的所有符号,包括代码段和数据段的符号,在链接时都生成了它在开启MMU时,所在物理内存地址映射到的虚拟内存地址。
以armkernel第一个符号(函数)stext为例,在编译链接,它生成的虚拟地址是0xc0008000,而放置它的物理地址为0x30008000(还记得这是PHYS_OFFSET+TEXT_OFFSET吗?)。实际上这个变换可以利用简单的公式进行表示:va=pa–PHYS_OFFSET+PAGE_OFFSET。Armlinux最终的kernel空间的页表,就是按照这个关系来建立。
之所以较早提及armlinux的内存映射,原因是在进入kernel代码,里面所有符号地址值为清一色的0xCXXXXXXX地址,而此时ARM未开启MMU功能,故在执行stext函数第一条执行时,它的PC值就是stext所在的内存地址(即物理地址,0x30008000)。因此,下面有些代码,需要使用地址无关技术。
__HEAD/*该宏定义了下面的代码位于".head.text"段内*/
.typestext,%function/*声明stext为函数*/
ENTRY(stext)/*第二阶段的入口地址*/
setmodePSR_F_BIT|PSR_I_BIT|SVC_MODE,r9@ensuresvcmodeandirqsdisabled进入超级权限模式,关中断
/*从协处理器CP15,C0读取CPUID,然后在__proc_info_begin开始的段中进行查找,如果找到,则返回对应处理器相关结构体在物理地址空间的首地址到r5,最后保存在r10中*/
mrcp15,0,r9,c0,c0@getprocessorid取出cpuid
bl__lookup_processor_type@r5=procinfor9=cpuid
//
__lookup_processor_type函数的具体解析开始(archarmkernelhead-common.S)
//
在讲解该程序段之前先来看一些相关知识,内核所支持的每一种CPU类型都由结构体proc_info_list来描述。
该结构体在文件arch/arm/include/asm/procinfo.h中定义:
structproc_info_list{
unsignedintcpu_val;
unsignedintcpu_mask;
unsignedlong__cpu_mm_mmu_flags;/*usedbyhead.S*/
unsignedlong__cpu_io_mmu_flags;/*usedbyhead.S*/
unsignedlong__cpu_flush;/*usedbyhead.S*/
constchar*arch_name;
constchar*elf_name;
unsignedintelf_hwcap;
constchar*cpu_name;
structprocessor*proc;
structcpu_tlb_fns*tlb;
structcpu_user_fns*user;
structcpu_cache_fns*cache;
};
对于arm920来说,其对应结构体在文件linux/arch/arm/mm/proc-arm920.S中初始化。
.section".proc.info.init",#alloc,#execinstr/*定义了一个段,下面的结构体存放在该段中*/
.type__arm920_proc_info,#object/*声明一个结构体对象*/
__arm920_proc_info:/*为该结构体赋值*/
.long0x41009200
.long0xff00fff0
.longPMD_TYPE_SECT|
PMD_SECT_BUFFERABLE|
PMD_SECT_CACHEABLE|
PMD_BIT4|
PMD_SECT_AP_WRITE|
PMD_SECT_AP_READ
.longPMD_TYPE_SECT|
PMD_BIT4|
PMD_SECT_AP_WRITE|
PMD_SECT_AP_READ
b__arm920_setup
…………………………………
.section".proc.info.init"表明了该结构在编译后存放的位置。在链接文件arch/arm/kernel/vmlinux.lds中:
SECTIONS
{
#ifdefCONFIG_XIP_KERNEL
.=XIP_VIRT_ADDR(CONFIG_XIP_PHYS_ADDR);
#else
.=PAGE_OFFSET+TEXT_OFFSET;
#endif
.text.head:{
_stext=.;
_sinittext=.;
*(.text.head)
}
.init:{/*Initcodeanddata*/
INIT_TEXT
_einittext=.;
__proc_info_begin=.;
*(.proc.info.init)
__proc_info_end=.;
__arch_info_begin=.;
*(.arch.info.init)
__arch_info_end=.;
__tagtable_begin=.;
*(.taglist.init)
__tagtable_end=.;
………………………………
}
所有CPU类型对应的被初始化的proc_info_list结构体都放在__proc_info_begin和__proc_info_end之间。
/*
*r9=cpuid
*Returns:
*r5=proc_infopointerinphysicaladdressspace
*r9=cpuid(preserved)
*/
__lookup_processor_type:
adrr3,3f@r3存储的是标号3的物理地址(由于没有启用mmu,所以当前肯定是物理地址)
ldmiar3,{r5-r7}@R5=__proc_info_begin,r6=__proc_info_end,r7=标号4处的虚拟地址,即4:.long.处的地址
addr3,r3,#8@得到4处的物理地址,刚好是跳过两条指令
subr3,r3,r7@getoffsetbetweenvirt&phys得到虚拟地址和物理地址之间的offset
/*利用offset,将r5和r6中保存的虚拟地址转变为物理地址*/
addr5,r5,r3@convertvirtaddressesto
addr6,r6,r3@physicaladdressspace
1:ldmiar5,{r3,r4}@value,maskr3=cpu_val,r4=cpu_mask
andr4,r4,r9@maskwantedbits;r9中存放的是先前读出的processorID,此处屏蔽不需要的位
teqr3,r4@查看代码和CPU硬件是否匹配(比如想在arm920t上运行为cortex-a8编译的内核?不让)
beq2f@如果相等则跳转到标号2处,执行返回指令
addr5,r5,#PROC_INFO_SZ@sizeof(proc_info_list结构的长度,在这等于48)如果没找到,跳到下一个proc_info_list处
cmpr5,r6@判断是不是到了该段的结尾
blo1b@如果没有,继续跳到标号1处,查找下一个
movr5,#0@unknownprocessor,如果到了结尾,没找到匹配的,就把0赋值给r5,然后返回
2:movpc,lr@找到后返回,r5指向找到的结构体
ENDPROC(__lookup_processor_type)
.align2
3:.long__proc_info_begin
.long__proc_info_end
4:.long.@“.”表示当前这行代码编译连接后的虚拟地址
.long__arch_info_begin
.long__arch_info_end
//
__lookup_processor_type函数的具体解析结束(archarmkernelhead-common.S)
//
movsr10,r5@invalidprocessor(r5=0)?
beq__error_p@yes,errorp
/*机器ID是由u-boot引导内核是通过thekernel第二个参数传递进来的,现在保存在r1中,在__arch_info_begin开始的段中进行查找,如果找到,则返回machine对应相关结构体在物理地址空间的首地址到r5,最后保存在r8中。
bl__lookup_machine_type@r5=machinfo
//
__lookup_machine_type函数的具体解析开始(archarmkernelhead-common.S)
//
每一个CPU平台都可能有其不一样的结构体,描述这个平台的结构体是machine_desc。
这个结构体在文件arch/arm/include/asm/mach/arch.h中定义:
structmachine_desc{
unsignedintnr;/*architecturenumber*/
unsignedintphys_io;/*startofphysicalio*/
………………………………
};
对于平台smdk2410来说其对应machine_desc结构在文件linux/arch/arm/mach-s3c2410/mach-smdk2410.c中初始化:
MACHINE_START(SMDK2410,"SMDK2410")
.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中定义:
#defineMACHINE_START(_type,_name)/
staticconststructmachine_desc__mach_desc_##_type/
__used/
__attribute__((__section__(".arch.info.init")))={/
.nr=MACH_TYPE_##_type,/
.name=_name,
#defineMACHINE_END/
};
__attribute__((__section__(".arch.info.init")))表明该结构体在并以后存放的位置。
在链接文件链接脚本文件arch/arm/kernel/vmlinux.lds中
SECTIONS
{
#ifdefCONFIG_XIP_KERNEL
.=XIP_VIRT_ADDR(CONFIG_XIP_PHYS_ADDR);
#else
.=PAGE_OFFSET+TEXT_OFFSET;
#endif
.text.head:{
_stext=.;
_sinittext=.;
*(.text.head)
}
.init:{/*Initcodeanddata*/
INIT_TEXT
_einittext=.;
__proc_info_begin=.;
*(.proc.info.init)
__proc_info_end=.;
__arch_info_begin=.;
*(.arch.info.init)
__arch_info_end=.;
………………………………
}
在__arch_info_begin和__arch_info_end之间存放了linux内核所支持的所有平台对应的machine_desc结构体。
/*
*r1=machinearchitecturenumber
*Returns:
*r5=mach_infopointerinphysicaladdressspace
*/
__lookup_machine_type:
adrr3,4b@把标号4处的地址放到r3寄存器里面
ldmiar3,{r4,r5,r6}@R4=标号4处的虚拟地址,r5=__arch_info_begin,r6=__arch_info_end
subr3,r3,r4@getoffsetbetweenvirt&phys计算出虚拟地址与物理地址的偏移
/*利用offset,将r5和r6中保存的虚拟地址转变为物理地址*/
addr5,r5,r3@convertvirtaddressesto
addr6,r6,r3@physicaladdressspace
/*读取machine_desc结构的nr参数,对于smdk2410来说该值是MACH_TYPE_SMDK2410,这个值在文件linux/arch/arm/tools/mach-types中:
smdk2410ARCH_SMDK2410SMDK2410193*/
1:ldrr3,[r5,#MACHINFO_TYPE]@getmachinetype
teqr3,r1@matchesloadernumber?把取到的machineid和从uboot中传过来的machineid(存放r1中)相比较
beq2f@found如果相等,则跳到标号2处,返回
addr5,r5,#SIZEOF_MACHINE_DESC@nextmachine_desc没有找到,则继续找下一个,加上该结构体的长度
cmpr5,r6@判断是否已经到该段的末尾
blo1b@如果没有,则跳转到标号1处,继续查找
movr5,#0@unknownmachine如果已经到末尾,并且没找到,则返回值r5寄存器赋值为0
2:movpc,lr@返回原函数,且r5作为返回值
ENDPROC(__lookup_machine_type)
.align2
3:.long__proc_info_begin
.long__proc_info_end
4:.long.@“.”表示当前这行代码编译连接后的虚拟地址
.long__arch_info_begin
.long__arch_info_end
//
__lookup_machine_type函数的具体解析结束(archarmkernelhead-common.S)
//
movsr8,r5@invalidmachine(r5=0)?
beq__error_a@yes,errora
/*检查bootloader传入的参数列表atags的合法性*/
bl__vet_atags
//
__vet_atags函数的具体解析开始(archarmkernelhead-common.S)
//
关于参数链表:
内核参数链表的格式和说明可以从内核源代码目录树中的archarmincludeasmsetup.h中找到,参数链表必须以ATAG_CORE开始,以ATAG_NONE结束。这里的ATAG_CORE,ATAG_NONE是各个参数的标记,本身是一个32位值,例如:ATAG_CORE=0x54410001。其它的参数标记还包括:ATAG_MEM32,ATAG_INITRD,ATAG_RAMDISK,ATAG_COMDLINE等。每个参数标记就代表一个参数结构体,由各个参数结构体构成了参数链表。参数结构体的定义如下:
structtag{
structtag_headerhdr;
union{
structtag_corecore;
structtag_mem32mem;
structtag_videotextvideotext;
structtag_ramdiskramdisk;
structtag_initrdinitrd;
structtag_serialnrserialnr;
structtag_revisionrevision;
structtag_videolfbvideolfb;
structtag_cmdlinecmdline;
structtag_acornacorn;
structtag_memclkmemclk;
}u;
};
参数结构体包括两个部分,一个是tag_header结构体,一个是u联合体。
tag_header结构体的定义如下:
structtag_header{
u32size;
u32tag;
};
其中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等。
/*r8=machinfo
*Returns:
*r2eithervalidatagspointer,orzero
*/
__vet_atags:
tstr2,#0x3@aligned?r2指向该参数链表的起始位置,此处判断它是否字对齐
bne1f@如果没有对齐,跳到标号1处直接返回,并且把r2的值赋值为0,作为返回值
ldrr5,[r2,#0]@isfirsttagATAG_CORE?获取第一个tag结构的size
cmpr5,#ATAG_CORE_SIZE@判断该tag的长度是否合法
cmpner5,#ATAG_CORE_SIZE_EMPTY
bne1f@如果不合法,异常返回
ldrr5,[r2,#4]@获取第一个tag结构体的标记
ldrr6,=ATAG_CORE@取出标记ATAG_CORE的内容
cmpr5,r6@判断该标记是否等于ATAG_CORE
bne1f@如果不等,异常返回
movpc,lr@atagpointerisok,如果都相等,则正常返回
1:movr2,#0@异常返回值
movpc,lr@异常返回
ENDPROC(__vet_atags)
//
__vet_atags函数的具体解析结束(archarmkernelhead-common.S)
//
/*创建内核初始化页表*/
bl__create_page_tables
//
__create_page_tables函数的具体解析开始(archarmkernelhead.S)
//
/*
*r8=machinfo
*r9=cpuid
*r10=procinfo
*Returns:
*r4=physicalpagetableaddress
*/
/*在该文件的开头有如下宏定义*/
#defineKERNEL_RAM_PADDR(PHYS_OFFSET+TEXT_OFFSET)
.macropgtbl,rd
ldrrd,=(KERNEL_RAM_PADDR-0x4000)
.endm
其中:PHYS_OFFSET在arch/arm/mach-s3c2410/include/mach/memory.h定义,为UL(0x30000000),而TEXT_OFFSET在arch/arm/Makefile中定义,为内核镜像在内存中到内存开始位置的偏移(字节),为$(textofs-y)textofs-y也在文件arch/arm/Makefile中定义,为textofs-y:=0x00008000,r4=30004000为临时页表的起始地址,首先即是初始化16K的页表,高12位虚拟地址为页表索引,每个页表索引占4个字节,所以为4K*4=16K,大页表,每一个页表项,映射1MB虚拟地址.
__create_page_tables:
/*为内核代码存储区域创建页表,首先将内核起始地址-0x4000到内核起始地址之间的16K存储器清0,将创建的页表存于此处*/
pgtblr4@r4中存放的为页表的基地址,最终该地址会写入cp15的寄存器c2,这个值必须是16K对齐的
movr0,r4@把页表的基地址存放到r0中
movr3,#0@把r3清0
addr6,r0,#0x4000@r6指向16K的末尾
1:strr3,[r0],#4@把16K的页表空间清0
strr3,[r0],#4
strr3,[r0],#4
strr3,[r0],#4
teqr0,r6
bne1b
/*从proc_info_list结构中获取字段__cpu_mm_mmu_flags,该字段包含了存储空间访问权限等,此处指令执行之后r7=0x00000c1e*/
ldrr7,[r10,#PROCINFO_MM_MMUFLAGS]@mm_mmuflags
/*为内核的第一MB创建一致的映射,以为打开MMU做准备,这个映射将会被paging_init()移除,这里使用程序计数器来获得相应的段的基地址*/
movr6,pc
movr6,r6,lsr#20@startofkernelsection
orrr3,r7,r6,lsl#20@flags+kernelbase
strr3,[r4,r6,lsl#2]@identitymapping
/*MMU是通过C2中基地址(高18位)与虚拟地址的高12位组合成物理地址,在转换表中查找地址条目。R4中存放的就是这个基地址0x30004000*/
addr0,r4,#(KERNEL_START&0xff000000)>>18@r0=0x30007000r0存放的是转换表的起始位置
strr3,[r0,#(KERNEL_START&0x00f00000)>>18]!@r3存放的是内核镜像代码段的起始地址
ldrr6,=(KERNEL_END-1)@获取内核的尾部虚拟地址存于r6中
addr0,r0,#4@第一个地址条目存放在0x30007004处,以后依次递增
addr6,r4,r6,lsr#18@计算最后一个地址条目存放的位置
1:cmpr0,r6@填充这之间的地址条目
/*每一个地址条目代表了1MB空间的地址映射。物理地址将从0x30100000开始映射。0X30000000开始的1MB空间将在下面映射*/
addr3,r3,#1<<20
strlsr3,[r0],#4
bls1b
…………………………………
…………………………………………
/*为了使用启动参数,将物理内存的第一MB映射到内核虚拟地址空间的第一个MB,r4存放的是页表的地址。映射0X30000000开始的1MB空间PAGE_OFFSET=0XC0000000,PHYS_OFFSET=0X30000000,r0=0x30007000,上面是从0x30007004开始存放地址条目的*/
addr0,r4,#PAGE_OFFSET>>18
orrr6,r7,#(PHYS_OFFSET&0xff000000)@r6=0x30000c1e
.if(PHYS_OFFSET&0x00f00000)
orrr6,r6,#(PHYS_OFFSET&0x00f00000)
.endif
strr6,[r0]@将0x30000c1e存于0x30007000处。
………………………
………………………………
movpc,lr@子程序返回
ENDPROC(__create_page_tables)
//
__create_page_tables函数的具体解析结束(archarmkernelhead.S)
//
/*把__switch_data标号处的地址放入r13寄存器,当执行完__enable_mmu函数时会把r13寄存器的值赋值给pc,跳转到__switch_data处执行*/
ldrr13,__switch_data@addresstojumptoaftermmuhasbeenenabled
/*把__enable_mmu函数的地址值,赋值给lr寄存器,当执行完__arm920_setup时,返回后执行__enable_mmu*/
adrlr,BSYM(__enable_mmu)@return(PIC)address
//
__enable_mmu函数的具体解析开始(archarmkernelhead.S)
//
__enable_mmu:
#ifdefCONFIG_ALIGNMENT_TRAP
orrr0,r0,#CR_A//使能地址对齐错误检测
#else
bicr0,r0,#CR_A
#endif
#ifdefCONFIG_CPU_DCACHE_DISABLE
bicr0,r0,#CR_C//禁止数据cache
#endif
#ifdefCONFIG_CPU_BPREDICT_DISABLE
bicr0,r0,#CR_Z
#endif
#ifdefCONFIG_CPU_ICACHE_DISABLE
bicr0,r0,#CR_I//禁止指令cache
#endif//配置相应的访问权限并存入r5中
movr5,#(domain_val(DOMAIN_USER,DOMAIN_MANAGER)|/
domain_val(DOMAIN_KERNEL,DOMAIN_MANAGER)|/
domain_val(DOMAIN_TABLE,DOMAIN_MANAGER)|/
domain_val(DOMAIN_IO,DOMAIN_CLIENT))
mcrp15,0,r5,c3,c0,0//将访问权限写入协处理器
mcrp15,0,r4,c2,c0,0//将页表基地址写入基址寄存器C2,0X30004000
b__turn_mmu_on//跳转到程序段去打开MMU
ENDPROC(__enable_mmu)
文件linux/arch/arm/kernel/head.S中
__turn_mmu_on:
movr0,r0
mcrp15,0,r0,c1,c0,0//打开MMU同时打开cache等。
mrcp15,0,r3,c0,c0,0@readidreg读取id寄存器
movr3,r3
movr3,r3//两个空操作,等待前面所取的指令得以执行。
movpc,r13//程序跳转
ENDPROC(__turn_mmu_on)
//
__enable_mmu函数的具体解析结束(archarmkernelhead.S)
//
/*执行__arm920_setup函数(archarmmmproc-arm920.S),该函数完成对数据cache,指令cache,writebuffer等初始化操作*/
ARM(addpc,r10,#PROCINFO_INITFUNC)
//
__arm920_setup函数的具体解析开始(archarmmmproc-arm920.S)
//
在上面程序段.section".text.head","ax"的最后有这样几行:
addpc,r10,#PROCINFO_INITFUNC
R10中存放的是在函数__lookup_processor_type中成功匹配的结构体proc_info_list。对于arm920来说在文件linux/arch/arm/mm/proc-arm920.S中有:
.section".proc.info.init",#alloc,#execinstr
.type__arm920_proc_info,#object
__arm920_proc_info:
.long0x41009200
.long0xff00fff0
.longPMD_TYPE_SECT|/
PMD_SECT_BUFFERABLE|/
PMD_SECT_CACHEABLE|/
PMD_BIT4|/
PMD_SECT_AP_WRITE|/
PMD_SECT_AP_READ
.longPMD_TYPE_SECT|/
PMD_BIT4|/
PMD_SECT_AP_WRITE|/
PMD_SECT_AP_READ
b__arm920_setup
………………………………
addpc,r10,#PROCINFO_INITFUNC的意思跳到函数__arm920_setup去执行。
.type__arm920_setup,#function//表明这是一个函数
__arm920_setup:
movr0,#0//设置r0为0。
mcrp15,0,r0,c7,c7//使数据cahche,指令cache无效。
mcrp15,0,r0,c7,c10,4//使writebuffer无效。
#ifdefCONFIG_MMU
mcrp15,0,r0,c8,c7//使数据TLB,指令TLB无效。
#endif
adrr5,arm920_crval//获取arm920_crval的地址,并存入r5。
ldmiar5,{r5,r6}//获取arm920_crval地址处的连续8字节分别存入r5,r6。
mrcp15,0,r0,c1,c0//获取CP15下控制寄存器的值,并存入r0。
bicr0,r0,r5//通过查看arm920_crval的值可知该行是清除r0中相关位,为以后对这些位的赋值做准备
orrr0,r0,r6//设置r0中的相关位,即为mmu做相应设置。
movpc,lr//上面有操作adrlr,__enable_mmu,此处将跳到程序段__enable_mmu处。
.size__arm920_setup,.-__arm920_setup
.typearm920_crval,#object
arm920_crval:
crvalclear=0x00003f3f,mmuset=0x00003135,ucset=0x00001130
//
__arm920_setup函数的具体解析结束(archarmmmproc-arm920.S)
//
ENDPROC(stext)
接着往下分析linux/arch/arm/kernel/head-common.S中:
.type__switch_data,%object@定义__switch_data为一个对象
__switch_data:
.long__mmap_switched
.long__data_loc@r4
.long_data@r5
.long__bss_start@r6
.long_end@r7
.longprocessor_id@r4
.long__machine_arch_type@r5
.long__atags_pointer@r6
.longcr_alignment@r7
.longinit_thread_union+THREAD_START_SP@sp
/*
*ThefollowingfragmentofcodeisexecutedwiththeMMUoninMMUmode,
*andusesabsoluteaddresses;thisisnotpositionindependent.
*r0=cp#15controlregister
*r1=machineID
*r2=atagspointer
*r9=processorID
*/
/*其中上面的几个段的定义是在文件arch/arm/kernel/vmlinux.lds中指定*/
vmlinux.lds开始*
SECTIONS
{
……………………
#ifdefCONFIG_XIP_KERNEL
__data_loc=ALIGN(4);/*locationinbinary*/
.=PAGE_OFFSET+TEXT_OFFSET;
#else
.=ALIGN(THREAD_SIZE);
__data_loc=.;
#endif
.data:AT(__data_loc){//此处数据存储在上面__data_loc处。
_data=.;/*addressinmemory*/
*(.data.init_task)
…………………………
.bss:{
__bss_start=.;/*BSS*/
*(.bss)
*(COMMON)
_end=.;
}
………………………………
}
init_thread_union是init进程的基地址.在arch/arm/kernel/init_task.c中:
unionthread_unioninit_thread_union__attribute__((__section__(".init.task")))={INIT_THREAD_INFO(init_task)};
对照vmlnux.lds.S中,我们可以知道inittask是存放在.data段的开始8k,并且是THREAD_SIZE(8k)对齐的*/
vmlinux.lds结束*
__mmap_switched:
adrr3,__switch_data+4
ldmiar3!,{r4,r5,r6,r7}
……………………
………………………………
movfp,#0@清除bss段
1:cmpr6,r7
strccfp,[r6],#4
bcc1b
ARM(ldmiar3,{r4,r5,r6,r7,sp})/*把__machine_arch_type变量值放入r5中,把__atags_pointer变量的值放入r6中*/
strr9,[r4]@SaveprocessorID保存处理器id到processor_id所在的地址中
strr1,[r5]@Savemachinetype保存machineid到__machine_arch_type中
strr2,[r6]@Saveatagspointer保存参数列表首地址到__atags_pointer中
bicr4,r0,#CR_A@ClearAbit
stmiar7,{r0,r4}@Savecontrolregistervalues
bstart_kernel@程序跳转到函数start_kernel进入C语言部分。
ENDPROC(__mmap_switched)
到处我们的启动的第二阶段分析完毕。
后面会接着分析第三阶段。第三阶段完全是C语言代码,从start_kernel函数开始。
评论