linux kernel 从入口到start_kernel 的代码分析

 

linux kernel  从入口到 start_kernel  的代码 分析

本文的很多内容是参考了网上某位大侠的文章写的<<>> ,有些东西是直接从他那copy 过来的。

最近分析了一下u-boot 的源码,并写了分文档, 为了能够衔接那篇文章,这次又把arm linux 的启动代码大致分析了一下,特此写下了这篇文档。一来是大家可以看看u-boot 到底是如何具体跳转到linux 下跑的,二来也为自己更深入的学习linux kernel 打下基础。

本文以arm  版的linux 为例kernel 的第一条指令开始分析, 一直分析到进入start_kernel() 函数,也就是kernel 启动的汇编部分,我们把它称之为第一部分, 以后有时间在把启动的第二部分在分析一下。我们当前以linux-2.6.18 内核版本作为范例来分析, 本文中所有的代码前面都会加上行号以便于讲解。

由于启动部分有一些代码是平台相关的, 虽然大部分的平台所实现 的功能都比较类似, 但是为了更好的对code 进行说明, 对于平台相关的代码, 我们选择smdk2410 平台, CPUs3c2410(arm 核是arm920T) 进行分析。
   
另外, 本文是以未压缩的kernel 来分析的. 对于内核解压缩部分的code, arch/arm/boot/compressed, 本文不做讨论。

  

启动条件
     通常从系统上电执行的boot loader 的代码, 而要从boot loader 跳转到linux kernel 的第一条指令处执行需要一些特定的条件。关于对boot loader 的分析请看我的另一篇文档u-boot 源码分析。
    
这里讨论下进入到linux kernel 时必须具备的一些条件, 这一般是boot loader 在跳转到kernel 之前要完成的:
   1. CPU
必须处于SVC(supervisor ) 模式, 并且IRQFIQ 中断都是禁止的;
   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, 用来描述设备信息属性的列表)

    更详细的关于启动arm linux 之前要做哪些准备工作可以参考,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 +131

0x00008000

内核在RAM 中起始位置相对于

RAM 起始地址的偏移

TEXTADDR

arch/arm/kernel/head.S +49 

0xc0008000 

kernel 的起始虚拟 地址

PHYS_OFFSET

include/asm-arm/arch- *** /memory.h

平台相关

RAM 的起始物理地址,对于s3c2410 来说在include/asm-arm/arch-s3c2410/memory.h 下定义,值为0x30000000(ram 接在片选6)

 

内核的入口是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             

 

在进入linux kernel 前要确保在管理模式下,并且IRQ,FIQ 都是关闭的,因此在00073 行就是要确保这几个条件成立。

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 手册,也可直接参考s3c2410data sheet
76
跳转到__lookup_processor_type.__lookup_processor_type, 会把找到匹配的processor type  对象存储在r5 中。
77,78
判断r5 中的processor type 是否是0, 如果是0, 说明系统中没找到匹配当前processor type 的对象, 则跳转到__error_p( 出错) 。系统中会预先定义本系统支持的processor type  对象集。
     __lookup_processor_type 
函数主要是根据从cpu 中获得的processor id 和系统中预先定义的本系统能支持的proc_info 集进行匹配, 看系统能否支持当前的processor,  并将匹配到的proc_info 的基地址存到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 行的位置的地址, 因而执行完后
        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:

};

我们当前以s3c2410 为例,processor920t.

arch/arm/mm/proc-arm920.S :

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

00449:

00450: .type    __arm920_proc_info,#object

00451:        __arm920_proc_info:

00452:        .long    0x41009200

004523:        .long    0xff00fff0

00454:        .long    PMD_TYPE_SECT | /

00455:            PMD_SECT_BUFFERABLE | /

00456:            PMD_SECT_CACHEABLE | /

00457:            PMD_BIT4 | /

00458:            PMD_SECT_AP_WRITE | /

00459:            PMD_SECT_AP_READ

00460:        .long    PMD_TYPE_SECT | /

00461:              PMD_BIT4 | /

00462:             PMD_SECT_AP_WRITE | /

00463:             PMD_SECT_AP_READ

00464:        b    __arm920_setup

00465:        .long    cpu_arch_name

00466:        .long    cpu_elf_name

00467:       .long    HWCAP_SWP | HWCAP_HALF | HWCAP_THUMB

00468:       .long    cpu_arm920_name

00469:       .long    arm920_processor_functions

00470:       .long    v4wbi_tlb_fns

00471:        .long    v4wb_user_fns

00472:   #ifndef CONFIG_CPU_DCACHE_WRITETHROUGH

00473:       .long   arm920_cache_fns

00474:   #else

00475:       .long    v4wt_cache_fns

00476:   #endif

00477:        .size    __arm920_proc_info, . - __arm920_proc_info

 

448, 我们可以看到 __arm920_proc_info  被放到了".proc.info.init" 段中. 对照struct proc_info_list, 我们可以看到 __cpu_flush 的定义是在464,__arm920_setup.( 我们将在"4.  调用平台 特定的__cpu_flush 函数" 一节中详细分析这部分的内容.)

我们继续分析__lookup_processor_type

149 从上面的分析我们可以知道r3 中存储的是3f 处的物理地址,r7 存储的是3f 处的虚拟 地址, 这一行是计算当前程序运行的物理地址和虚拟地址的差值, 将其保存到r3.
150
r5 存储的虚拟地址(__proc_info_begin) 转换成物理地址
151
r6 存储的虚拟地址(__proc_info_end) 转换成物理地址
152
对照struct proc_info_list, 可以得知, 这句是将当前proc_infocpu_valcpu_mask 分别存

r3, r4
153
: r9 中存储了processor id(arch/arm/kernel/head.S 中的75),r4cpu_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

继续分析head.S, 确定 processor type 后,就要确定 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 函数proc_info 一样,在系统中也预先定义好了本系统能支持的machine type 集, 在__lookup_machine_type, 就是要查找系统中是否有对当前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

实际上上面这段代码的原理和确定processor type 的原理是一样的。

 

内核中, 一般使用宏MACHINE_START 来定义machine type

对于smdk2410 来说 arch/arm/mach-s3c2410/Mach-smdk2410.c :
MACHINE_START(SMDK2410, "SMDK2410") /* @TODO: request a new identifier and switch

                      * to SMDK2410 */

     /* Maintainer: Jonas Dietsche */

     .phys_io     = S3C2410_PA_UART,

     .io_pg_offst     = (((u32)S3C24XX_VA_UART) >> 18) & 0xfffc,

     .boot_params     = S3C2410_SDRAM_PA + 0x100,

     .map_io      = smdk2410_map_io,

     .init_irq    = s3c24xx_init_irq,

     .init_machine    = smdk_machine_init,

     .timer       = &s3c24xx_timer,

MACHINE_END

195 行:把3b 处的地址存入r3 中,3b 处的地址就是178 行处的地址。

196 3b 处开始的连续地址即3b 处的地址,__arch_info_begin__arch_info_end 依次存入r4,r5,r6.
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.  创建页表

继续分析head.S, 确定了processor type machine type 之后,就是创建页表。

通过前面的两步, 我们已经确定了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 实现
这里, 我们使用的是armL1 主页表,L1 主页表也称为段页表(section page table) , L1  主页表将4 GB  的地址空间分成若干个1 MB 的段(section), 因此L1 页表包含4096 个页表项(section entry).  每个页表项是32 bits(4 bytes)
因而L1 主页表占用 4096 *4 = 16k 的内存空间.

对于ARM920,L1 section entry 的格式为可参考arm920t TRM):

它的地址翻译过程如下:

 linux kernel 从入口到start_kernel 的代码分析_第1张图片

下面我们来分析 __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. We round TEXTADDR down to the

00238:            * nearest megabyte boundary.   It is assumed that

00239:           * the kernel fits within 4 contigous 1MB sections.
00240:          */
00241:         add       r0, r4,  #(TEXTADDR & 0xff000000) >> 18        @ start of kernel
00242:         str      r3, [r0, #(TEXTADDR & 0x00f00000) >> 18]!
00243:          add       r3, r3, #1 << 20
00244:         str       r3, [r0, #4]!           @ KERNEL + 1MB
00245:         add       r3, r3, #1 << 20

00246:          str       r3, [r0, #4]!           @ KERNEL + 2MB
00247:          add       r3, r3, #1 << 20
00248:         str       r3, [r0, #4]            @ KERNEL + 3MB
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 中定义)

231 通过pc 值的高12( 右移20), 得到kernelsection 基址( 从上面的图可以看出), 并存储到r6. 因为当前是通过运行时地址得到的kernelsection 地址, 因而是物理地址.
232
: r3 = r7 | (r6 << 20); flags + kernel base, 得到页表中需要设置的值.
233
设置页表: mem[r4 + r6 * 4] = r3, 这里, 因为页表的每一项是32 bits(4 bytes), 所以要乘以4(<<2).
上面这三行, 设置了kernel 当前运行的section( 物理地址所在的page entry) 的页表项
241--248
: TEXTADDR 是内核的起始虚拟 地址(0xc0008000),  这几行是设置kernel 起始4M 虚拟地址的页表项( 个人觉得242 行设置的页表项和上面233 行设置的页表项是同一个,因为r3 没有变,就是kernel1M 的页表项)

/* TODO:  这两行的code 很奇怪, 为什么要先取TEXTADDR 的高8(Bit[31:24])0xff000000, 然后再取后面的8(Bit[23:20])0x00f00000*/           
253
r0 设置为RAM 第一兆虚拟地址的页表项地址(page entry)
254
: r7 中存储的是mmu flags,  逻辑或上RAM 的起始物理地址, 得到RAM 第一个MB 页表项的值.
255
:  设置RAM 的第一个MB 虚拟地址的页表.
上面这三行是用来设置RAM 中第一兆虚拟地址的页表 之所以要设置这个页表项的原因是RAM 的第一兆内存中可能存储着boot params.

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

 linux kernel 从入口到start_kernel 的代码分析_第2张图片

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            

91r13 设置为 __switch_data  的地址
92lr 设置为 __enable_mmu  的地址
93: r10 存储的是procinfo 的基地址, PROCINFO_INITFUNC 是在 arch/arm/kernel/asm-offset.c 107 行定义 该行将pc 设为 proc_info_list __cpu_flush  函数的地址 即下面跳转到该函数. 在分析 __lookup_processor_type  的时候, 我们已经知道, 对于 ARM920t  来说,__cpu_flush 指向的是函数__arm920_setup

 

下面我们来分析函数 __arm920_setup        

 arch/arm/mm/proc-arm920.S :

00385: .type    __arm920_setup, #function

00386: __arm920_setup:

00387:        mov  r0, #0

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

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

00390: #ifdef CONFIG_MMU

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

00392: #endif

00393:        adr  r5, arm920_crval

00394:        ldmia    r5, {r5, r6}

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

00396:        bic  r0, r0, r5

00397:        orr  r0, r0, r6

00398:        mov  pc, lr

00399:        .size    __arm920_setup, . - __arm920_setup

385,386 定义__arm920_setup 函数。

387 行: 设置r00

388 行: 使数据cahche,  指令cache 无效。

389 行: 使write buffer 无效。

391 行: 使数据TLB, 指令TLB 无效。

393 行: 获取arm920_crval 的地址,并存入r5

394 行: 获取arm920_crval 地址处的连续8 字节分别存入r5,r6

arm920_crval arch/arm/mm/proc-arm920t.c:

     .type    arm920_crval, #object

arm920_crval:

     crval    clear=0x00003f3f, mmuset=0x00003135, ucset=0x00001130 

由此可知,r5 = 0x00003f3f, r6 = 0x00003135

 

395 行: 获取CP15 下控制寄存器的值,并存入r0

396 行: 通过查看arm920_crval 的值可知该行是清除r0 中相关位,为以后对这些位的赋值做准备。

397 行: 设置r0 中的相关位,即为mmu 做相应设置。

398 行: 函数返回。

 

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 定义__enable_mmu 函数。

148--152 行:根据配置使能或禁止地址对齐错误检测。

153--155 行:根据配置使能或禁止数据cache

156--158 行:reserved

159--161 行:根据配置使能或禁止指令cache

162--165 行:配置相应的访问权限并存入r5

166 行:把访问权限写入CP15 协处理器。

167 行:把页表地址写入CP15 协处理器。

168 行:跳转到__turn_mmu_on 来打开MMU

 

接下来就是打开MMU 了,我们看它的代码:

 

185cp15 的控制寄存器c1,  这里是打开mmu 的动作, 同时会打开cache( 根据r0 相应的配置)
186 读取id 寄存器.
187 - 188 两个nop. 
189r13pc, 我们前面已经看到了, r13 中存储的是 __switch_data (arch/arm/kernel/head.S 91), 下面会跳到 __switch_data.

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

 

6.  切换数据

下面我们就来看 __switch_data:

 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: 

14, 15 对象定义。
16 - 24 为对象里的每个域赋值,例如第16 行存储的是 __mmap_switched  的地址17 行存储的是 __data_loc  的地址 ......

由上面对__switch_data 的定义可知,最终调用的是__mmap_switched

下面我们就来看 __mmap_switched:

 arch/arm/kernel/head-common.S :
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

注意上面这些代码就已经跑在了MMU 打开的情况下了。

 

34, 35 函数 __mmap_switched 的定义。
36 __switch_data + 4 的地址到r3.  从上文可以看到这个地址就是第17 行的地址.
38:  依次取出从第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) 对齐的
51r9 中存放的 processor id (arch/arm/kernel/head.S 75 赋值给变量 processor_id
52r1 中存放的 machine id (" 启动条件" 一节) 赋值给变量 __machine_arch_type
53 清除r0 中的 CR_A  位并将值存到r4. CR_A  是在 include/asm-arm/system.h 21 行定义cp15 控制寄存器c1Bit[1](alignment fault enable/disable)
54 这一行是存储控制寄存器的值
    
从上面 arch/arm/kernel/entry-armv.S  的代码我们可以得知.
    
这一句是将r0 存储到了 cr_alignment ,r4 存储到了 cr_no_alignment .
55 最终跳转到start_kernel

你可能感兴趣的:(linux kernel 从入口到start_kernel 的代码分析)