Linux内核启动流程分析二文档格式.docx
《Linux内核启动流程分析二文档格式.docx》由会员分享,可在线阅读,更多相关《Linux内核启动流程分析二文档格式.docx(27页珍藏版)》请在冰点文库上搜索。
/*
初始化代码段*/
_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
#ifndef
CONFIG_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。
ARM
kernel将SDRAM的开始地址定义为PHYS_OFFSET。
经bootloader加载kernel并由自解压部分代码运行后,最终kernel被放置到KERNEL_RAM_PADDR(=PHYS_OFFSET
+
TEXT_OFFSET,即0x30008000)地址上的一段内存,经此放置后,kernel代码以后均不会被移动。
在进入kernel代码前,即bootloader和自解压缩阶段,ARM未开启MMU功能。
因此kernel启动代码一个重要功能是设置好相应的页表,并开启MMU功能。
为了支持MMU功能,kernel镜像中的所有符号,包括代码段和数据段的符号,在链接时都生成了它在开启MMU时,所在物理内存地址映射到的虚拟内存地址。
以arm
kernel第一个符号(函数)stext为例,在编译链接,它生成的虚拟地址是0xc0008000,而放置它的物理地址为0x30008000(还记得这是PHYS_OFFSET+TEXT_OFFSET吗?
)。
实际上这个变换可以利用简单的公式进行表示:
va
pa
–
PHYS_OFFSET
PAGE_OFFSET。
Arm
linux最终的kernel空间的页表,就是按照这个关系来建立。
之所以较早提及arm
linux
的内存映射,原因是在进入kernel代码,里面所有符号地址值为清一色的0xCXXXXXXX地址,而此时ARM未开启MMU功能,故在执行stext函数第一条执行时,它的PC值就是stext所在的内存地址(即物理地址,0x30008000)。
因此,下面有些代码,需要使用地址无关技术。
__HEAD
/*该宏定义了下面的代码位于"
.head.text"
段内*/
.type
stext,
%function
/*声明stext为函数*/
ENTRY(stext)
/*第二阶段的入口地址*/
setmode
PSR_F_BIT
|
PSR_I_BIT
SVC_MODE,
r9
@
ensure
svc
mode
and
irqs
disabled
进入超级权限模式,关中断
/*从协处理器CP15,C0读取CPU
ID,然后在__proc_info_begin开始的段中进行查找,如果找到,则返回对应处理器相关结构体在物理地址空间的首地址到r5,最后保存在r10中*/
mrc
p15,
r9,
c0,
c0
get
processor
id
取出cpu
id
bl
__lookup_processor_type
r5=procinfo
r9=cpuid
/**********************************************************************/
__lookup_processor_type函数的具体解析开始(\arch\arm\kernel\
head-common.S)
在讲解该程序段之前先来看一些相关知识,内核所支持的每一种CPU
类型都由结构体proc_info_list来描述。
该结构体在文件arch/arm/include/asm/procinfo.h
中定义:
struct
unsigned
int
cpu_val;
cpu_mask;
long
__cpu_mm_mmu_flags;
used
by
head.S
*/
__cpu_io_mmu_flags;
__cpu_flush;
const
char
*arch_name;
*elf_name;
elf_hwcap;
*cpu_name;
*proc;
cpu_tlb_fns
*tlb;
cpu_user_fns
*user;
cpu_cache_fns
*cache;
};
对于
arm920
来说,其对应结构体在文件
linux/arch/arm/mm/proc-arm920.S
中初始化。
.section
"
.proc.info.init"
#alloc,
#execinstr
/*定义了一个段,下面的结构体存放在该段中*/
__arm920_proc_info,#object
/*声明一个结构体对象*/
__arm920_proc_info:
/*为该结构体赋值*/
.long
0x41009200
0xff00fff0
PMD_TYPE_SECT
\
PMD_SECT_BUFFERABLE
PMD_SECT_CACHEABLE
PMD_BIT4
PMD_SECT_AP_WRITE
PMD_SECT_AP_READ
b
__arm920_setup
…………………………………
表明了该结构在编译后存放的位置。
在链接文件
arch/arm/kernel/vmlinux.lds
中:
SECTIONS
#ifdef
CONFIG_XIP_KERNEL
XIP_VIRT_ADDR(CONFIG_XIP_PHYS_ADDR);
#else
PAGE_OFFSET
TEXT_OFFSET;
#endif
.text.head
*(.text.head)
Init
code
data
INIT_TEXT
*(.proc.info.init)
*(.arch.info.init)
*(.taglist.init)
………………………………
}
所有CPU类型对应的被初始化的
proc_info_list结构体都放在
__proc_info_begin和__proc_info_end之间。
/
*
*
cpuid
Returns:
r5
proc_info
pointer
in
physical
address
space
cpuid
(preserved)
*/
__lookup_processor_type:
adr
r3,
3f
@r3存储的是标号
3
的物理地址(由于没有启用
mmu
,所以当前肯定是物理地址)
ldmia
{r5
-
r7}
R5=__proc_info_begin,r6=__proc_info_end,r7=标号4处的虚拟地址,即4:
处的地址
add
#8
得到4处的物理地址,刚好是跳过两条指令
sub
r7
offset
between
virt&
phys得到虚拟地址和物理地址之间的offset
/*利用offset
,将
和
r6
中保存的虚拟地址转变为物理地址*/
r5,
r3
convert
virt
addresses
to
r6,
1:
{r3,
r4}
value,
mask
r3=
cpu_val
r4=
cpu_mask
r4,
wanted
bits;
中存放的是先前读出的
ID
,此处屏蔽不需要的位
teq
r4
查看代码和CPU
硬件是否匹配(
比如想在arm920t上运行为cortex-a8编译的内核?
不让)
beq
2f
如果相等则跳转到标号2处,执行返回指令
#PROC_INFO_SZ
sizeof(proc_info_list结构的长度,在这等于48)如果没找到,
跳到下一个proc_info_list
处
cmp
判断是不是到了该段的结尾
blo
1b
如果没有,继续跳到标号1处,查找下一个
mov
#0
unknown
,如果到了结尾,没找到匹配的,就把0赋值给r5,然后返回
2:
pc,
lr
找到后返回,r5指向找到的结构体
ENDPROC(__lookup_processor_type)
.align
2
3:
__proc_info_begin
__proc_info_end
4:
@“.”表示当前这行代码编译连接后的虚拟地址
__arch_info_begin
__arch_info_end
__lookup_processor_type函数的具体解析结束(\arch\arm\kernel\
movs
r10,
invalid
(r5=0)?
__error_p
yes,
error
'
p'
/*机器
ID是由u-boot引导内核是通过thekernel第二个参数传递进来的,现在保存在r1中,在__arch_info_begin开始的段中进行查找,如果找到,则返回machine对应相关结构体在物理地址空间的首地址到r5,最后保存在r8中。
__lookup_machine_type
r5=machinfo
__lookup_machine_type函数的具体解析开始(\arch\arm\kernel\
每一个CPU
平台都可能有其不一样的结构体,描述这个平台的结构体是
。
这个结构体在文件arch/arm/include/asm/mach/arch.h
nr;
architecture
number
phys_io;
start
of
io
对于平台smdk2410
来说其对应
结构在文件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
#define
MACHINE_START(_type,_name)
static
__mach_desc_##_type
__used
__attribute__((__section__("
.arch.info.init"
)))
.nr
MACH_TYPE_##_type,
.name
_name,
)))表明该结构体在并以后存放的位置。
链接脚本文件
中
在__arch_info_begin和
__arch_info_end之间存放了linux内核所支持的所有平台对应的
结构体。
/*
number
mach_info
__lookup_machine_type:
4b
把标号4处的地址放到r3寄存器里面
{r4,
r6}
R
4
标号4处的虚拟地址
,r
5
6=
phys
计算出虚拟地址与物理地址的偏移
/*读取machine_desc结构的
nr
参数,对于smdk2410
来说该值是
MACH_TYPE_SMDK2410,这个值在文件linux/arch/arm/tools/mach-types
中:
smdk2410
ARCH_SMDK2410
SMDK2410
193
ldr
[r5,
#MACHINFO_TYPE]
type
mat