Linux内核启动流程
arch/arm/kernel/head.S该文件末尾包含了语句:#include "head-common.S",__lookup_processor_type函数在arch/arm/kernel/head-common.S中定义。关键数据结构:struct proc_info_list,在arch/arm/include/asm/procinfo.h文件中定义,Cortex对此数据结构
__lookup_processor_type函数:
__lookup_processor_type:
ARM( adr r3, 3f )
ARM( ldmda r3, {r5 - r7} )
THUMB( adr r3, 3f+4 )
THUMB( ldmdb r3, {r5 - r7} )
THUMB( sub r3, r3, #4 )
sub r3, r3, r7 @ get offset between virt&phys
add r5, r5, r3 @ convert virt addresses to
add r6, r6, r3 @ physical address space
1: ldmia r5, {r3, r4} @ value, mask
and r4, r4, r9 @ mask wanted bits
teq r3, r4
beq 2f
add r5, r5, #PROC_INFO_SZ @ sizeof(proc_info_list)
cmp r5, r6
blo 1b
mov r5, #0 @ unknown processor
2: mov pc, lr
ENDPROC(__lookup_processor_type)
/*
* Look in <asm/procinfo.h> and arch/arm/kernel/arch.[ch] for
* more information about the __proc_info and __arch_info structures.
*/
.long __proc_info_begin
.long __proc_info_end
3: .long .
.long __arch_info_begin
.long __arch_info_end
语句“adr r3, 3f”向前寻找到标号3的地址,赋给寄存器r3;
语句“ldmda r3, {r5 - r7}”将寄存器r3所指地址处的数据分别读入r5,r6,r7,由于ldmda是“过后减少装载”方式,因此r6与r7分别读入的应是__proc_info_end和__proc_info_begin;
函数返回后,有一个操作:
movs r10, r5
将寄存器r5中的proc_info入口地址保存在寄存器r10中,这是为了后来调用__create_page_tables准备的
__lookup_machine_type函数:
__lookup_machine_type:
adr r3, 3b
ldmia r3, {r4, r5, r6}
sub r3, r3, r4 @ get offset between virt&phys
add r5, r5, r3 @ convert virt addresses to
add r6, r6, r3 @ physical address space
1: ldr r3, [r5, #MACHINFO_TYPE] @ get machine type
teq r3, r1 @ matches loader number?
beq 2f @ found
add r5, r5, #SIZEOF_MACHINE_DESC @ next machine_desc
cmp r5, r6
blo 1b
mov r5, #0 @ unknown machine
2: mov pc, lr
ENDPROC(__lookup_machine_type)
此函数紧挨着__lookup_processor_type,与__lookup_processor_type共用标号3,不同的是语句“adr r3, 3b”为向后查找标号3,将标号3的地址赋给寄存器r3;
接下来的语句“ldmia r3, {r4, r5, r6}”采用了过后增加装载指令查表,将__arch_info_begin、__arch_info_end地地址分别装入寄存器r5、r6;
回顾一下标号3:
/*
* Look in <asm/procinfo.h> and arch/arm/kernel/arch.[ch] for
* more information about the __proc_info and __arch_info structures.
*/
.long __proc_info_begin
.long __proc_info_end
3: .long .
.long __arch_info_begin
.long __arch_info_end
可见,__lookup_processor_type函数通过ldmda指令查找__proc_info_表格,__lookup_machine_type函数则通过ldmia指令查找__arch_info_表格。
__arch_info_表格的生成,可见arch/arm/include/asm/mach/arch.h文件中定义的宏MACHINE_START:
/*
* Set of macros to define architecture features. This is built into
* a table by the linker.
*/
#define MACHINE_START(_type,_name) /
static const struct machine_desc __mach_desc_##_type /
__used /
__attribute__((__section__(".arch.info.init"))) = { /
.nr = MACH_TYPE_##_type, /
.name = _name,
#define MACHINE_END /
};
以TI OMAP3 参考板为例,MACHINE_START表格的具体填写,在arch/arm/mach-omap2/board-zoom2.c文件中:
MACHINE_START(OMAP_ZOOM2, "OMAP ZOOM2 board")
.phys_io = 0x48000000,
.io_pg_offst = ((0xd8000000) >> 18) & 0xfffc,
.boot_params = 0x80000100,
.map_io = omap_ldp_map_io,
.init_irq = omap_ldp_init_irq,
.init_machine = omap_ldp_init,
.timer = &omap_timer,
MACHINE_END
回到__lookup_machine_type函数,寄存器r1在kernel入口时已被赋予了type的数值,从u-boot代码include/asm-arm/mach-types.h文件中定义:
#define MACH_TYPE_OMAP_ZOOM2 1967
可知,boot向Kernel传入的数据应为1967,kernel中,OMAP_ZOOM2对应的type值由脚本文件arch/arm/tools/mach-types指定,该脚本中,行
omap_zoom2 MACH_OMAP_ZOOM2 OMAP_ZOOM2 1967
指定OMAP_ZOOM2对应的type值为1967,因此,函数__lookup_machine_type的查表过程成功,寄存器r5中返回ZOOM2对应信息的数据结构首地址;函数返回后,有一个操作:
movs r8, r5
将寄存器r5中的__arch_info入口地址保存在寄存器r8中,这也是为了后来调用__create_page_tables准备的
需要关注的数据结构machine_desc(在文件arch/arm/include/asm/mach/arch.h中定义):
struct machine_desc {
/*
* Note! The first four elements are used
* by assembler code in head.S, head-common.S
*/
unsigned int nr; /* architecture number */
unsigned int phys_io; /* start of physical io */
unsigned int io_pg_offst; /* byte offset for io
* page tabe entry */
const char *name; /* architecture name */
unsigned long boot_params; /* tagged list */
unsigned int video_start; /* start of video RAM */
unsigned int video_end; /* end of video RAM */
unsigned int reserve_lp0 :1; /* never has lp0 */
unsigned int reserve_lp1 :1; /* never has lp1 */
unsigned int reserve_lp2 :1; /* never has lp2 */
unsigned int soft_reboot :1; /* soft reboot */
void (*fixup)(struct machine_desc *,
struct tag *, char **,
struct meminfo *);
void (*map_io)(void);/* IO mapping function */
void (*init_irq)(void);
struct sys_timer *timer; /* system tick timer */
void (*init_machine)(void);
};
有许多事,需要回到u-boot才能说清楚……
include/configs/XXXX.h中(XXXX视具体平台而定),一般会作类似如下定义:
#define CONFIG_BOOTCOMMAND "mmcinit; fatload mmc 0 0x81c00000 uImage; bootm 0x81c00000"
编译时该宏CONFIG_BOOTCOMMAND传递给一个ENV项bootcmd,而在common/main.c中,函数main_loop取出了该env项,作为boot的过程开始启动kernel:
s = getenv ("bootcmd");
debug ("### main_loop: bootcmd=/"%s/"/n", s ? s : "<UNDEFINED>");
if (bootdelay >= 0 && s && !abortboot (bootdelay)) {
# ifdef CONFIG_AUTOBOOT_KEYED
int prev = disable_ctrlc(1); /* disable Control C checking */
# endif
# ifndef CFG_HUSH_PARSER
run_command (s, 0);
我们关心的是最后一条命令bootm,在文件common/cmd_bootm.c中已经定义:
U_BOOT_CMD(
bootm, CFG_MAXARGS, 1, do_bootm,
"bootm - boot application image from memory/n",
"[addr [arg ...]]/n - boot application image stored in memory/n"
"/tpassing arguments 'arg ...'; when booting a Linux kernel,/n"
"/t'arg' can be the address of an initrd image/n"
);
即,真正要运行的程序是do_bootm。
欢迎回到Kernel中……(看了2.6.29内核,做了些修订,关于it指令的)
继续说__vet_atags函数,这个函数仍旧定义在arch/arm/kernel/head-common.s文件中:
/* Determine validity of the r2 atags pointer. The heuristic requires
* that the pointer be aligned, in the first 16k of physical RAM and
* that the ATAG_CORE marker is first and present. Future revisions
* of this function may be more lenient with the physical address and
* may also be able to move the ATAGS block if necessary.
*
* r8 = machinfo
*
* Returns:
* r2 either valid atags pointer, or zero
* r5, r6 corrupted
*/
__vet_atags:
tstr2, #0x3@ aligned?
bne1f
ldrr5, [r2, #0]@ is first tag ATAG_CORE?
subsr5, r5, #ATAG_CORE_SIZE
bne1f
ldrr5, [r2, #4]
ldrr6, =ATAG_CORE
cmpr5, r6
bne1f
movpc, lr@ atag pointer is ok
1:movr2, #0
movpc, lr
ENDPROC(__vet_atags)
由TI的芯片手册知,OMAP3430的SDRAM地址空间自0x80000000起,计1GB空间。函数开始的注释明确要求ATAG表需要在RAM物理地址前16KB,即0x80000000-0x80004000范围内,r2的0x80000100满足此要求。简单的判断了一下ATAG表的起始地址是否对齐,是否以ATAG_CORE作为开头标志,该函数返回。
__create_page_tables函数,这是要分析的重点函数,该函数就在arch/arm/kernel/head.s文件末尾。
先来看一个宏定义:
.macro pgtbl, rd
ldr /rd, =(KERNEL_RAM_PADDR - 0x4000)
.endm
KERNEL_RAM_PADDR的定义为:
#define KERNEL_RAM_PADDR (PHYS_OFFSET + TEXT_OFFSET)
PHYS_OFFSET在文件arch/arm/plat-omap/include/mach/memory.h中(怎么找到这里的,其实是arch/arm/kernel/head.s中包含了arch/arm/include/asm/memory.h中又包含了arch/arm/plat-omap/include/mach/memory.h的缘故……)定义为0x80000000,而TEXT_OFFSET则需要在Makefile中寻找到答案。arch/arm/Makefile中定义
……
textofs-y := 0x00008000
……
TEXT_OFFSET := $(textofs-y)
可见,TEXT_OFFSET的值为0x00008000,再回到宏pgtbl,可计算出KERNEL_RAM_PADDR的值为0x80000000+0x00008000=0x80008000最后赋给寄存器值为0x80008000-0x4000=0x80004000。
__create_page_tables函数的第一条语句为:
pgtbl r4 @ page table address
根据前面计算,寄存器r4赋值0x80004000,该地址是页表的起始地址,在kernel的入口地址(0x80008000)之前16KB。
之后,对该页表进行初始化操作。
第一步、将这16KB空间清零:
/*
* Clear the 16K level 1 swapper page table
*/
mov r0, r4
mov r3, #0
add r6, r0, #0x4000
1: str r3, [r0], #4
str r3, [r0], #4
str r3, [r0], #4
str r3, [r0], #4
teq r0, r6
bne 1b
第二步、填写内核所在页的页目录项
ldr r7, [r10, #PROCINFO_MM_MMUFLAGS] @ mm_mmuflags
这里需要再来回顾一下proc_info_list数据结构(在arch/arm/include/asm/procinfo.h文件中定义):
struct proc_info_list {
unsigned int cpu_val;
unsigned int cpu_mask;
unsigned long __cpu_mm_mmu_flags; /* used by head.S */
unsigned long __cpu_io_mmu_flags; /* used by head.S */
unsigned long __cpu_flush; /* used by head.S */
const char *arch_name;
const char *elf_name;
unsigned int elf_hwcap;
const char *cpu_name;
struct processor *proc;
struct cpu_tlb_fns *tlb;
struct cpu_user_fns *user;
struct cpu_cache_fns *cache;
};
指令ldr r7, [r10, #PROCINFO_MM_MMUFLAGS]取到了结构中的数据__cpu_mm_mmu_flags,送入寄存器r7,该参数在arch/arm/mm/proc-v7.s文件中定义如下:
.long PMD_TYPE_SECT | /
PMD_SECT_BUFFERABLE | /
PMD_SECT_CACHEABLE | /
PMD_SECT_AP_WRITE | /
PMD_SECT_AP_READ
计算下来,应该是:0b0000110000001110,即0x0c0e。
参照如下表格:arm采用的是段式页表,按照VMSA(Virtual Memory System Architecture)规定,每个表项描述1MB空间,16KB可存放4K个表项,覆盖4GB虚拟地址空间。关于页表的详细说明,可参阅ARM Architecture Reference Manual DDI0406B的B3章节。
mov r6, pc, lsr #20 @ start of kernel section
当前pc的高12位,即内核所在物理地址的索引放入寄存器r6;
orr r3, r7, r6, lsl #20 @ flags + kernel base
索引号与寄存器r7中的标志字节相或,合并后的描述符放入寄存器r3;
str r3, [r4, r6, lsl #2] @ identity mapping
每个描述符占4字节,所以内核的起始页目录项地址应该在[r4]+[r6]x4处,将r3的描述符内容送入其中;
从__v7_setup函数返回来,进入到__enable_mmu过程,做起飞前的最后确认:
__enable_mmu:
#ifdef CONFIG_ALIGNMENT_TRAP
orr r0, r0, #CR_A
#else
bic r0, r0, #CR_A
#endif
根据配置选项,决定是否打开数据对齐检查,一般是要打开的;
#ifdef CONFIG_CPU_DCACHE_DISABLE
bic r0, r0, #CR_C
#endif
根据配置选项,决定是否关闭开数据缓冲区,一般是不需要关闭的;
#ifdef CONFIG_CPU_BPREDICT_DISABLE
bic r0, r0, #CR_Z
#endif
根据配置选项,决定是否要关闭分支预测功能,一般是不需要关闭的;
#ifdef CONFIG_CPU_ICACHE_DISABLE
bic r0, r0, #CR_I
#endif
根据配置选项,决定是否关闭开指令缓冲区,一般是不需要关闭的;
mov r5, #(domain_val(DOMAIN_USER, DOMAIN_MANAGER) | /
domain_val(DOMAIN_KERNEL, DOMAIN_MANAGER) | /
domain_val(DOMAIN_TABLE, DOMAIN_MANAGER) | /
domain_val(DOMAIN_IO, DOMAIN_CLIENT))
结合数据手册,此段是将D0、D1两个域,D1对应于USER域,D0对应于KERNEL和TABLE域,配置为管理模式,对这两个域的访问和执行不受TLB中相应标志位影响,D2域,对应于IO域,配置为客户模式,对此域的访问受TLB中相应标志位的检查;
mcr p15, 0, r5, c3, c0, 0 @ load domain access register
写入域访问控制寄存器,其实和__v7_setup函数中写入的值相同;
mcr p15, 0, r4, c2, c0, 0 @ load page table pointer
__v7_setup函数中也已经写过这个寄存器了;
b __turn_mmu_on
__turn_mmu_on就在后面紧跟着,不明白为何还要b一下;
ENDPROC(__enable_mmu)
再看看紧接着的__turn_mmu_on函数:
/*
* Enable the MMU. This completely changes the structure of the visible
* memory space. You will not be able to trace execution through this.
* If you have an enquiry about this, *please* check the linux-arm-kernel
* mailing list archives BEFORE sending another post to the list.
*
* r0 = cp#15 control register
* r13 = *virtual* address to jump to upon completion
*
* other registers depend on the function called upon completion
*/
.align 5
原来是因为有这个,所以要b过来,但为什么是以5对齐?在网上搜索了一下,原来.align n的语法,有按照n对齐的,也有按照2的n次方对齐的,而arm-linux是按照2的n次方对齐的,即,是以20字节位置对齐的,详见某篇博文:http://www.eetop.cn/blog/html/45/11145-1211.html
__turn_mmu_on:
mov r0, r0
暂时认为这是常规的nop语句……
mcr p15, 0, r0, c1, c0, 0 @ write control reg
终于将寄存器r0的值写入到了mmu控制寄存器中,此时的CPU终于可以“内牛满面”!
mrc p15, 0, r3, c0, c0, 0 @ read id reg
对于Cortex A8处理器来说,此时寄存器r3的值也许是:0x413fc082字样,其中41代表ARM,c08就是Cortex A8;
mov r3, r3
mov r3, r3
继续认为这是nop语句,或者说,在这里是清除流水线,那么,之后的CPU,已经平稳过渡到了虚拟地址空间的环境?(我不敢确认)……
mov pc, r13
无需废话了,很久以前,寄存器r13就准备好了__switch_data的入口地址;
ENDPROC(__turn_mmu_on)
更多推荐
所有评论(0)