| 1.介绍..........................................................................................2 |
| 2.相关定义介绍..........................................................................3 |
| 2.1.TEXTADDR......................................................3 |
| 2.2.stext....................................................................3 |
| 2.3.swapper_pg_dir..................................................4 |
| 2.4.(M)pgtbl.............................................................4 |
| 2.5.(M)krnladr..........................................................5 |
| 2.6..proc.info段.......................................................5 |
| 2.7.__proc_info_begin/__proc_info_end.................6 |
| 2.8..arch.info段.......................................................7 |
| 2.9.__arch_info_begin/__arch_info_end.................8 |
| 3.代码分析..................................................................................9 |
| 3.1.KERNEL ENTRY..............................................9 |
| 3.2.__arm920_setup...............................................11 |
| 3.3.__ret.................................................................13 |
| 3.4.__mmap_switched...........................................14 |
| 3.5.__lookup_processor_type................................16 |
| 3.6.__lookup_architecture_type.............................16 |
| 3.7.__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等等 |
| 由于此分析基于MX1的内核启动过程,因此除了通用代码,只有定义在CONFIG_ARCH_MX1ADS下的代码和proc-arm920.S(arm920是MX1的CPU)的代码被分析 |
| 在下面程序流程的说明中,MX1板子启动过程中的寄存器值将会用绿色字体表示出来,而对于专门针对MX1的代码则会用下划线字体表示 |
2.相关定义介绍 |
| 2.1.TEXTADDR |
| TEXTADDR是内核Image的映像地址,也是内核Image所处的虚拟地址,它在系统内核空间起始地址——通常是0xC0000000(这相对应于物理内存开始的地方)+32K的位置,也就是0xC0008000处TEXTADDR的赋值在arch/arm/Makefile文件中:(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 & 0xffff) != 0x8000 //TEXTADDR必须为0xXXXX8000 |
| #error TEXTADDR must start at 0xXXXX8000 |
| #endif |
| 2.2.stext |
| stext是TEXTADDR相对应的物理地址,由于内核空间的起始地址(0xC0000000)相对应的物理地址就是最低物理内存的地址,因此stext就是最低物理内存+32K处(虽然这并不是有内核指定,而是由bootloader指定),它的赋值在连接脚本vmlinux-armv.lds.in中实现:(0x08008000) |
| ENTRY(stext) |
| SECTIONS |
| { |
| . = TEXTADDR; //此处的映像地址为TEXTADDR |
| .init : { /* Init code and data */ |
| _stext = .; //stext为此处的物理地址 |
| 2.3.swapper_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定义在include/linux/sched.h文件中: #define INIT_MM(name) \ |
| { \ |
| … |
| pgd: swapper_pg_dir, \ |
| … |
| } |
| 2.4.(M)pgtbl |
| pgtbl是一个用于获得启动页表物理地址的宏,它将stext减去16K给reg,它也在head-armv.S中定义: |
| .macro pgtbl, reg, rambase |
| adr \reg, stext |
| sub \reg, \reg, #0x4000 //reg=stext-0x4000 |
| .endm |
| 2.5.(M)krnladr |
| 这个宏用于由pgtable获得内核空间的起始物理地址的所在的段(MB),将pgtable,也就是页表地址(和内核空间的起始物理地址在同一个段内)和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 |
| .endm |
| 2.6..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 */ |
| 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,#object |
| __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 |
| 2.7.__proc_info_begin/__proc_info_end |
| __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 = .; |
| 2.8..arch.info段 |
| .arch.info段类似于.proc.info段,不过它是用来存放板子信息的,它的定义是在include/asm/mach/arch.h文件中: |
| struct 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; /* byte 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 |
| BOOT_MEM(0x12000000, 0x00200000, 0xf0200000) |
| #else |
| BOOT_MEM(0x08000000, 0x00200000, 0xf0200000) |
| //phys_ram=0x08000000,phys_io=0x00200000, |
| //io_pg_offset = ((0xf0200000)>>18)&0xfffc=0x00003c08 |
| //右移20位获得IO空间虚拟地址在页表内偏移再乘以四(4个字节) |
| #endif |
| FIXUP(mx1ads_fixup) |
| MAPIO(mx1ads_map_io) |
| INITIRQ(mx1ads_init_irq) |
| MACHINE_END |
| 2.9.__arch_info_begin/__arch_info_end |
| __arch_info_begin是.arch.info段的起始地址,而__arch_info_end是终止地址,他们存放在head-armv.S中: |
| .long __arch_info_begin |
| .long __arch_info_end |
| 在连接脚本vmlinux.lds.in文件中,他们被赋值: |
| __arch_info_begin = .; |
| *(.arch.info) |
| __arch_info_end = .; |
3.代码分析 |
| 3.1.KERNEL ENTRY |
| 下面是整个内核Image的入口,进入后必须满足以下条件,即r0为0,r1为板子ID,MMU和D-cache关闭,这其中r1的architecture ID是由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 //以下代码属于“.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 don't pass this from bootloader, so we'll set it here |
| #if defined(CONFIG_ARCH_MX1ADS) |
| 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 processor? //如果是无效处理器 |
| 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 |
| 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函数入口处 |
| 3.2.__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中的数据写进内存 |
| mcr 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 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 |
| #ifdef 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,关闭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(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处 |
| 3.3.__ret |
| 在proc-arm920.S中的__arm920_setup函数进行过一些启动MMU之前的初始化工作后,根据lr寄存器中的值跳转到__ret处执行,这里做了三件事: |
| 1. 首先将__switch_data处的值(即__mmap_switched)作为返回值存放在lr寄存器中 |
| 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(processor_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, r0, c1, c0 //将r0中的值送回c1,开启MMU!! |
| mov r0, r0 //执行三次NOP操作,清空流水线 |
| mov r0, r0 |
| mov r0, r0 |
| mov pc, lr //跳到__mmap_switched处执行 |
| 3.4.__mmap_switched |
| __ret开启MMU之后,通过将__switch_data中保存的__map_switched的值跳转到此处执行,也就是从此处开始PC值转为0xCXXXXXXX |
| /* |
| * This code follows on after the page |
| * table switch and jump above. |
| * |
| * r0 = processor control 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=保存板子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 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=cmdline_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(start_kernel) //跳到start_kernel处执行 |
| 3.5.__lookup_processor_type |
| 3.6.__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空间虚拟地址的段号 |
| /* |
| * Read processor ID register (CP#15, CR0), and look up in the linker-built |
| * supported processor list. Note that we can't use the absolute addresses |
| * for the __proc_info lists since we aren't running with the MMU on |
| * (and therefore, we are not in the correct address space). We have to |
| * calculate the offset. |
| * |
| * Returns: |
| * r5, r6, r7 corrupted |
| * r8 = page table flags |
| * r9 = processor ID |
| * r10 |
本文详细解析了ARM Linux内核启动的过程,重点介绍了head-armv.S文件中的关键步骤,如处理器类型检测、页表创建及MMU初始化等。
2311

被折叠的 条评论
为什么被折叠?



