1、 1介绍2 2相关定义介绍3 21TEXTADDR 3 22stext 3 23swapper_pg_dir 4 24(M)pgtbl .4 25(M)krnladr 5 26.proc.info段.5 27_proc_info_begin/_proc_info_end .6 28.arch.info段.7 29_arch_info_begin/_arch_info_end .8 3代码分析9 31KERNEL ENTRY 9 32_arm920_setup .11 33_ret .13 34_mmap_switched .14 35_lookup_processor_type 16 36_l
2、ookup_architecture_type .16 37_create_page_tables .19 1介绍 这是一篇对armlinux内核启动的分析,主要是arch/arm/kernel/head-armv.S文件, head-armv.S文件是整个内核的入口,也就是说bootloader执行完毕后将跳转到head-armv.S的第一条指令,head-armv.S执行完后将跳转到start_kernel(),在head-armv.S的执行过程中也用到了其他一些文件,包括arch/arm/kernel/debug-armv.S、arch/arm/mm/proc-arm920.S等等 由于
3、此分析基于MX1的内核启动过程,因此除了通用代码,只有定义在CONFIG_ARCH_MX1ADS下的代码和proc-arm920.S(arm920是MX1的CPU)的代码被分析 在下面程序流程的说明中,MX1板子启动过程中的寄存器值将会用绿色字体表示出来,而对于专门针对MX1的代码则会用下划线字体表示 2相关定义介绍 21TEXTADDR TEXTADDR是内核Image的映像地址,也是内核Image所处的虚拟地址,它在系统内核空间起始地址通常是0xC0000000(这相对应于物理内存开始的地方)+32K的位置,也就是0xC0008000处TEXTADDR的赋值在arch/arm/Makefi
4、le文件中:(0xC0008000) ifeq ($(CONFIG_CPU_32),y) PROCESSOR = armv TEXTADDR = 0xC0008000 LDSCRIPT = arch/arm/vmlinux-armv.lds.in endif 在内核映像之前的16K空间用来存放内核的页目录表,这就是为什么TEXTADDR要在系统要放在0xC0008000的缘故它必须留出足够的物理空间来放页表 在head-armv.S中TEXTADDR将被检测: #if (TEXTADDR /此处的映像地址为TEXTADDR .init : /* Init code and data */ _s
5、text = .; /stext为此处的物理地址 23swapper_pg_dir swapper_pg_dir是页表的映像地址,由于启动页表在内核Image之前的16K处,因此它等于0xC0004000,它的定义在head-armv.S文件中 .globl SYMBOL_NAME(swapper_pg_dir) /设置swapper_pg_dir .equ SYMBOL_NAME(swapper_pg_dir), TEXTADDR - 0x4000 在定义init进程的mm_struct结构的宏INIT_MM中,swapper_pg_dir作为页表基址被赋给init_mm,INIT_MM定义
6、在include/linux/sched.h文件中: #define INIT_MM(name) pgd: swapper_pg_dir, 24(M)pgtbl pgtbl是一个用于获得启动页表物理地址的宏,它将stext减去16K给reg,它也在head-armv.S中定义: .macro pgtbl, reg, rambase adr reg, stext sub reg, reg, #0x4000 /reg=stext-0x4000 .endm 25(M)krnladr 这个宏用于由pgtable获得内核空间的起始物理地址的所在的段(MB),将pgtable,也就是页表地址(和内核空间的
7、起始物理地址在同一个段内)和0x000FFFFF相与,因为页表地址后12位为零,所以将其和0x000FF000相与 /* * Since the page table is closely related to the kernel start address, we * can convert the page table base address to the base address of the section * containing both. */ .macro krnladr, rd, pgtable, rambase bic rd, pgtable, #0x000ff000
8、.endm 26.proc.info段 .proc.info段中存放的是各种处理器的信息,每个处理器的信息用一个proc_info_list结构来表示,这个结构在include/asm/procinfo.h文件中声明: struct proc_info_list unsigned int cpu_val; /处理器类型 unsigned int cpu_mask; /处理器类型掩码 unsigned long _cpu_mmu_flags; /* used by head-armv.S */ unsigned long _cpu_flush; /* used by head-armv.S */
9、 const char *arch_name; const char *elf_name; unsigned int elf_hwcap; struct proc_info_item *info; #ifdef MULTI_CPU struct processor *proc; #else void *unused; #endif ; 虽然结构是在这里声明,但是真正的定义却是在proc-arm920.S文件的最后: .section “.proc.info“, #alloc, #execinstr /声明以下代码在.proc.info段中 .type _arm920_proc_info,#ob
10、ject _arm920_proc_info: .long 0x41009200 /cpu_val .long 0xff00fff0 /cpu_mask .long 0x00000c1e mmuflags b _arm920_setup /是的!这是一条跳转指令 .long cpu_arch_name .long cpu_elf_name .long HWCAP_SWP | HWCAP_HALF | HWCAP_26BIT .long cpu_arm920_info .long arm920_processor_functions 27_proc_info_begin/_proc_info_e
11、nd _proc_info_begin是.proc.info段的起始地址,而_proc_info_end是终止地址,他们存放在head-armv.S中: 2: .long _proc_info_end .long _proc_info_begin 在连接脚本vmlinux.lds.in文件中,他们被赋值: _proc_info_begin = .; *(.proc.info) _proc_info_end = .; 28.arch.info段 .arch.info段类似于.proc.info段,不过它是用来存放板子信息的,它的定义是在include/asm/mach/arch.h文件中: st
12、ruct machine_desc /* * Note! The first four elements are used * by assembler code in head-armv.S */ unsigned int nr; /* architecture number */ /板子ID unsigned int phys_ram; /* start of physical ram */ /物理内存起始地址 unsigned int phys_io; /* start of physical io */ /IO空间起始地址 unsigned int io_pg_offst; /* by
13、te offset for io * page tabe entry */ /IO空间起始地址的虚拟地址在启动页表中的偏移 ; 而具体MX1板子的machine_desc定义则通过宏在arch/arm/mach-XXXX/arch.c文件(或者该目录下的其他文件)中,这些宏的定义在include/asm/mach/arch.h文件中实现,通过这些宏定义了一个machine_desc: MACHINE_START(MX1ADS, “Motorola MX1ADS“) MAINTAINER(“WBSG SPS Motorola“) #ifdef CONFIG_ARCH_MX1ADS_SRAM BO
14、OT_MEM(0x12000000, 0x00200000, 0xf0200000) #else BOOT_MEM(0x08000000, 0x00200000, 0xf0200000) /phys_ram=0x08000000,phys_io=0x00200000, /io_pg_offset = (0xf0200000)18) *(.arch.info) _arch_info_end = .; 3代码分析 31KERNEL ENTRY 下面是整个内核Image的入口,进入后必须满足以下条件,即r0为0,r1为板子ID,MMU和D-cache关闭,这其中r1的architecture ID是
15、由bootloader传进来的 /* * Kernel startup entry point. * * The rules are: * r0 - should be 0 * r1 - unique architecture number * MMU - off * I-cache - on or off * D-cache - off * * See linux/arch/arm/tools/mach-types for the complete list of numbers * for r1. */ .section “.text.init“,#alloc,#execinstr /以下
16、代码属于“.text.init”段 .type stext, #function ENTRY(stext) / mov r12, r0 mov r12, #0 / r12=0 #if defined(CONFIG_ARCH_MX1ADS) mov fp, r1 r1 contain pointer to cmdline from bootloader #endif /将r1中包含的内核命令行指针移到fp中 / for MX1ADS, we dont pass this from bootloader, so well set it here #if defined(CONFIG_ARCH_MX
17、1ADS) mov r1, #MACH_TYPE_MX1ADS #endif /此时,r1=0x000000a0 mov r0, #F_BIT | I_BIT | MODE_SVC make sure svc mode msr cpsr_c, r0 and all irqs disabled /将模式设置为svc模式,并禁止IRQ和FIQ bl _lookup_processor_type /检查处理器类型 /如果处理器有效: /r8 = _cpu_mmu_flags, r8 = 0x00000c1e /r9 =处理器ID r10指向处理器结构 teq r10, #0 invalid proc
18、essor? /如果是无效处理器 moveq r0, #p yes, error p /打印“p”和出错信息 beq _error bl _lookup_architecture_type /检查板子类型 /如果板子有效: / r5=物理内存的起始地址, r5 = 0x08000000 / r6=IO空间的起始物理地址 r6=0x00200000 / r7=IO空间虚拟地址在页表中的偏移 r7=0x00003c08 teq r7, #0 invalid architecture? /如果是无效板子 moveq r0, #a yes, error a /打印“a”和出错信息 beq _error
19、 bl _create_page_tables /建立页表 /此时页表建立完毕, /r1=板子ID, /r4=页表地址 (stext-16K) /r9=处理器ID, /r10指向处理器结构 adr lr, _ret return address /将返回地址存放在lr中 add pc, r10, #12 initialise processor (return control reg) /跳转到处理器结构+12的位置,参看.proc.info段可以知 /道,这里是一条跳转指令“b _arm920_setup”,因此再 /跳转到proc-arm920.S中的_arm920_setup函数入口处
20、32_arm920_setup _arm920_setup函数在proc-arm920.S文件中,在页表建立起来之后,此函数进行一些开启MMU之前的初始化操作 .section “.text.init“, #alloc, #execinstr _arm920_setup: mov r0, #0 mcr p15, 0, r0, c7, c7 invalidate I,D caches on v4 /使无效整个I-cache和整个D-cache mcr p15, 0, r0, c7, c10, 4 drain write buffer on v4 /将Write Buffer中的数据写进内存 mc
21、r p15, 0, r0, c8, c7 invalidate I,D TLBs on v4 /使无效整个I-TLB和整个D-TLB mcr p15, 0, r4, c2, c0 load page table pointer /将r4(页表地址)写进c2(页表基址寄存器) mov r0, #0x1f Domains 0, 1 = client mcr p15, 0, r0, c3, c0 load domain access register /设置Domain0、Domain1和Domain2的访问权限 mrc p15, 0, r0, c1, c0 get control register
22、 v4 /将控制寄存器(control register)的值送给r0 /* * Clear out unwanted bits (then put them in if we need them) */ VI ZFRS BLDP WCAM bic r0, r0, #0x0e00 bic r0, r0, #0x0002 bic r0, r0, #0x000c bic r0, r0, #0x1000 .0 000. 000. /* * Turn on what we want */ orr r0, r0, #0x0031 orr r0, r0, #0x2100 1. .1 11 .1 #ifde
23、f CONFIG_CPU_ARM920_D_CACHE_ON orr r0, r0, #0x0004 .1 #endif #ifdef CONFIG_CPU_ARM920_I_CACHE_ON orr r0, r0, #0x1000 .1 #endif /修改r0的某些位,使它的后16位为:XX1I 0001 XX11 0D01 /其中I表示根据CONFIG_CPU_ARM920_I_CACHE_ON设定 /D表示根据CONFIG_CPU_ARM920_D_CACHE_ON设定 /X表示不变,1表示置位,0表示清位 /具体含义如下: /M(bit0)=1,打开MMU /A(bit1)=0,关闭
24、Alignment checking /C(bit2)=D,D-cache打开/关闭 /W(bit3)=0,关闭Write Buffer /P(bit4)=1,Exception handler进入32-bit模式 /D(bit5)=1,关闭32-bit address exception checking /L(bit6)=X,选择Early Abort模式或者Late Abort模式 /B(bit7)=X,Little Endian/Big Endian模式 /S(bit8)=1,System Protection Bit /R(bit9)=0,Rom Protection Bit /F(
25、bit10)=0,Implementation Defined /Z(bit11)=0,关闭Branch prediction /I(bit12)=I,I-cache打开/关闭 /V(bit13)=1,选择High exception vector mov pc, lr /返回,跳转到head-armv.S的_ret处 33_ret 在proc-arm920.S中的_arm920_setup函数进行过一些启动MMU之前的初始化工作后,根据lr寄存器中的值跳转到_ret处执行,这里做了三件事: 1 首先将_switch_data处的值(即_mmap_switched)作为返回值存放在lr寄存器中
26、 2 开启MMU 3 最后返回(即跳转到_mmap_switched处) 请注意!_switch_data中保存的值(也就是_mmap_switched)是一个映像地址(也就是虚拟地址),也就是说,PC的值从此处由物理地址的值跳到内核空间(0xCXXXXXXX) .type _switch_data, %object _switch_data: .long _mmap_switched .long SYMBOL_NAME(compat) .long SYMBOL_NAME(_bss_start) .long SYMBOL_NAME(_end) .long SYMBOL_NAME(process
27、or_id) .long SYMBOL_NAME(_machine_arch_type) .long SYMBOL_NAME(cr_alignment) .long SYMBOL_NAME(init_task_union)+8192 #ifdef CONFIG_ARCH_MX1ADS .long SYMBOL_NAME(cmdline_from_bootloader) /这是为MX1板子定义的从bootloader传来的参数地址 #endif .type _ret, %function _ret: ldr lr, _switch_data /这里保存的是内核空间地址! mcr p15, 0,
28、r0, c1, c0 /将r0中的值送回c1,开启MMU! mov r0, r0 /执行三次NOP操作,清空流水线 mov r0, r0 mov r0, r0 mov pc, lr /跳到_mmap_switched处执行 34_mmap_switched _ret开启MMU之后,通过将_switch_data中保存的_map_switched的值跳转到此处执行,也就是从此处开始PC值转为0xCXXXXXXX /* * This code follows on after the page * table switch and jump above. * * r0 = processor co
29、ntrol register * r1 = machine ID * r9 = processor ID */ .align 5 _mmap_switched: adr r3, _switch_data + 4 /r3=_switch_data+4 ldmia r3, r2, r4, r5, r6, r7, r8, sp r2 = compat sp = stack pointer /r2=compat /r4=bss_start=.bss段的起始地址 /r5=_end=.bss段的终止地址 /r6=processor_id=保存处理器ID的地址 /r7=_machine_arch_type=
30、保存板子ID的地址 /r8=cr_alignment /sp=initial_task+8192,由task_union结构可知,这是init进程的堆栈 str r12, r2 /将r12中的值(0)存进compat #ifdef CONFIG_ARCH_MX1ADS mov r12, fp fp/r11 gets used below (it originally contain pointer to cmdline from bootloader) #endif /在内核入口的一开始,r1中的包含指向内核命令行的指针被送到fp寄存器中,/现在将它送给r12 mov fp, #0 Clear
31、 BSS (and zero fp) 1: cmp r4, r5 /将整个.bss段清零 strcc fp, r4,#4 bcc 1b str r9, r6 Save processor ID /保存处理器ID str r1, r7 Save machine type /保存板子ID #ifdef CONFIG_ARCH_MX1ADS /* now save a pointer to the cmdline_from_bootloader */ adr r3, _switch_data + 32 cmdline_from_bootloader /r3=_switch_data+32=cmdli
32、ne_from_bootloader的地址 ldmia r3, r4 r4 = address of above /r4=r3=cmdline_from_bootloader str r12, r4 / r4=r12, /将指向内核命令行的指针赋给cmdline_from_bootloader #endif #ifdef CONFIG_ALIGNMENT_TRAP orr r0, r0, #2 .A. #endif bic r2, r0, #2 Clear A bit stmia r8, r0, r2 Save control register values b SYMBOL_NAME(sta
33、rt_kernel) /跳到start_kernel处执行 35_lookup_processor_type 36_lookup_architecture_type _lookup_processor_type例程用于检查当前的处理器是否有效,它没有输入,使用了r5,r6,r7三个寄存器,返回r8=_cpu_mmu_flags,r9=处理器ID,r10指向处理器结构 _lookup_architecture_type用于检查当前的板子是否有效,它需要输入r1为板子ID,使用了r2, r3, r4三个寄存器,返回r5=物理内存的起始地址,r6=IO空间的起始地址,r7=IO空间虚拟地址的段号 /
34、* * Read processor ID register (CP#15, CR0), and look up in the linker-built * supported processor list. Note that we cant use the absolute addresses * for the _proc_info lists since we arent running with the MMU on * (and therefore, we are not in the correct address space). We have to * calculate t
35、he offset. * * Returns: * r5, r6, r7 corrupted * r8 = page table flags * r9 = processor ID * r10 = pointer to processor structure */ _lookup_processor_type: /_lookup_processor_type函数入口 adr r5, 2f / r5=_proc_info_end的物理地址,此处伪指令adr会被 /编译成add r5, pc , #xx获得运行时地址 ldmia r5, r7, r9, r10 / r7=_proc_info_en
36、d / r9=_proc_info_start / r10=_proc_info_end的映像地址 sub r5, r5, r10 convert addresses / r5=物理地址减映像地址的差 add r7, r7, r5 to our address space add r10, r9, r5 /r7=_proc_info_end对应的物理地址 /r10=_proc_info_start对应的物理地址 mrc p15, 0, r9, c0, c0 get processor id /r9=处理器ID 1: ldmia r10, r5, r6, r8 value, mask, mmuf
37、lags / r5=cpu_val,r6=cpu_mask,r8=_cpu_mmu_flags / r5=0x41009200, r6=0xff00fff0, r8=0x00000c1e and r6, r6, r9 mask wanted bits teq r5, r6 /比较两个处理器ID是否相同,如果相同返回 moveq pc, lr add r10, r10, #36 sizeof(proc_info_list) cmp r10, r7 /如果不同尝试下一个处理器结构 blt 1b /直到_proc_info_end mov r10, #0 unknown processor mov
38、pc, lr /* * Look in include/asm-arm/procinfo.h and arch/arm/kernel/arch.ch for * more information about the _proc_info and _arch_info structures. */ 2: .long _proc_info_end /.proc.info段的终止映像地址 .long _proc_info_begin /.proc.info段的起始映像地址 .long 2b /_proc_info_end的映像地址 .long _arch_info_begin /.arch.info
39、段的起始映像地址 .long _arch_info_end /.arch.info段的终止映像地址 /* * Lookup machine architecture in the linker-build list of architectures. * Note that we cant use the absolute addresses for the _arch_info * lists since we arent running with the MMU on (and therefore, we are * not in the correct address space). W
40、e have to calculate the offset. * * r1 = machine architecture number * Returns: * r2, r3, r4 corrupted * r5 = physical start address of RAM * r6 = physical address of IO * r7 = byte offset into page tables for IO */ _lookup_architecture_type: /_lookup_architecture_type函数入口 adr r4, 2b / r4=_proc_info
41、_end的物理地址 ldmia r4, r2, r3, r5, r6, r7 throw away r2, r3 / r2=_proc_info_end,丢弃 / r3=_proc_info_start,丢弃 / r5=_proc_info_end的映像地址 / r6=_arch_info_begin / r7=_arch_info_end sub r5, r4, r5 convert addresses / r5=物理地址减映像地址的差 add r4, r6, r5 to our address space add r7, r7, r5 /r7=_proc_info_end对应的物理地址 /
42、r10=_proc_info_start对应的物理地址 /同_lookup_processor_type一样,将映像地址转化为物理地址 1: ldr r5, r4 get machine type / r5=板子ID r5=0x000000a0 teq r5, r1 /比较两个板子ID beq 2f /相同则跳到2 add r4, r4, #SIZEOF_MACHINE_DESC cmp r4, r7 blt 1b mov r7, #0 unknown architecture mov pc, lr 2: ldmib r4, r5, r6, r7 found, get results / r5
43、=物理内存的起始地址, r5=0x08000000 / r6=IO空间的起始地址 r6=0x00200000 / r7=IO空间虚拟地址的段号 r7=0x00003c08 mov pc, lr /返回 37_create_page_tables _create_page_tables用来建立初始化一级页表,此时各个寄存器的情况如下: r5=物理内存的起始地址 r5=0x08000000 r6=IO空间的起始地址 r6=0x00200000 r7=IO空间虚拟地址的段号 r7=0x00003c08 r8 = _cpu_mmu_flags r8=0x00000c1e r9 =处理器ID r10指向
44、处理器结构 r0,r1,r2,r3和r4中没有有效数据 _create_page_tables: pgtbl r4, r5 page table address / r4=stext-16K(即页表地址) / r4=0x08004000 /* * Clear the 16K level 1 swapper page table */ /将页表空间(stext-16K到stext)的16K空间清零 mov r0, r4 / r0 = r4 = stext-16K ,r4=0x08004000 mov r3, #0 / r3 = 0 add r2, r0, #0x4000 / r2 = r0+16
45、K = stext,r2=0x08008000 1: str r3, r0, #4 /循环清零页表空间 str r3, r0, #4 str r3, r0, #4 str r3, r0, #4 teq r0, r2 bne 1b /此时:r0 = r2 = stext ,r4 = stext-16K,r3 = 0 /* * Create identity mapping for first MB of kernel to * cater for the MMU enable. This identity mapping * will be removed by paging_init() */
46、 krnladr r2, r4, r5 start of kernel add r3, r8, r2 flags + kernel base str r3, r4, r2, lsr #18 identity mapping /建立恒等映射(为了开启MMU前后地址空间的一致性),即: /映射虚拟空间:0x08000000-0x08100000 /到物理空间:0x08000000-0x08100000 / r2= stext&0xfff00fff =内核物理空间起始地址所在的段的起始地址 / r8 =页表一级描述符标志位, / r3 = r2 + r8 =内核物理空间所在段的一级描述符 / r2
47、= 0x08000000 / r8= 0x00000c1e / r3= 0x08000c1e /将r2(内核空间起始物理地址)右移20位得到内核起始地址在页/表内的索引,再将它左移两位(即乘以4,页表内每个描述符占四/个字节)后加上页表基址就得到了内核空间起始地址所在段的一级/描述符的地址,r3存入此处 / r4, r2, lsr #18=0x08004200 /* * Now setup the pagetables for our kernel direct * mapped region. We round TEXTADDR down to the * nearest megabyte boundary. */ /此时: / r4=页表物理地址,r4=0x08004000 /r2=内核空间起始物理地址,r2 = 0x08000000 /r3=物理内核空间所在段的一级描述符,r3= 0x08000c1e add r0, r4, #(TEXTADDR & 0xff000000) 18 start of kernel bic r2, r3, #0x00f00000 str r2, r0 PAGE_OFFSET + 0MB /映射虚拟空间:0xC0000000-0xC