参考资料:

《ARM体系结构与编程》

《嵌入式Linux应用开发完全手册》

Linux_Memory_Address_Mapping

http://www.chinaunix.net/old_jh/4/1021226.html

更多文档参见:http://pan.baidu.com/s/1mg3DbHQ

本文针对arm linux, 从kernel的第一条指令开始分析,一直分析到进入start_kernel()函数.
我们当前以linux-2.6.19内核版本作为范例来分析,本文中所有的代码,前面都会加上行号以便于和源码进行对照,
例:
在文件init/main.c中:
00478: asmlinkage void __init start_kernel(void)
前面的"00478:" 表示478行,冒号后面的内容就是源码了.
在分析代码的过程中,我们使用缩进来表示各个代码的调用层次.
由于启动部分有一些代码是平台特定的,虽然大部分的平台所实现的功能都比较类似,但是为了更好的对code进行说明,对于平台相关的代码,我们选择at91(ARM926EJS)平台进行分析.
另外,本文是以uncompressed kernel开始讲解的.对于内核解压缩部分的code,在 arch/arm/boot/compressed中,本文不做讨论.

一. 启动条件

通常从系统上电到执行到linux kenel这部分的任务是由boot loader来完成.
        关于boot loader的内容,本文就不做过多介绍.
        这里只讨论进入到linux kernel的时候的一些限制条件,这一般是boot loader在最后跳转到kernel之前要完成的:
        1. CPU必须处于SVC(supervisor)模式,并且IRQ和FIQ中断都是禁止的;
        2. MMU(内存管理单元)必须是关闭的, 此时虚拟地址对物理地址;
        3. 数据cache(Data cache)必须是关闭的
        4. 指令cache(Instruction cache)可以是打开的,也可以是关闭的,这个没有强制要求;
        5. CPU 通用寄存器0 (r0)必须是 0;
        6. CPU 通用寄存器1 (r1)必须是 ARM Linux machine type (关于machine type, 我们后面会有讲解)
        7. CPU 通用寄存器2 (r2) 必须是 kernel parameter list 的物理地址(parameter list 是由boot loader传递给kernel,用来描述设备信息属性的列表,详细内容可参考"Booting ARM Linux"文档).

二. starting kernel

首先,我们先对几个重要的宏进行说明(我们针对有MMU的情况):

宏                                               位置                                       默认值             说明
KERNEL_RAM_ADDR      arch/arm/kernel/head.S +26              0xc0008000      kernel在RAM中的的虚拟地址
PAGE_OFFSET                include/asm-arm/memeory.h +50     0xc0000000      内核空间的起始虚拟地址
TEXT_OFFSET                 arch/arm/Makefile +137                    0x00008000      内核相对于存储空间的偏移
TEXTADDR                     arch/arm/kernel/head.S +49              0xc0008000      kernel的起始虚拟地址
PHYS_OFFSET                include/asm-arm/arch-xxx/memory.h   平台相关          RAM的起始物理地址
        内核的入口是stext,这是在arch/arm/kernel/vmlinux.lds.S中定义的:
        00011: ENTRY(stext)
        对于vmlinux.lds.S,这是ld script文件,此文件的格式和汇编及C程序都不同,本文不对ld script作过多的介绍,只对内核中用到的内容进行讲解,关于ld的详细内容可以参考ld.info
        这里的ENTRY(stext) 表示程序的入口是在符号stext.
        而符号stext是在arch/arm/kernel/head.S中定义的:
        下面我们将arm linux boot的主要代码列出来进行一个概括的介绍,然后,我们会逐个的进行详细的讲解.
        在arch/arm/kernel/head.S中 72 - 94 行,是arm linux boot的主代码:
00072: ENTRY(stext)                                                        
00073:         msr        cpsr_c, #PSR_F_BIT | PSR_I_BIT | SVC_MODE @ ensure svc mode
00074:                                                 @ and irqs disabled        
00075:         mrc        p15, 0, r9, c0, c0                @ get processor id         
00076:         bl        __lookup_processor_type                @ r5=procinfo r9=cpuid     
00077:         movs        r10, r5                                @ invalid processor (r5=0)?
00078:         beq        __error_p                        @ yes, error 'p'           
00079:         bl        __lookup_machine_type                @ r5=machinfo              
00080:         movs        r8, r5                                @ invalid machine (r5=0)?  
00081:         beq        __error_a                        @ yes, error 'a'           
00082:         bl        __create_page_tables                                       
00083:                                                                     
00084:         /*                                                                 
00085:          * The following calls CPU specific code in a position independent
00086:          * manner.  See arch/arm/mm/proc-*.S for details.  r10 = base of   
00087:          * xxx_proc_info structure selected by __lookup_machine_type      
00088:          * above.  On return, the CPU will be ready for the MMU to be      
00089:          * turned on, and r0 will hold the CPU control register value.     
00090:          */                                                               
00091:         ldr        r13, __switch_data                @ address to jump to after
00092:                                                 @ mmu has been enabled     
00093:         adr        lr, __enable_mmu                @ return (PIC) address     
00094:         add        pc, r10, #PROCINFO_INITFUNC                                
其中,73行是确保kernel运行在SVC模式下,并且IRQ和FIRQ中断已经关闭,这样做是很谨慎的.
arm linux boot的主线可以概括为以下几个步骤:
        1. 确定 processor type                        (75 - 78行)
        2. 确定 machine type                        (79 - 81行)
        3. 创建页表                                (82行)     
        4. 调用平台特定的__cpu_flush函数        (在struct proc_info_list中)        (94 行)                           
        5. 开启mmu                                (93行)
        6. 切换数据                                 (91行)
        最终跳转到start_kernel                        (在__switch_data的结束的时候,调用了 b        start_kernel)
下面,我们按照这个主线,逐步的分析Code.

1. 确定 processor type

arch/arm/kernel/head.S中:
00075:         mrc        p15, 0, r9, c0, c0                @ get processor id         
00076:         bl        __lookup_processor_type                @ r5=procinfo r9=cpuid     
00077:         movs        r10, r5                                @ invalid processor (r5=0)?
00078:         beq        __error_p                        @ yes, error 'p'           
75行: 通过cp15协处理器的c0寄存器来获得processor id的指令. 关于cp15的详细内容可参考相关的arm手册
76行: 跳转到__lookup_processor_type.在__lookup_processor_type中,会把processor type 存储在r5中
77,78行: 判断r5中的processor type是否是0,如果是0,说明是无效的processor type,跳转到__error_p(出错)
__lookup_processor_type 函数主要是根据从cpu中获得的processor id和系统中的proc_info进行匹配,将匹配到的proc_info_list的基地址存到r5中, 0表示没有找到对应的processor type.
下面我们分析__lookup_processor_type函数
        arch/arm/kernel/head-common.S中:
00145:         .type        __lookup_processor_type, %function
00146: __lookup_processor_type:
00147:         adr        r3, 3f
00148:         ldmda        r3, {r5 - r7}
00149:         sub        r3, r3, r7                        @ get offset between virt&phys
00150:         add        r5, r5, r3                        @ convert virt addresses to
00151:         add        r6, r6, r3                        @ physical address space
00152: 1:        ldmia        r5, {r3, r4}                        @ value, mask
00153:         and        r4, r4, r9                        @ mask wanted bits
00154:         teq        r3, r4
00155:         beq        2f
00156:         add        r5, r5, #PROC_INFO_SZ                @ sizeof(proc_info_list)
00157:         cmp        r5, r6
00158:         blo        1b
00159:         mov        r5, #0                                @ unknown processor
00160: 2:        mov        pc, lr
00161:
00162: /*
00163:  * This provides a C-API version of the above function.
00164:  */
00165: ENTRY(lookup_processor_type)
00166:         stmfd        sp!, {r4 - r7, r9, lr}
00167:         mov        r9, r0
00168:         bl        __lookup_processor_type
00169:         mov        r0, r5
00170:         ldmfd        sp!, {r4 - r7, r9, pc}
00171:
00172: /*
00173:  * Look in include/asm-arm/procinfo.h and arch/arm/kernel/arch.[ch] for
00174:  * more information about the __proc_info and __arch_info structures.
00175:  */
00176:         .long        __proc_info_begin
00177:         .long        __proc_info_end
00178: 3:        .long        .
00179:         .long        __arch_info_begin
00180:         .long        __arch_info_end
145, 146行是函数定义
147行: 取地址指令,这里的3f是向前symbol名称是3的位置,即第178行,将该地址存入r3.
        这里需要注意的是,adr指令取址,获得的是基于pc的一个地址,要格外注意,这个地址是3f处的"运行时地址", 由于此时MMU还没有打开,也可以理解成物理地址(实地址).(详细内容可参考arm指令手册)
148行: 因为r3中的地址是178行的位置的地址,因而执行完后: (ldmda表示栈指针递减,即r3递减,内存的地址编号较大的对应寄存器编号较大的)
        r5存的是176行符号 __proc_info_begin的地址;
        r6存的是177行符号 __proc_info_end的地址;
        r7存的是3f处的地址.
        这里需要注意链接地址和运行时地址的区别. r3存储的是运行时地址(物理地址),而r7中存储的是链接地址(虚拟地址).
                __proc_info_begin和__proc_info_end是在arch/arm/kernel/vmlinux.lds.S中:
        00031:                __proc_info_begin = .;
        00032:                        *(.proc.info.init)
        00033:                __proc_info_end = .;
        这里是声明了两个变量:__proc_info_begin 和 __proc_info_end,其中等号后面的"."是location counter(详细内容请参考ld.info)
        这三行的意思是: __proc_info_begin 的位置上,放置所有文件中的 ".proc.info.init" 段的内容,然后紧接着是 __proc_info_end 的位置.
        kernel 使用struct proc_info_list来描述processor type.
                在 include/asm-arm/procinfo.h 中:
        00029: struct proc_info_list {
        00030:         unsigned int                cpu_val;
        00031:         unsigned int                cpu_mask;
        00032:         unsigned long                __cpu_mm_mmu_flags;        /* used by head.S */
        00033:         unsigned long                __cpu_io_mmu_flags;        /* used by head.S */
        00034:         unsigned long                __cpu_flush;                /* used by head.S */
        00035:         const char                *arch_name;
        00036:         const char                *elf_name;
        00037:         unsigned int                elf_hwcap;
        00038:         const char                *cpu_name;
        00039:         struct processor        *proc;
        00040:         struct cpu_tlb_fns        *tlb;
        00041:         struct cpu_user_fns        *user;
        00042:         struct cpu_cache_fns        *cache;
        00043: };
        我们当前以at91为例,其processor是926的.
                在arch/arm/mm/proc-arm926.S 中:
        00464:         .section ".proc.info.init", #alloc, #execinstr
        00465:
        00466:         .type        __arm926_proc_info,#object
        00467: __arm926_proc_info:
        00468:         .long        0x41069260                        @ ARM926EJ-S (v5TEJ)
        00469:         .long        0xff0ffff0
        00470:         .long   PMD_TYPE_SECT | \
        00471:                 PMD_SECT_BUFFERABLE | \
        00472:                 PMD_SECT_CACHEABLE | \
        00473:                 PMD_BIT4 | \
        00474:                 PMD_SECT_AP_WRITE | \
        00475:                 PMD_SECT_AP_READ
        00476:         .long   PMD_TYPE_SECT | \
        00477:                 PMD_BIT4 | \
        00478:                 PMD_SECT_AP_WRITE | \
        00479:                 PMD_SECT_AP_READ
        00480:         b        __arm926_setup
        00481:         .long        cpu_arch_name
        00482:         .long        cpu_elf_name
        00483:         .long        HWCAP_SWP|HWCAP_HALF|HWCAP_THUMB|HWCAP_FAST_MULT|HWCAP_VFP|HWCAP_EDSP|HWCAP_JAVA
        00484:         .long        cpu_arm926_name
        00485:         .long        arm926_processor_functions
        00486:         .long        v4wbi_tlb_fns
        00487:         .long        v4wb_user_fns
        00488:         .long        arm926_cache_fns
        00489:         .size        __arm926_proc_info, . - __arm926_proc_info
        从464行,我们可以看到 __arm926_proc_info 被放到了".proc.info.init"段中.
        对照struct proc_info_list,我们可以看到 __cpu_flush的定义是在480行,即__arm926_setup.(我们将在"4. 调用平台特定的__cpu_flush函数"一节中详细分析这部分的内容.)
从以上的内容我们可以看出: r5中的__proc_info_begin是proc_info_list的起始地址, r6中的__proc_info_end是proc_info_list的结束地址.
149行: 从上面的分析我们可以知道r3中存储的是3f处的物理地址,而r7存储的是3f处的虚拟地址,这一行是计算当前程序运行的物理地址和虚拟地址的差值,将其保存到r3中.
150行: 将r5存储的虚拟地址(__proc_info_begin)转换成物理地址
151行: 将r6存储的虚拟地址(__proc_info_end)转换成物理地址
152行: 对照struct proc_info_list,可以得知,这句是将当前proc_info的cpu_val和cpu_mask分别存r3, r4中
153行: r9中存储了processor id(arch/arm/kernel/head.S中的75行),与r4的cpu_mask进行逻辑与操作,得到我们需要的值
154行: 将153行中得到的值与r3中的cpu_val进行比较
155行: 如果相等,说明我们找到了对应的processor type,跳到160行,返回
156行: (如果不相等) , 将r5指向下一个proc_info,
157行: 和r6比较,检查是否到了__proc_info_end.
158行: 如果没有到__proc_info_end,表明还有proc_info配置,返回152行继续查找
159行: 执行到这里,说明所有的proc_info都匹配过了,但是没有找到匹配的,将r5设置成0(unknown processor)
160行: 返回

2. 确定 machine type

arch/arm/kernel/head.S中:
00079:         bl        __lookup_machine_type                @ r5=machinfo              
00080:         movs        r8, r5                                @ invalid machine (r5=0)?  
00081:         beq        __error_a                        @ yes, error 'a'  
79行: 跳转到__lookup_machine_type函数,在__lookup_machine_type中,会把struct machine_desc的基地址(machine type)存储在r5中
80,81行: 将r5中的 machine_desc的基地址存储到r8中,并判断r5是否是0,如果是0,说明是无效的machine type,跳转到__error_a(出错)
__lookup_machine_type 函数
下面我们分析__lookup_machine_type 函数:
        arch/arm/kernel/head-common.S中:
00176:         .long        __proc_info_begin
00177:         .long        __proc_info_end
00178: 3:        .long        .
00179:         .long        __arch_info_begin
00180:         .long        __arch_info_end
00181:
00182: /*
00183:  * Lookup machine architecture in the linker-build list of architectures.
00184:  * Note that we can't use the absolute addresses for the __arch_info
00185:  * lists since we aren't running with the MMU on (and therefore, we are
00186:  * not in the correct address space).  We have to calculate the offset.
00187:  *
00188:  *  r1 = machine architecture number
00189:  * Returns:
00190:  *  r3, r4, r6 corrupted
00191:  *  r5 = mach_info pointer in physical address space
00192:  */       
00193:         .type        __lookup_machine_type, %function
00194: __lookup_machine_type:
00195:         adr        r3, 3b
00196:         ldmia        r3, {r4, r5, r6}
00197:         sub        r3, r3, r4                        @ get offset between virt&phys
00198:         add        r5, r5, r3                        @ convert virt addresses to
00199:         add        r6, r6, r3                        @ physical address space
00200: 1:        ldr        r3, [r5, #MACHINFO_TYPE]        @ get machine type
00201:         teq        r3, r1                                @ matches loader number?
00202:         beq        2f                                @ found
00203:         add        r5, r5, #SIZEOF_MACHINE_DESC        @ next machine_desc
00204:         cmp        r5, r6
00205:         blo        1b
00206:         mov        r5, #0                                @ unknown machine
00207: 2:        mov        pc, lr
193, 194行: 函数声明
195行: 取地址指令,这里的3b是向后symbol名称是3的位置,即第178行,将该地址存入r3.
        和上面我们对__lookup_processor_type 函数的分析相同,r3中存放的是3b处物理地址.
196行: r3是3b处的地址,因而执行完后:(ldmia 表示栈是递增的,即r3递增,低内存地址对应小号寄存器)
        r4存的是 3b处的地址
        r5存的是__arch_info_begin 的地址
        r6存的是__arch_info_end 的地址
        __arch_info_begin 和 __arch_info_end是在 arch/arm/kernel/vmlinux.lds.S中:
        00034:                __arch_info_begin = .;
        00035:                        *(.arch.info.init)
        00036:                __arch_info_end = .;
        这里是声明了两个变量:__arch_info_begin 和 __arch_info_end,其中等号后面的"."是location counter(详细内容请参考ld.info)
        这三行的意思是: __arch_info_begin 的位置上,放置所有文件中的 ".arch.info.init" 段的内容,然后紧接着是 __arch_info_end 的位置.
        kernel 使用struct machine_desc 来描述 machine type.
        在 include/asm-arm/mach/arch.h 中:
        00017: struct machine_desc {
        00018:         /*
        00019:          * Note! The first four elements are used
        00020:          * by assembler code in head-armv.S
        00021:          */
        00022:         unsigned int                nr;                /* architecture number        */
        00023:         unsigned int                phys_io;        /* start of physical io        */
        00024:         unsigned int                io_pg_offst;        /* byte offset for io
        00025:                                                  * page tabe entry        */
        00026:
        00027:         const char                *name;                /* architecture name        */
        00028:         unsigned long                boot_params;        /* tagged list                */
        00029:
        00030:         unsigned int                video_start;        /* start of video RAM        */
        00031:         unsigned int                video_end;        /* end of video RAM        */
        00032:
        00033:         unsigned int                reserve_lp0 :1;        /* never has lp0        */
        00034:         unsigned int                reserve_lp1 :1;        /* never has lp1        */
        00035:         unsigned int                reserve_lp2 :1;        /* never has lp2        */
        00036:         unsigned int                soft_reboot :1;        /* soft reboot                */
        00037:         void                        (*fixup)(struct machine_desc *,
        00038:                                          struct tag *, char **,
        00039:                                          struct meminfo *);
        00040:         void                        (*map_io)(void);/* IO mapping function        */
        00041:         void                        (*init_irq)(void);
        00042:         struct sys_timer        *timer;                /* system tick timer        */
        00043:         void                        (*init_machine)(void);
        00044: };
        00045:
        00046: /*
        00047:  * Set of macros to define architecture features.  This is built into
        00048:  * a table by the linker.
        00049:  */
        00050: #define MACHINE_START(_type,_name)                        \
        00051: static const struct machine_desc __mach_desc_##_type        \
        00052:  __attribute_used__                                        \
        00053:  __attribute__((__section__(".arch.info.init")) = {        \
        00054:         .nr                = MACH_TYPE_##_type,                \
        00055:         .name                = _name,
        00056:
        00057: #define MACHINE_END                                \
        00058: };        
        内核中,一般使用宏MACHINE_START来定义machine type.
        对于at91, 在 arch/arm/mach-at91rm9200/board-ek.c 中:
        00137: MACHINE_START(AT91RM9200EK, "Atmel AT91RM9200-EK"
        00138:         /* Maintainer: SAN People/Atmel */
        00139:         .phys_io        = AT91_BASE_SYS,
        00140:         .io_pg_offst        = (AT91_VA_BASE_SYS >> 1 & 0xfffc,
        00141:         .boot_params        = AT91_SDRAM_BASE + 0x100,
        00142:         .timer                = &at91rm9200_timer,
        00143:         .map_io                = ek_map_io,
        00144:         .init_irq        = ek_init_irq,
        00145:         .init_machine        = ek_board_init,
        00146: MACHINE_END
197行: r3中存储的是3b处的物理地址,而r4中存储的是3b处的虚拟地址,这里计算处物理地址和虚拟地址的差值,保存到r3中
198行: 将r5存储的虚拟地址(__arch_info_begin)转换成物理地址            
199行: 将r6存储的虚拟地址(__arch_info_end)转换成物理地址            
200行: MACHINFO_TYPE 在 arch/arm/kernel/asm-offset.c 101行定义, 这里是取 struct machine_desc中的nr(architecture number) 到r3中
201行: 将r3中取到的machine type 和 r1中的 machine type(见前面的"启动条件"进行比较
202行: 如果相同,说明找到了对应的machine type,跳转到207行的2f处,此时r5中存储了对应的struct machine_desc的基地址
203行: (不相同), 取下一个machine_desc的地址
204行: 和r6进行比较,检查是否到了__arch_info_end.
205行: 如果不相同,说明还有machine_desc,返回200行继续查找.
206行: 执行到这里,说明所有的machind_desc都查找完了,并且没有找到匹配的, 将r5设置成0(unknown machine).
207行: 返回

3. 创建页表

通过前面的两步,我们已经确定了processor type 和 machine type.
此时,一些特定寄存器的值如下所示:
r8 = machine info       (struct machine_desc的基地址)
r9 = cpu id             (通过cp15协处理器获得的cpu id)
r10 = procinfo          (struct proc_info_list的基地址)
创建页表是通过函数 __create_page_tables 来实现的.
这里,我们使用的是arm的L1主页表,L1主页表也称为段页表(section page table)
L1 主页表将4 GB 的地址空间分成若干个1 MB的段(section),因此L1页表包含4096个页表项(section entry). 每个页表项是32 bits(4 bytes)
因而L1主页表占用 4096 *4 = 16k的内存空间.
        对于ARM926,其L1 section entry的格式为可参考arm926EJS TRM):

(一级描述符的格式  可以参考《ARM体系结构与编程》P180)

下面我们来分析 __create_page_tables 函数:
         在 arch/arm/kernel/head.S 中:
00206:         .type        __create_page_tables, %function
00207: __create_page_tables:
00208:         pgtbl        r4                                @ page table address
00209:
00210:         /*
00211:          * Clear the 16K level 1 swapper page table
00212:          */
00213:         mov        r0, r4
00214:         mov        r3, #0
00215:         add        r6, r0, #0x4000
00216: 1:        str        r3, [r0], #4
00217:         str        r3, [r0], #4
00218:         str        r3, [r0], #4
00219:         str        r3, [r0], #4
00220:         teq        r0, r6
00221:         bne        1b
00222:
00223:         ldr        r7, [r10, #PROCINFO_MM_MMUFLAGS] @ mm_mmuflags
00224:
00225:         /*
00226:          * Create identity mapping for first MB of kernel to
00227:          * cater for the MMU enable.  This identity mapping
00228:          * will be removed by paging_init().  We use our current program
00229:          * counter to determine corresponding section base address.
00230:          */
00231:         mov        r6, pc, lsr #20                        @ start of kernel section
00232:         orr        r3, r7, r6, lsl #20                @ flags + kernel base
00233:         str        r3, [r4, r6, lsl #2]                @ identity mapping
00234:
00235:         /*
00236:          * Now setup the pagetables for our kernel direct
00237:          * mapped region.
00238:          */
00239:         add        r0, r4,  #(TEXTADDR & 0xff000000) >> 18        @ start of kernel
00240:         str        r3, [r0, #(TEXTADDR & 0x00f00000) >> 18]!
00241:
00242:         ldr        r6, =(_end - PAGE_OFFSET - 1)        @ r6 = number of sections
00243:         mov        r6, r6, lsr #20                        @ needed for kernel minus 1
00244:
00245: 1:        add        r3, r3, #1 << 20
00246:         str        r3, [r0, #4]!
00247:         subs        r6, r6, #1
00248:         bgt        1b
00249:
00250:         /*
00251:          * Then map first 1MB of ram in case it contains our boot params.
00252:          */
00253:         add        r0, r4, #PAGE_OFFSET >> 18
00254:         orr        r6, r7, #PHYS_OFFSET
00255:         str        r6, [r0]
        ...
00314:        mov        pc, lr
00315:        .ltorg         
206, 207行: 函数声明
208行: 通过宏 pgtbl 将r4设置成页表的基地址(物理地址)
        宏pgtbl 在 arch/arm/kernel/head.S 中:
        00042:        .macro        pgtbl, rd
        00043:        ldr        \rd, =(__virt_to_phys(KERNEL_RAM_ADDR - 0x4000))
        00044:        .endm
        可以看到,页表是位于 KERNEL_RAM_ADDR 下面 16k 的位置
        宏 __virt_to_phys 是在incude/asm-arm/memory.h 中:
        00125: #ifndef __virt_to_phys
        00126: #define __virt_to_phys(x)        ((x) - PAGE_OFFSET + PHYS_OFFSET)
        00127: #define __phys_to_virt(x)        ((x) - PHYS_OFFSET + PAGE_OFFSET)
        00128: #endif        
下面从213行 - 221行, 是将这16k 的页表清0.
213行: r0 = r4, 将页表基地址存在r0中
214行: 将 r3 置成0
215行: r6  = 页表基地址 + 16k, 可以看到这是页表的尾地址
216 - 221 行: 循环,从 r0 到 r6 将这16k页表用0填充.
223行: 获得proc_info_list的__cpu_mm_mmu_flags的值,并存储到 r7中. (宏PROCINFO_MM_MMUFLAGS是在arch/arm/kernel/asm-offset.c中定义,值为8)(可以参考《嵌入式Linux应用完全开发手册》P118)(r7的值就是设置这个段描述符的权限、域字段,)

在arch/arm/mm/proc-arm926.S 中:

        00464:         .section ".proc.info.init", #alloc, #execinstr

        00465: 

        00466:         .type        __arm926_proc_info,#object

        00467: __arm926_proc_info:

        00468:         .long        0x41069260                        @ ARM926EJ-S (v5TEJ)

        00469:         .long        0xff0ffff0

        00470:         .long   PMD_TYPE_SECT | \

        00471:                 PMD_SECT_BUFFERABLE | \

        00472:                 PMD_SECT_CACHEABLE | \

        00473:                 PMD_BIT4 | \

        00474:                 PMD_SECT_AP_WRITE | \

        00475:                 PMD_SECT_AP_READ

        00476:         .long   PMD_TYPE_SECT | \

        00477:                 PMD_BIT4 | \

        00478:                 PMD_SECT_AP_WRITE | \

        00479:                 PMD_SECT_AP_READ

        00480:         b        __arm926_setup

        00481:         .long        cpu_arch_name

        00482:         .long        cpu_elf_name

        00483:         .long        HWCAP_SWP|HWCAP_HALF|HWCAP_THUMB|HWCAP_FAST_MULT|HWCAP_VFP|HWCAP_EDSP|HWCAP_JAVA

        00484:         .long        cpu_arm926_name

        00485:         .long        arm926_processor_functions

        00486:         .long        v4wbi_tlb_fns

        00487:         .long        v4wb_user_fns

        00488:         .long        arm926_cache_fns

        00489:         .size        __arm926_proc_info, . - __arm926_proc_info


231行: 通过pc值的高12位(右移20位),得到kernel的section,并存储到r6中.因为当前是通过运行时地址得到的kernel的section,因而是物理地址.

232行: r3 = r7 | (r6 << 20); flags + kernel base,得到页表中需要设置的值.

233行: 设置页表: mem[r4 + r6 * 4] = r3

这里,因为页表的每一项是32 bits(4 bytes),所以要乘以4(<<2).

上面这三行,设置了kernel的第一个section(物理地址所在的page entry)的页表项

239, 240行: TEXTADDR是内核的起始虚拟地址(0xc0008000), 这两行是设置kernel起始虚拟地址的页表项(注意,这里设置的页表项和上面的231 - 233行设置的页表项是不同的 )

执行完后,r0指向kernel的第2个section的虚拟地址所在的页表项.

/* TODO: 这两行的code很奇怪,为什么要先取TEXTADDR的高8位(Bit[31:24])0xff000000,然后再取后面的8位 (Bit[23:20])0x00f00000*/           
242行: 这一行计算kernel镜像的大小(bytes).

_end 是在vmlinux.lds.S中162行定义的,标记kernel的结束位置(虚拟地址):

00158                .bss : {

00159                __bss_start = .;        /* BSS                                */

00160                *(.bss)

00161                *(COMMON)

00162                _end = .;

00163        }

kernel的size =  _end - PAGE_OFFSET -1, 这里 减1的原因是因为 _end 是 location counter,它的地址是kernel镜像后面的一个byte的地址.

243行: 地址右移20位,计算出kernel有多少sections(也就是有多少兆,因为段描述符每个可以映射1MiB的虚拟地址),并将结果存到r6中

245 - 248行: 这几行用来填充kernel所有section虚拟地址对应的页表项.

253行: 将r0设置为RAM第一兆虚拟地址的页表项地址(page entry)

254行: r7中存储的是mmu flags, 逻辑或上RAM的起始物理地址,得到RAM第一个MB页表项的值.

255行: 设置RAM的第一个MB虚拟地址的页表.

上面这三行是用来设置RAM中第一兆虚拟地址的页表. 之所以要设置这个页表项的原因是RAM的第一兆内存中可能存储着boot params.

这样,kernel所需要的基本的页表我们都设置完了, 如下图所示:

下面是linux-2.6.30.4中的arch/arm/kernel/head.S,代码有一些不同,但是效果一样:

   1: /*

   2:  *  linux/arch/arm/kernel/head.S

   3:  *

   4:  *  Copyright (C) 1994-2002 Russell King

   5:  *  Copyright (c) 2003 ARM Limited

   6:  *  All Rights Reserved

   7:  *

   8:  * This program is free software; you can redistribute it and/or modify

   9:  * it under the terms of the GNU General Public License version 2 as

  10:  * published by the Free Software Foundation.

  11:  *

  12:  *  Kernel startup code for all 32-bit CPUs

  13:  */

  14: #include <linux/linkage.h>

  15: #include <linux/init.h>

  16:  

  17: #include <asm/assembler.h>

  18: #include <asm/domain.h>

  19: #include <asm/ptrace.h>

  20: #include <asm/asm-offsets.h>

  21: #include <asm/memory.h>

  22: #include <asm/thread_info.h>

  23: #include <asm/system.h>

  24:  

  25: #if (PHYS_OFFSET & 0x001fffff)

  26: #error "PHYS_OFFSET must be at an even 2MiB boundary!"

  27: #endif

  28:  

  29: #define KERNEL_RAM_VADDR    (PAGE_OFFSET + TEXT_OFFSET)

  30: #define KERNEL_RAM_PADDR    (PHYS_OFFSET + TEXT_OFFSET)

  31:  

  32:  

  33: /*

  34:  * swapper_pg_dir is the virtual address of the initial page table.

  35:  * We place the page tables 16K below KERNEL_RAM_VADDR.  Therefore, we must

  36:  * make sure that KERNEL_RAM_VADDR is correctly set.  Currently, we expect

  37:  * the least significant 16 bits to be 0x8000, but we could probably

  38:  * relax this restriction to KERNEL_RAM_VADDR >= PAGE_OFFSET + 0x4000.

  39:  */

  40: #if (KERNEL_RAM_VADDR & 0xffff) != 0x8000

  41: #error KERNEL_RAM_VADDR must start at 0xXXXX8000

  42: #endif

  43:  

  44:     .globl    swapper_pg_dir

  45:     .equ    swapper_pg_dir, KERNEL_RAM_VADDR - 0x4000

  46:  

  47:     .macro    pgtbl, rd

  48:     ldr    \rd, =(KERNEL_RAM_PADDR - 0x4000)

  49:     .endm

  50:  

  51: #ifdef CONFIG_XIP_KERNEL

  52: #define KERNEL_START    XIP_VIRT_ADDR(CONFIG_XIP_PHYS_ADDR)

  53: #define KERNEL_END    _edata_loc

  54: #else

  55: #define KERNEL_START    KERNEL_RAM_VADDR

  56: #define KERNEL_END    _end

  57: #endif

  58:  

  59: /*

  60:  * Kernel startup entry point.

  61:  * ---------------------------

  62:  *

  63:  * This is normally called from the decompressor code.  The requirements

  64:  * are: MMU = off, D-cache = off, I-cache = dont care, r0 = 0,

  65:  * r1 = machine nr, r2 = atags pointer.

  66:  *

  67:  * This code is mostly position independent, so if you link the kernel at

  68:  * 0xc0008000, you call this at __pa(0xc0008000).

  69:  *

  70:  * See linux/arch/arm/tools/mach-types for the complete list of machine

  71:  * numbers for r1.

  72:  *

  73:  * We're trying to keep crap to a minimum; DO NOT add any machine specific

  74:  * crap here - that's what the boot loader (or in extreme, well justified

  75:  * circumstances, zImage) is for.

  76:  */

  77:     .section ".text.head", "ax"

  78: ENTRY(stext)

  79:     msr    cpsr_c, #PSR_F_BIT | PSR_I_BIT | SVC_MODE @ ensure svc mode

  80:                         @ and irqs disabled

  81:     mrc    p15, 0, r9, c0, c0        @ get processor id

  82:     bl    __lookup_processor_type        @ r5=procinfo r9=cpuid

  83:     movs    r10, r5                @ invalid processor (r5=0)?

  84:     beq    __error_p            @ yes, error 'p'

  85:     bl    __lookup_machine_type        @ r5=machinfo

  86:     movs    r8, r5                @ invalid machine (r5=0)?

  87:     beq    __error_a            @ yes, error 'a'

  88:     bl    __vet_atags

  89:     bl    __create_page_tables

  90:  

  91:     /*

  92:      * The following calls CPU specific code in a position independent

  93:      * manner.  See arch/arm/mm/proc-*.S for details.  r10 = base of

  94:      * xxx_proc_info structure selected by __lookup_machine_type

  95:      * above.  On return, the CPU will be ready for the MMU to be

  96:      * turned on, and r0 will hold the CPU control register value.

  97:      */

  98:     ldr    r13, __switch_data        @ address to jump to after

  99:                         @ mmu has been enabled

 100:     adr    lr, __enable_mmu        @ return (PIC) address

 101:     add    pc, r10, #PROCINFO_INITFUNC

 102: ENDPROC(stext)

 103:  

 104: #if defined(CONFIG_SMP)

 105: ENTRY(secondary_startup)

 106:     /*

 107:      * Common entry point for secondary CPUs.

 108:      *

 109:      * Ensure that we're in SVC mode, and IRQs are disabled.  Lookup

 110:      * the processor type - there is no need to check the machine type

 111:      * as it has already been validated by the primary processor.

 112:      */

 113:     msr    cpsr_c, #PSR_F_BIT | PSR_I_BIT | SVC_MODE

 114:     mrc    p15, 0, r9, c0, c0        @ get processor id

 115:     bl    __lookup_processor_type

 116:     movs    r10, r5                @ invalid processor?

 117:     moveq    r0, #'p'            @ yes, error 'p'

 118:     beq    __error

 119:  

 120:     /*

 121:      * Use the page tables supplied from  __cpu_up.

 122:      */

 123:     adr    r4, __secondary_data

 124:     ldmia    r4, {r5, r7, r13}        @ address to jump to after

 125:     sub    r4, r4, r5            @ mmu has been enabled

 126:     ldr    r4, [r7, r4]            @ get secondary_data.pgdir

 127:     adr    lr, __enable_mmu        @ return address

 128:     add    pc, r10, #PROCINFO_INITFUNC    @ initialise processor

 129:                         @ (return control reg)

 130: ENDPROC(secondary_startup)

 131:  

 132:     /*

 133:      * r6  = &secondary_data

 134:      */

 135: ENTRY(__secondary_switched)

 136:     ldr    sp, [r7, #4]            @ get secondary_data.stack

 137:     mov    fp, #0

 138:     b    secondary_start_kernel

 139: ENDPROC(__secondary_switched)

 140:  

 141:     .type    __secondary_data, %object

 142: __secondary_data:

 143:     .long    .

 144:     .long    secondary_data

 145:     .long    __secondary_switched

 146: #endif /* defined(CONFIG_SMP) */

 147:  

 148:  

 149:  

 150: /*

 151:  * Setup common bits before finally enabling the MMU.  Essentially

 152:  * this is just loading the page table pointer and domain access

 153:  * registers.

 154:  */

 155: __enable_mmu:

 156: #ifdef CONFIG_ALIGNMENT_TRAP

 157:     orr    r0, r0, #CR_A

 158: #else

 159:     bic    r0, r0, #CR_A

 160: #endif

 161: #ifdef CONFIG_CPU_DCACHE_DISABLE

 162:     bic    r0, r0, #CR_C

 163: #endif

 164: #ifdef CONFIG_CPU_BPREDICT_DISABLE

 165:     bic    r0, r0, #CR_Z

 166: #endif

 167: #ifdef CONFIG_CPU_ICACHE_DISABLE

 168:     bic    r0, r0, #CR_I

 169: #endif

 170:     mov    r5, #(domain_val(DOMAIN_USER, DOMAIN_MANAGER) | \

 171:               domain_val(DOMAIN_KERNEL, DOMAIN_MANAGER) | \

 172:               domain_val(DOMAIN_TABLE, DOMAIN_MANAGER) | \

 173:               domain_val(DOMAIN_IO, DOMAIN_CLIENT))

 174:     mcr    p15, 0, r5, c3, c0, 0        @ load domain access register

 175:     mcr    p15, 0, r4, c2, c0, 0        @ load page table pointer

 176:     b    __turn_mmu_on

 177: ENDPROC(__enable_mmu)

 178:  

 179: /*

 180:  * Enable the MMU.  This completely changes the structure of the visible

 181:  * memory space.  You will not be able to trace execution through this.

 182:  * If you have an enquiry about this, *please* check the linux-arm-kernel

 183:  * mailing list archives BEFORE sending another post to the list.

 184:  *

 185:  *  r0  = cp#15 control register

 186:  *  r13 = *virtual* address to jump to upon completion

 187:  *

 188:  * other registers depend on the function called upon completion

 189:  */

 190:     .align    5

 191: __turn_mmu_on:

 192:     mov    r0, r0

 193:     mcr    p15, 0, r0, c1, c0, 0        @ write control reg

 194:     mrc    p15, 0, r3, c0, c0, 0        @ read id reg

 195:     mov    r3, r3

 196:     mov    r3, r3

 197:     mov    pc, r13

 198: ENDPROC(__turn_mmu_on)

 199:  

 200:  

 201: /*

 202:  * Setup the initial page tables.  We only setup the barest

 203:  * amount which are required to get the kernel running, which

 204:  * generally means mapping in the kernel code.

 205:  *

 206:  * r8  = machinfo

 207:  * r9  = cpuid

 208:  * r10 = procinfo

 209:  *

 210:  * Returns:

 211:  *  r0, r3, r6, r7 corrupted

 212:  *  r4 = physical page table address

 213:  */

 214: __create_page_tables:

 215:     pgtbl    r4                @ page table address

 216:  

 217:     /*

 218:      * Clear the 16K level 1 swapper page table

 219:      */

 220:     mov    r0, r4

 221:     mov    r3, #0

 222:     add    r6, r0, #0x4000

 223: 1:    str    r3, [r0], #4

 224:     str    r3, [r0], #4

 225:     str    r3, [r0], #4

 226:     str    r3, [r0], #4

 227:     teq    r0, r6

 228:     bne    1b

 229:  

 230:     ldr    r7, [r10, #PROCINFO_MM_MMUFLAGS] @ mm_mmuflags

 231:  

 232:     /*

 233:      * Create identity mapping for first MB of kernel to

 234:      * cater for the MMU enable.  This identity mapping

 235:      * will be removed by paging_init().  We use our current program

 236:      * counter to determine corresponding section base address.

 237:      */

 238:     mov    r6, pc, lsr #20            @ start of kernel section

 239:     orr    r3, r7, r6, lsl #20        @ flags + kernel base

 240:     str    r3, [r4, r6, lsl #2]        @ identity mapping

 241:  

 242:     /*

 243:      * Now setup the pagetables for our kernel direct

 244:      * mapped region.

 245:      */

 246:     add    r0, r4,  #(KERNEL_START & 0xff000000) >> 18

 247:     str    r3, [r0, #(KERNEL_START & 0x00f00000) >> 18]!

 248:     ldr    r6, =(KERNEL_END - 1)

 249:     add    r0, r0, #4

 250:     add    r6, r4, r6, lsr #18

 251: 1:    cmp    r0, r6

 252:     add    r3, r3, #1 << 20

 253:     strls    r3, [r0], #4

 254:     bls    1b

 255:  

 256: #ifdef CONFIG_XIP_KERNEL

 257:     /*

 258:      * Map some ram to cover our .data and .bss areas.

 259:      */

 260:     orr    r3, r7, #(KERNEL_RAM_PADDR & 0xff000000)

 261:     .if    (KERNEL_RAM_PADDR & 0x00f00000)

 262:     orr    r3, r3, #(KERNEL_RAM_PADDR & 0x00f00000)

 263:     .endif

 264:     add    r0, r4,  #(KERNEL_RAM_VADDR & 0xff000000) >> 18

 265:     str    r3, [r0, #(KERNEL_RAM_VADDR & 0x00f00000) >> 18]!

 266:     ldr    r6, =(_end - 1)

 267:     add    r0, r0, #4

 268:     add    r6, r4, r6, lsr #18

 269: 1:    cmp    r0, r6

 270:     add    r3, r3, #1 << 20

 271:     strls    r3, [r0], #4

 272:     bls    1b

 273: #endif

 274:  

 275:     /*

 276:      * Then map first 1MB of ram in case it contains our boot params.

 277:      */

 278:     add    r0, r4, #PAGE_OFFSET >> 18

 279:     orr    r6, r7, #(PHYS_OFFSET & 0xff000000)

 280:     .if    (PHYS_OFFSET & 0x00f00000)

 281:     orr    r6, r6, #(PHYS_OFFSET & 0x00f00000)

 282:     .endif

 283:     str    r6, [r0]

 284:  

 285: #ifdef CONFIG_DEBUG_LL

 286:     ldr    r7, [r10, #PROCINFO_IO_MMUFLAGS] @ io_mmuflags

 287:     /*

 288:      * Map in IO space for serial debugging.

 289:      * This allows debug messages to be output

 290:      * via a serial console before paging_init.

 291:      */

 292:     ldr    r3, [r8, #MACHINFO_PGOFFIO]

 293:     add    r0, r4, r3

 294:     rsb    r3, r3, #0x4000            @ PTRS_PER_PGD*sizeof(long)

 295:     cmp    r3, #0x0800            @ limit to 512MB

 296:     movhi    r3, #0x0800

 297:     add    r6, r0, r3

 298:     ldr    r3, [r8, #MACHINFO_PHYSIO]

 299:     orr    r3, r3, r7

 300: 1:    str    r3, [r0], #4

 301:     add    r3, r3, #1 << 20

 302:     teq    r0, r6

 303:     bne    1b

 304: #if defined(CONFIG_ARCH_NETWINDER) || defined(CONFIG_ARCH_CATS)

 305:     /*

 306:      * If we're using the NetWinder or CATS, we also need to map

 307:      * in the 16550-type serial port for the debug messages

 308:      */

 309:     add    r0, r4, #0xff000000 >> 18

 310:     orr    r3, r7, #0x7c000000

 311:     str    r3, [r0]

 312: #endif

 313: #ifdef CONFIG_ARCH_RPC

 314:     /*

 315:      * Map in screen at 0x02000000 & SCREEN2_BASE

 316:      * Similar reasons here - for debug.  This is

 317:      * only for Acorn RiscPC architectures.

 318:      */

 319:     add    r0, r4, #0x02000000 >> 18

 320:     orr    r3, r7, #0x02000000

 321:     str    r3, [r0]

 322:     add    r0, r4, #0xd8000000 >> 18

 323:     str    r3, [r0]

 324: #endif

 325: #endif

 326:     mov    pc, lr

 327: ENDPROC(__create_page_tables)

 328:     .ltorg

 329:  

 330: #include "head-common.S"

下面仅对__create_page_tables进行简单注释:

   1: __create_page_tables:

   2:     pgtbl    r4                @ page table address

   3:  

   4:     /*

   5:      * Clear the 16K level 1 swapper page table

   6:      */

   7:     mov    r0, r4

   8:     mov    r3, #0

   9:     add    r6, r0, #0x4000

  10: 1:    str    r3, [r0], #4

  11:     str    r3, [r0], #4

  12:     str    r3, [r0], #4

  13:     str    r3, [r0], #4

  14:     teq    r0, r6

  15:     bne    1b

  16:  

  17:     ldr    r7, [r10, #PROCINFO_MM_MMUFLAGS] @ mm_mmuflags

  18:  

  19:     /*

  20:      * Create identity mapping for first MB of kernel to

  21:      * cater for the MMU enable.  This identity mapping

  22:      * will be removed by paging_init().  We use our current program

  23:      * counter to determine corresponding section base address.

  24:      下面三句完成:

  25:      以tq2440为例:

  26: 

  27:      将虚拟机地址0x30000000~0x30100000映射到物理地址的0x30000000~0x30100000-1

  28: 

  29:      */

  30:     mov    r6, pc, lsr #20            @ start of kernel section  此时pc在0x30008000附近,r6=0x300

  31:     orr    r3, r7, r6, lsl #20        @ flags + kernel base      构造段描述符的内容,为什么是20,参见《ARM体系结构与编程》

  32:     str    r3, [r4, r6, lsl #2]        @ identity mapping     填写页表项,完成映射

  33:     

  34:  

  35:     /*

  36:      * Now setup the pagetables for our kernel direct

  37:      * mapped region.

  38:      KERNEL_START = 0xC0008000

  39:      KERNEL_END = _end  在链接脚本中,它的地址是kernel镜像后面的一个byte的地址

  40: 

  41:      */

  42:     add    r0, r4,  #(KERNEL_START & 0xff000000) >> 18 

  43:     @为什么是18,因为一级页表每个描述符4个字节,r4是一个字节一个字节的加

  44:     str    r3, [r0, #(KERNEL_START & 0x00f00000) >> 18]!

  45:     @上面完成的任务:将虚拟地址0xC0000000~0xC0100000-1映射到物理地址的0x30000000~0x30100000-1,因为r3

  46:     @中还是上次的值

  47:     

  48:     ldr    r6, =(KERNEL_END - 1)  @可以知道r6是一个虚拟地址,0xC0008000+解压后的内核大小-1

  49:     add    r0, r0, #4  @r0指向下一个待填写的页表项

  50:     add    r6, r4, r6, lsr #18  @r6指向最后一个页表项的地址 ls后缀:无符号数小于等于

  51: 1:    cmp    r0, r6

  52:     add    r3, r3, #1 << 20

  53:     strls    r3, [r0], #4

  54:     bls    1b   

  55:     @通过循环,将内核所在的虚拟地址空间(0xC0008000+解压内核大小-1)映射到物理内存

  56:     @0x30008000+解压内核大小-1,接下来,mmu开启后,就不用考虑是不是位置无关码了。

  57:  

  58:  

  59:     /*

  60:      * Then map first 1MB of ram in case it contains our boot params.

  61:      个人感觉:

  62:      对于tq2440将内核加载到距离物理内存起始地址32KiB的地方时,也就是0x30008000,下面的代码

  63:      不要也可以,因为下面的目的就是将虚拟地址0xC0000000映射到物理地址的0x30000000,这个

  64:      上面的代码已经完成了。

  65: 

  66:      但是,如果没有将内核加载到距离物理内存起始地址32KiB的地方,比如加载到0x30300000,即距离

  67:      物理内存起始地址3MiB的地方,下面的代码就有必要了,这种情况下,上面的代码仅仅完成了将:

  68: 

  69:      虚拟地址0xC0300000~解压内核大小-1映射到物理内存0x30300000~解压内核大小-1,没有将uboot传给

  70:      内核的参数所在的内存区域(一般在距离物理内存起始地址16KiB范围内)进行映射。下面的代码完成了

  71:      这个任务,此时PAGE_OFFSET=0xc0000000  PHYS_OFFSET=0x30000000

  72:      完成将虚拟地址0xC0000000~0xC0100000-1映射到物理地址的0x30000000~0x30100000-1

  73:      */

  74:     add    r0, r4, #PAGE_OFFSET >> 18

  75:     orr    r6, r7, #(PHYS_OFFSET & 0xff000000)

  76:     .if    (PHYS_OFFSET & 0x00f00000)

  77:     orr    r6, r6, #(PHYS_OFFSET & 0x00f00000)

  78:     .endif

  79:     str    r6, [r0]

  80:  

  81:     mov    pc, lr

  82: ENDPROC(__create_page_tables)

  83:     .ltorg

4. 调用平台特定的 __cpu_flush 函数

当 __create_page_tables 返回之后

此时,一些特定寄存器的值如下所示:

r4 = pgtbl              (page table 的物理基地址)

r8 = machine info       (struct machine_desc的基地址)

r9 = cpu id             (通过cp15协处理器获得的cpu id)

r10 = procinfo          (struct proc_info_list的基地址)

在我们需要在开启mmu之前,做一些必须的工作:清除ICache, 清除 DCache, 清除 Writebuffer, 清除TLB等.

这些一般是通过cp15协处理器来实现的,并且是平台相关的. 这就是 __cpu_flush 需要做的工作.

在 arch/arm/kernel/head.S中

00091:         ldr        r13, __switch_data                @ address to jump to after

00092:                                                 @ mmu has been enabled     
00093:         adr        lr, __enable_mmu                @ return (PIC) address     
00094:         add        pc, r10, #PROCINFO_INITFUNC            
第91行: 将r13设置为 __switch_data 的地址

第92行: 将lr设置为 __enable_mmu 的地址

第93行: r10存储的是procinfo的基地址, PROCINFO_INITFUNC是在 arch/arm/kernel/asm-offset.c 中107行定义.

则该行将pc设为 proc_info_list的 __cpu_flush 函数的地址, 即下面跳转到该函数.

对于arm920t来说,PROCINFO_INITFUNC=16,此时r10+16---->b __arm920_setup

   1: .section ".proc.info.init", #alloc, #execinstr

   2:  

   3: .type    __arm920_proc_info,#object

   4: m920_proc_info:

   5: .long    0x41009200

   6: .long    0xff00fff0

   7: .long   PMD_TYPE_SECT | \

   8:     PMD_SECT_BUFFERABLE | \

   9:     PMD_SECT_CACHEABLE | \

  10:     PMD_BIT4 | \

  11:     PMD_SECT_AP_WRITE | \

  12:     PMD_SECT_AP_READ

  13: .long   PMD_TYPE_SECT | \

  14:     PMD_BIT4 | \

  15:     PMD_SECT_AP_WRITE | \

  16:     PMD_SECT_AP_READ

  17: b    __arm920_setup

  18: .long    cpu_arch_name

  19: .long    cpu_elf_name

  20: .long    HWCAP_SWP | HWCAP_HALF | HWCAP_THUMB

  21: .long    cpu_arm920_name

  22: .long    arm920_processor_functions

  23: .long    v4wbi_tlb_fns

  24: .long    v4wb_user_fns

  25: def CONFIG_CPU_DCACHE_WRITETHROUGH

  26: .long    arm920_cache_fns

  27: e

  28: .long    v4wt_cache_fns

  29: if

  30: .size    __arm920_proc_info, . - __arm920_proc_info

在分析 __lookup_processor_type 的时候,我们已经知道,对于 ARM926EJS 来说,其__cpu_flush指向的是函数 __arm926_setup

下面我们来分析函数 __arm926_setup

在 arch/arm/mm/proc-arm926.S 中:

00391:         .type        __arm926_setup, #function

00392: __arm926_setup:

00393:         mov        r0, #0

00394:         mcr        p15, 0, r0, c7, c7                @ invalidate I,D caches on v4

00395:         mcr        p15, 0, r0, c7, c10, 4                @ drain write buffer on v4

00396: #ifdef CONFIG_MMU

00397:         mcr        p15, 0, r0, c8, c7                @ invalidate I,D TLBs on v4

00398: #endif

00399:

00400:

00401: #ifdef CONFIG_CPU_DCACHE_WRITETHROUGH

00402:         mov        r0, #4                                @ disable write-back on caches explicitly

00403:         mcr        p15, 7, r0, c15, c0, 0

00404: #endif

00405:

00406:         adr        r5, arm926_crval

00407:         ldmia        r5, {r5, r6}

00408:         mrc        p15, 0, r0, c1, c0                @ get control register v4

00409:         bic        r0, r0, r5

00410:         orr        r0, r0, r6

00411: #ifdef CONFIG_CPU_CACHE_ROUND_ROBIN

00412:         orr        r0, r0, #0x4000                        @ .1.. .... .... ....

00413: #endif

00414:         mov        pc, lr        
00415:         .size        __arm926_setup, . - __arm926_setup

00416:

00417:         /*

00418:          *  R

00419:          * .RVI ZFRS BLDP WCAM

00420:          * .011 0001 ..11 0101

00421:          *

00422:          */

00423:         .type        arm926_crval, #object

00424: arm926_crval:

00425:         crval        clear=0x00007f3f, mmuset=0x00003135, ucset=0x00001134

第391, 392行: 是函数声明

第393行: 将r0设置为0

第394行: 清除(invalidate)Instruction Cache 和 Data Cache.

第395行: 清除(drain) Write Buffer.

第396 - 398行: 如果有配置了MMU,则需要清除(invalidate)Instruction TLB 和Data TLB

接下来,是对控制寄存器c1进行配置,请参考 ARM926 TRM.

第401 - 404行: 如果配置了Data Cache使用writethrough方式, 需要关掉write-back.

第406行: 取arm926_crval的地址到r5中, arm926_crval 在第424行

第407行: 这里我们需要看一下424和425行,其中用到了宏crval,crval是在 arch/arm/mm/proc-macro.S 中:

00053:         .macro        crval, clear, mmuset, ucset

00054: #ifdef CONFIG_MMU

00055:         .word        \clear

00056:         .word        \mmuset

00057: #else

00058:         .word        \clear

00059:         .word        \ucset

00060: #endif

00061:         .endm

配合425行,我们可以看出,首先在arm926_crval的地址处存放了clear的值,然后接下来的地址存放了mmuset的值(对于配置了MMU的情况)               
所以,在407行中,我们将clear和mmuset的值分别存到了r5, r6中

第408行: 获得控制寄存器c1的值

第409行:  将r0中的 clear (r5) 对应的位都清除掉

第410行: 设置r0中 mmuset (r6) 对应的位

第411 - 413行: 如果配置了使用 round robin方式,需要设置控制寄存器c1的 Bit[16]

第412行: 取lr的值到pc中.

而lr中的值存放的是 __enable_mmu 的地址(arch/arm/kernel/head.S 93行),所以,接下来就是跳转到函数 __enable_mmu

5. 开启mmu

开启mmu是又函数 __enable_mmu 实现的.

        在进入 __enable_mmu 的时候, r0中已经存放了控制寄存器c1的一些配置(在上一步中进行的设置), 但是并没有真正的打开mmu,

在 __enable_mmu 中,我们将打开mmu.

此时,一些特定寄存器的值如下所示:

r0 = c1 parameters      (用来配置控制寄存器的参数)        
r4 = pgtbl              (page table 的物理基地址)

r8 = machine info       (struct machine_desc的基地址)

r9 = cpu id             (通过cp15协处理器获得的cpu id)

r10 = procinfo          (struct proc_info_list的基地址)

在 arch/arm/kernel/head.S 中:

00146:         .type        __enable_mmu, %function

00147: __enable_mmu:

00148: #ifdef CONFIG_ALIGNMENT_TRAP

00149:         orr        r0, r0, #CR_A

00150: #else

00151:         bic        r0, r0, #CR_A

00152: #endif

00153: #ifdef CONFIG_CPU_DCACHE_DISABLE

00154:         bic        r0, r0, #CR_C

00155: #endif

00156: #ifdef CONFIG_CPU_BPREDICT_DISABLE

00157:         bic        r0, r0, #CR_Z

00158: #endif

00159: #ifdef CONFIG_CPU_ICACHE_DISABLE

00160:         bic        r0, r0, #CR_I

00161: #endif

00162:         mov        r5, #(domain_val(DOMAIN_USER, DOMAIN_MANAGER) | \

00163:                       domain_val(DOMAIN_KERNEL, DOMAIN_MANAGER) | \

00164:                       domain_val(DOMAIN_TABLE, DOMAIN_MANAGER) | \

00165:                       domain_val(DOMAIN_IO, DOMAIN_CLIENT))

00166:         mcr        p15, 0, r5, c3, c0, 0                @ load domain access register

00167:         mcr        p15, 0, r4, c2, c0, 0                @ load page table pointer

00168:         b        __turn_mmu_on

00169:

00170: /*

00171:  * Enable the MMU.  This completely changes the structure of the visible

00172:  * memory space.  You will not be able to trace execution through this.

00173:  * If you have an enquiry about this, *please* check the linux-arm-kernel

00174:  * mailing list archives BEFORE sending another post to the list.

00175:  *

00176:  *  r0  = cp#15 control register

00177:  *  r13 = *virtual* address to jump to upon completion

00178:  *

00179:  * other registers depend on the function called upon completion

00180:  */

00181:         .align        5

00182:         .type        __turn_mmu_on, %function

00183: __turn_mmu_on:

00184:         mov        r0, r0

00185:         mcr        p15, 0, r0, c1, c0, 0                @ write control reg

00186:         mrc        p15, 0, r3, c0, c0, 0                @ read id reg

00187:         mov        r3, r3

00188:         mov        r3, r3

00189:         mov        pc, r13

第146, 147行: 函数声明

第148 - 161行:  根据相应的配置,设置r0中的相应的Bit. (r0 将用来配置控制寄存器c1)

第162 - 165行: 设置 domain 参数r5.(r5 将用来配置domain)

第166行: 配置 domain (详细信息清参考arm相关手册)

第167行: 配置页表在存储器中的位置(set ttb).这里页表的基地址是r4, 通过写cp15的c2寄存器来设置页表基地址.

第168行: 跳转到 __turn_mmu_on. 从名称我们可以猜到,下面是要真正打开mmu了.

(继续向下看,我们会发现,__turn_mmu_on就下当前代码的下方,为什么要跳转一下呢? 这是有原因的. go on)

第169 - 180行: 空行和注释. 这里的注释我们可以看到, r0是cp15控制寄存器的内容, r13存储了完成后需要跳转的虚拟地址(因为完成后mmu已经打开了,都是虚拟地址了).

第181行: .algin 5 这句是cache line对齐. 我们可以看到下面一行就是 __turn_mmu_on, 之所以

第182 - 183行:  __turn_mmu_on 的函数声明. 这里我们可以看到, __turn_mmu_on 是紧接着上面第168行的跳转指令的,只是中间在第181行多了一个cache line对齐.

这么做的原因是: 下面我们要进行真正的打开mmu操作了, 我们要把打开mmu的操作放到一个单独的cache line上. 而在之前的"启动条件"一节我们说了,I Cache是可以打开也可以关闭的,这里这么做的原因是要保证在I Cache打开的时候,打开mmu的操作也能正常执行.

第184行: 这是一个空操作,相当于nop. 在arm中,nop操作经常用指令 mov rd, rd 来实现.

注意: 为什么这里要有一个nop,我思考了很长时间,这里是我的猜测,可能不是正确的:

因为之前设置了页表基地址(set ttb),到下一行(185行)打开mmu操作,中间的指令序列是这样的:

set ttb(第167行)

branch(第168行)

nop(第184行)

enable mmu(第185行)

对于arm的五级流水线: fetch - decode - execute - memory - write

他们执行的情况如下图所示:

这里需要说明的是,branch操作会在3个cycle中完成,并且会导致重新取指.

        从这个图我们可以看出来,在enable mmu操作取指的时候, set ttb操作刚好完成.

第185行: 写cp15的控制寄存器c1, 这里是打开mmu的操作,同时会打开cache等(根据r0相应的配置)

第186行: 读取id寄存器.

第187 - 188行: 两个nop.

第189行: 取r13到pc中,我们前面已经看到了, r13中存储的是 __switch_data (在 arch/arm/kernel/head.S 91行),下面会跳到 __switch_data.

第187,188行的两个nop是非常重要的,因为在185行打开mmu操作之后,要等到3个cycle之后才会生效,这和arm的流水线有关系.

因而,在打开mmu操作之后的加了两个nop操作.

6. 切换数据

在 arch/arm/kernel/head-common.S 中:

00014:         .type        __switch_data, %object

00015: __switch_data:

00016:         .long        __mmap_switched

00017:         .long        __data_loc                        @ r4

00018:         .long        __data_start                        @ r5

00019:         .long        __bss_start                        @ r6

00020:         .long        _end                                @ r7

00021:         .long        processor_id                        @ r4

00022:         .long        __machine_arch_type                @ r5

00023:         .long        cr_alignment                        @ r6

00024:         .long        init_thread_union + THREAD_START_SP @ sp

00025:

00026: /*

00027:  * The following fragment of code is executed with the MMU on in MMU mode,

00028:  * and uses absolute addresses; this is not position independent.

00029:  *

00030:  *  r0  = cp#15 control register

00031:  *  r1  = machine ID

00032:  *  r9  = processor ID

00033:  */

00034:         .type        __mmap_switched, %function

00035: __mmap_switched:

00036:         adr        r3, __switch_data + 4

00037:

00038:         ldmia        r3!, {r4, r5, r6, r7}

00039:         cmp        r4, r5                                @ Copy data segment if needed

00040: 1:        cmpne        r5, r6

00041:         ldrne        fp, [r4], #4

00042:         strne        fp, [r5], #4

00043:         bne        1b

00044:

00045:         mov        fp, #0                                @ Clear BSS (and zero fp)

00046: 1:        cmp        r6, r7

00047:         strcc        fp, [r6],#4

00048:         bcc        1b

00049:

00050:         ldmia        r3, {r4, r5, r6, sp}

00051:         str        r9, [r4]                        @ Save processor ID

00052:         str        r1, [r5]                        @ Save machine type

00053:         bic        r4, r0, #CR_A                        @ Clear 'A' bit

00054:         stmia        r6, {r0, r4}                        @ Save control register values

00055:         b        start_kernel        
第14, 15行: 函数声明

第16 - 24行: 定义了一些地址,例如第16行存储的是 __mmap_switched 的地址, 第17行存储的是 __data_loc 的地址 ......

第34, 35行: 函数 __mmap_switched

第36行: 取 __switch_data + 4的地址到r3. 从上文可以看到这个地址就是第17行的地址.

第37行: 依次取出从第17行到第20行的地址,存储到r4, r5, r6, r7 中. 并且累加r3的值.当执行完后, r3指向了第21行的位置.

对照上文,我们可以得知:

r4 - __data_loc

r5 - __data_start

r6 - __bss_start

r7 - _end

这几个符号都是在 arch/arm/kernel/vmlinux.lds.S 中定义的变量:

00102: #ifdef CONFIG_XIP_KERNEL

00103:         __data_loc = ALIGN(4);                /* location in binary */

00104:         . = PAGE_OFFSET + TEXT_OFFSET;

00105: #else

00106:         . = ALIGN(THREAD_SIZE);

00107:         __data_loc = .;

00108: #endif

00109:

00110:         .data : AT(__data_loc) {

00111:                 __data_start = .;        /* address in memory */

00112:

00113:                 /*

00114:                  * first, the init task union, aligned

00115:                  * to an 8192 byte boundary.

00116:                  */

00117:                 *(.init.task)

......

00158:         .bss : {

00159:                 __bss_start = .;        /* BSS                                */

00160:                 *(.bss)

00161:                 *(COMMON)

00162:                 _end = .;

00163:         }

对于这四个变量,我们简单的介绍一下:

__data_loc 是数据存放的位置

__data_start 是数据开始的位置

__bss_start 是bss开始的位置

_end 是bss结束的位置, 也是内核结束的位置

其中对第110行的指令讲解一下: 这里定义了.data 段,后面的AT(__data_loc) 的意思是这部分的内容是在__data_loc中存储的(要注意,储存的位置和链接的位置是可以不相同的).

关于 AT 详细的信息请参考 ld.info

第38行: 比较 __data_loc 和 __data_start

第39 - 43行: 这几行是判断数据存储的位置和数据的开始的位置是否相等,如果不相等,则需要搬运数据,从 __data_loc 将数据搬到 __data_start.

其中 __bss_start 是bss的开始的位置,也标志了 data 结束的位置,因而用其作为判断数据是否搬运完成.

第45 - 48行: 是清除 bss 段的内容,将其都置成0. 这里使用 _end 来判断 bss 的结束位置.

第50行: 因为在第38行的时候,r3被更新到指向第21行的位置.因而这里取得r4, r5, r6, sp的值分别是:

r4 - processor_id

r5 - __machine_arch_type

r6 - cr_alignment

sp - init_thread_union + THREAD_START_SP

processor_id 和 __machine_arch_type 这两个变量是在 arch/arm/kernel/setup.c 中 第62, 63行中定义的.

cr_alignment 是在 arch/arm/kernel/entry-armv.S 中定义的:

00182:         .globl        cr_alignment

00183:         .globl        cr_no_alignment

00184: cr_alignment:

00185:         .space        4

00186: cr_no_alignment:

00187:         .space        4

init_thread_union 是 init进程的基地址. 在 arch/arm/kernel/init_task.c 中:

00033: union thread_union init_thread_union

00034:         __attribute__((__section__(".init.task"))) =

00035:                 { INIT_THREAD_INFO(init_task) };        
        对照 vmlnux.lds.S 中的 的117行,我们可以知道init task是存放在 .data 段的开始8k, 并且是THREAD_SIZE(8k)对齐的

第51行: 将r9中存放的 processor id (在arch/arm/kernel/head.S 75行) 赋值给变量 processor_id

第52行: 将r1中存放的 machine id (见"启动条件"一节)赋值给变量 __machine_arch_type

第53行: 清除r0中的 CR_A 位并将值存到r4中. CR_A 是在 include/asm-arm/system.h 21行定义, 是cp15控制寄存器c1的Bit[1](alignment fault enable/disable)

第54行: 这一行是存储控制寄存器的值.

从上面 arch/arm/kernel/entry-armv.S 的代码我们可以得知.

这一句是将r0存储到了 cr_alignment 中,将r4存储到了 cr_no_alignment 中.

第55行: 最终跳转到start_kernel

arm linux kernel 从入口到start_kernel 的代码分析的更多相关文章

  1. Linux Kernel文件系统写I/O流程代码分析(二)bdi_writeback

    Linux Kernel文件系统写I/O流程代码分析(二)bdi_writeback 上一篇# Linux Kernel文件系统写I/O流程代码分析(一),我们看到Buffered IO,写操作写入到 ...

  2. Linux Kernel文件系统写I/O流程代码分析(一)

    Linux Kernel文件系统写I/O流程代码分析(一) 在Linux VFS机制简析(二)这篇博客上介绍了struct address_space_operations里底层文件系统需要实现的操作 ...

  3. arm linux kernel启动之start_kernel

    了解完kernel启动以前的汇编之后我们来看看正式的c语言启动代码,也就是我们的start_kernel函数了.start_kernel相当大,里面每一个调用到的函数都足够我们伤脑筋了,我这里只是浅尝 ...

  4. 《linux 内核全然剖析》 fork.c 代码分析笔记

    fork.c 代码分析笔记 verifiy_area long last_pid=0; //全局变量,用来记录眼下最大的pid数值 void verify_area(void * addr,int s ...

  5. linux kernel的cmdline參数解析原理分析

    利用工作之便,今天研究了kernel下cmdline參数解析过程.记录在此.与大家共享.转载请注明出处.谢谢. Kernel 版本:3.4.55 Kernel启动时会解析cmdline,然后依据这些參 ...

  6. 《linux 内核全然剖析》 sys.c 代码分析

    sys.c 代码分析 setregid /* * This is done BSD-style, with no consideration of the saved gid, except * th ...

  7. andriod and linux kernel启动流程

    虽然这里的Arm Linux kernel前面加上了Android,但实际上还是和普遍Arm linux kernel启动的过程一样的,这里只是结合一下Android的Makefile,讲一下boot ...

  8. Linux kernel的中断子系统之(八):softirq

    返回目录:<ARM-Linux中断系统>. 总结:中断分为上半部和下半部,上半部关中断:下半部开中断,处理可以延迟的事情.下半部有workqueue/softirq/tasklet三种方式 ...

  9. spinlock in linux kernel

    spinlock in linux kernel 作为一种锁机制, spinlock可以制造一段临界区, 同一时刻只有一个线程能进入这个临界区, 从而达到保护数据的目的. semaphore, mut ...

随机推荐

  1. AndroidStudio debug

    1. view as text

  2. spring 占位符 默认值

    问题: 今天结合spel使用占位符时,存在没有配置文件中没有配置项的情况,就想给配置一个默认值. 解决方案: public abstract class PlaceholderConfigurerSu ...

  3. labview中的文件格式

     

  4. ASP.NET前台AJAX方法调用后台的方法写法

    前台: <input id="AjaxDemo" type="button" onclick="get()" value=" ...

  5. LeetCode 刷题记录

    写在前面:因为要准备面试,开始了在[LeetCode]上刷题的历程.LeetCode上一共有大约150道题目,本文记录我在<http://oj.leetcode.com>上AC的所有题目, ...

  6. spring+jpg环境下,spring实现文件下载web实现通用的文件下载方法

    jar包复制到WEB-INF 文件夹lib下: commons-io-1.3.2.jar public static String download(HttpServletRequest reques ...

  7. My集合框架第五弹 最小堆

    二叉堆(以最小堆为例),其具有结构性质和堆序性质结构性质: 堆是一棵完全的二叉树,一颗高为h的完全二叉树有2^h到2^h-1个节点,高度为log N            而且该结构可以很容易的使用数 ...

  8. javaScript-原型、继承-02

    原型链 首先回顾下实列.构造函数.原型对象之间的关系: 实列都包含指向原型对象的一个指针(_proto_): 构造函数都有prototype(原型属性)指向原型对象的指针: 原型是一个对象也存在一个内 ...

  9. 分布式服务框架 Zookeeper -- 管理分布式环境中的数据(转载)

    本文转载自:http://www.ibm.com/developerworks/cn/opensource/os-cn-zookeeper/ Zookeeper 分布式服务框架是 Apache Had ...

  10. ThinkPHP模板(一)

    如何关闭ThinkPHP的模板缓存 ThinkPHP的模板缓存是无奈关闭的,因为内置的模板引擎是一个编译型的模板引擎,必须经过编译后生成一个可执行的缓存文件才能被执行.但是可以设置缓存的有效期,例如设 ...