分类: LINUX
2010-10-13 09:56:20
Linux启动之\arch\arm\kernel\head.S
当解压缩部分的head.S执行完后,就开始执行kernel\目录下真正的linux内核代码。在内核连接文件/kernel/vmlinux/lds里定义了这部分开始所处的段空间为.text.head,也即内核代码段的头
关键代码如下:
mrc p15,
0, r9, c0, c0 @ get processor
id//读出CPUid
bl __lookup_processor_type @
r5=procinfo r9=cpuid
movs r10,
r5 @
invalid processor (r5=0)?
beq __error_p @
yes, error 'p'
bl __lookup_machine_type @
r5=machinfo
movs r8,
r5 @
invalid machine (r5=0)?
beq __error_a @
yes, error 'a'
bl __vet_atags
bl __create_page_tables
大致流程为,寻找CPU类型查找机器信息,解析内核参数列表,创建内存分页机制
__lookup_processor_type,__lookup_machine_type,__vet_atags函数都在kernel\head-comm.S内,这个文件实际上是被包含在head.S内
Linux之所以把搜索机器类型和CPU类型独立出来,就是为了让内核尽可能的和bootload独立,增强移植性,把不同CPU的差异性处理减到最小。比如不同ARM架构的CPU处理中断的,打开MMU,cach操作是不同的,因此,在内核开始执行前需要定位CPU架构,比如高通利用的ARM11,Ti用的cortex-8架构
__lookup_machine_type 寻找的机器类型结构定义在arch\arm\include\asm\mach.h中
查询方法比较简单,利用bootloa传进来的参数依次查询上述结构表项
这个表项是在编译阶段将#define
MACHINE_START(_type,_name)宏定义的结构体struct
machine_desc 连接到
__arch_info段,那么结构体开始和结束地址用__arch_info_begin和__arch_info_end符号引用
3: .long .
.long __arch_info_begin
.long __arch_info_end
//r1 = 机器架构代码 number,由bootload最后阶段传进来
.type __lookup_machine_type,
%function
__lookup_machine_type:
adr r3,
3b
ldmia r3,
{r4, r5, r6}
sub r3,
r3,
r4 @ 此时没有开MMU,因此需要确定放置__arch_info_begin的实际物理地址
add r5,
r5,
r3 @ 调整地址,找到__arch_info的实际地址(连接地址和物理地址不一定一样,因此需要调整)
add r6,
r6,
r3 @
1: ldr r3,
[r5, #MACHINFO_TYPE] @ MACHINFO_TYPE=机器类型域的偏移量
teq r3,
r1 @ 是否和bootload传进来的参数相同?
beq 2f @ 找到则跳出循环
add r5,
r5, #SIZEOF_MACHINE_DESC @ 地址偏移至下个__arch_inf表项
cmp r5,
r6
blo 1b
mov r5,
#0 @ 未知的类型
2: mov pc, lr//返回
__lookup_processor_type的查询的结构为struct proc_info_list
机器类型确定后即开始解析(__vet_atags)内核参数列表,判断第一个参数类型是不是ATAG_CORE。
内核参数列表一般放在内核前面16K地址空间处。列表的表项由struct tag构成,每个struct tag有常见的以下类型:
:ATAG_CORE、ATAG_MEM、ATAG_CMDLINE、ATAG_RAMDISK、ATAG_INITRD等。
这些类型是宏定义,比如#define ATAG_CORE 0x54410001
arch\arm\include\asm\setup.h
struct tag_header {
__u32
size;
__u32
tag;
};
struct tag {
struct
tag_header hdr;
union
{
struct
tag_core core;//有效的内核
struct
tag_mem32 mem;
struct
tag_videotext videotext;
struct
tag_ramdisk ramdisk;//文件系统
struct
tag_initrd initrd;//临时根文件系统
struct
tag_serialnr serialnr;
struct
tag_revision revision;
struct
tag_videolfb videolfb;
struct
tag_cmdline cmdline;//命令行
}
u;
};
接下来就是创建页表,因为要使能MMU进行虚拟内存管理,因此必须创建映射用的页表。页表就像一个函数发生器,保证访问虚拟地址时能从物理地址里取到正确代码
pgtbl r4 @
page table address
//页表放置的位置可由下面的宏确定,即在内核所在空间的前16K处
.macro pgtbl, rd
ldr \rd,
=(KERNEL_RAM_PADDR - 0x4000)
.endm
mov r0,
r4
mov r3,
#0
add r6,
r0, #0x4000//16K的空间,r6即是页表结束处
1: str r3,
[r0], #4//清空页表项,页表项共有16K/4项
str r3,
[r0], #4
str r3,
[r0], #4
str r3,
[r0], #4
teq r0,
r6
bne 1b
ldr r7,
[r10, #PROCINFO_MM_MMUFLAGS]
//从从差得的proc_info_list结构PROCINFO_MM_MMUFLAGS处获取MMU的信息
/*
为内核创建1M的映射空间,这里是按照1:1一致映射,即代码的基地址(高12bit)对应相同的物理块地址。这种映射关系只是在启动阶段,在跳进start_kernel后会被paging_init().移除。这种映射可以直接利用当前地址的高12bit作为基地址,这种方式很巧妙,因为当前的PC(加颜色处的地址)依然在1M空间内,因此,高12bit(段基地址)在1M空间内都是相同的。
*/
mov r6,
pc, lsr #20 @ 内核映像的基地址
orr r3,
r7, r6, lsl #20 @ 基地址偏移后再加上标示符,即可得一个页表项的值
str r3,
[r4, r6, lsl #2] @将此表项按照页表项的索引存入对应的表项中。比如,若//基地址是0xc0001000,那么存入页表的第0xc00项中
//目前的映射依然是1:1的映射
//然后移到下个段基地址处,开始映射此KERNEL_START对应的空间
//这个空间映射的物理地址与上面的相同,也就是两个虚拟地址映射到了同一个物理地址空间
//r0+基地址组成//在第一级页表中索引到相关的项
add r0,
r4, #(KERNEL_START & 0xff000000) >> 18
str r3,
[r0, #(KERNEL_START & 0x00f00000) >> 18]!
ldr r6,
=(KERNEL_END - 1)
add r0,
r0, #4//移到下个表项
add r6,
r4, r6, lsr #18//结束的基地址
1: cmp r0, r6
add r3,
r3, #1 << 20//下个1M物理地址空间
strls r3,
[r0], #4//建立映射表项,开始创建所有的内核空间页表项
bls 1b//
#ifdef CONFIG_XIP_KERNEL
/*
*
Map some ram to cover our .data and .bss areas.
*/
orr r3,
r7, #(KERNEL_RAM_PADDR & 0xff000000)
.if (KERNEL_RAM_PADDR
& 0x00f00000)
orr r3,
r3, #(KERNEL_RAM_PADDR & 0x00f00000)
.endif
add r0,
r4, #(KERNEL_RAM_VADDR & 0xff000000) >> 18
str r3,
[r0, #(KERNEL_RAM_VADDR & 0x00f00000) >> 18]!
ldr r6,
=(_end - 1)
add r0,
r0, #4
add r6,
r4, r6, lsr #18
1: cmp r0, r6
add r3,
r3, #1 << 20
strls r3,
[r0], #4
bls 1b
#endif
/*
*
Then map first 1MB of ram in case it contains our boot params.
*/
//虚拟ram地址的第一个1M空间包含了参数列表,也需要映射
add r0,
r4, #PAGE_OFFSET >> 18
orr r6,
r7, #(PHYS_OFFSET & 0xff000000)
.if (PHYS_OFFSET
& 0x00f00000)
orr r6,
r6, #(PHYS_OFFSET & 0x00f00000)
.endif
str r6,
[r0]
mov pc,
lr//页表建立完成,返回
页表创建后,具体的映射空间如下图:
执行完上述页表创建,开始执行内核跳转:
ldr r13,
__switch_data @
address to jump to after
@
mmu has been enabled
adr lr,
__enable_mmu @ return (PIC)
address
add pc,
r10, #PROCINFO_INITFUNC
__switch_data 是一个数据结构,如下
.type __switch_data,
%object
__switch_data:
.long __mmap_switched
.long __data_loc @
r4
.long __data_start @
r5
.long __bss_start @
r6
.long _end @
r7
.long processor_id @
r4
.long __machine_arch_type @
r5
.long __atags_pointer @
r6
.long cr_alignment @
r7
.long init_thread_union
+ THREAD_START_SP @ sp
语句“add pc,
r10, #PROCINFO_INITFUNC”通过查表调用proc-v7.s中__v7_setup函数,该函数末尾通过将lr寄存器赋给pc,导致对__enable_mmu的调用,完成使能mmu的操作,之后将r13寄存器值赋给pc,调用__switch_data数据结构中的第一个函数__mmap_switched,
.type __mmap_switched,
%function
__mmap_switched:
adr r3,
__switch_data + 4
ldmia r3!,
{r4, r5, r6, r7}
cmp r4,
r5 @ 拷贝数据段
1: cmpne r5,
r6
ldrne fp,
[r4], #4
strne fp,
[r5], #4
bne 1b
mov fp,
#0 @ 清除BSS段
1: cmp r6, r7
strcc fp,
[r6],#4
bcc 1b
ldmia r3,
{r4, r5, r6, r7, sp}//然后调整指针到processor_id 域
str r9,
[r4] @ 保存CPU ID
str r1,
[r5] @保存机器类型
str r2,
[r6] @ 保存参数列表指针
bic r4,
r0,
#CR_A @
Clear 'A' bit
stmia r7,
{r0,
r4} @ 保存控制信息
b start_kernel
最终调用init\main.c文件中的start_kernel函数。
这个start_kernel正是kernel\init\main.c的内核起始函数
chinaunix网友2010-10-13 20:09:30
很好的, 收藏了 推荐一个博客,提供很多免费软件编程电子书下载: http://free-ebooks.appspot.com