嵌入式 arm平台kernel启动第二阶段分析
第二阶段的代码是从\arch\arm\kernel\head.S开始的。
内核启动第二阶段主要完成的工作有,cpuID检查,machineID(也就是开发板ID)检查,创建初始化页表,设置C代码运行环境,跳转到内核第一个真正的C函数startkernel开始执行。
这一阶段涉及到两个重要的结构体:
(1)一个是structproc_info_list主要描述CPU相关的信息,定义在文件arch\arm\include\asm\procinfo.h中,与其相关的函数及变量在文件arch/arm/mm/proc_arm920.S中被定义和赋值。
(2)另一个结构体是描述开发板或者说机器信息的结构体structmachine_desc,定义在\arch\arm\include\asm\mach\arch.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
}
*链接脚本
下面开始代码\arch\arm\kernel\head.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所在的内存地址(
嵌入式arm平台kernel启 相关文章:
- 嵌入式 arm平台kernel启动第一阶段汇编head.s分析(11-09)
- Windows CE 进程、线程和内存管理(11-09)
- RedHatLinux新手入门教程(5)(11-12)
- uClinux介绍(11-09)
- openwebmailV1.60安装教学(11-12)
- Linux嵌入式系统开发平台选型探讨(11-09)