转自:http://blog.chinaunix.net/uid-25909619-id-3380544.html

S3C2410 Linux 2.6.35.7启动分析(第二阶段)

接着上面的分析,第一阶段的代码跳转后,会进入第二阶段的代码。

第二阶段的代码是从\arch\arm\kernel\head.S开始的。

内核启动第二阶段主要完成的工作有,cpu ID检查,machine ID(也就是开发板ID)检查,创建初始化页表,设置C代码运行环境,跳转到内核第一个真正的C函数startkernel开始执行。

这一阶段涉及到两个重要的结构体:

(1) 一个是struct proc_info_list 主要描述CPU相关的信息,定义在文件arch\arm\include\asm\procinfo.h中,与其相关的函数及变量在文件arch/arm/mm/proc_arm920.S中被定义和赋值。

(2) 另一个结构体是描述开发板或者说机器信息的结构体struct machine_desc,定义在\arch\arm\include\asm\mach\arch.h文件中,其函数的定义和变量的赋值在板极相关文件arch/arm/mach-s3c2410/mach-smdk2410.c中实现,这也是内核移植非常重要的一个文件。

该阶段一般由前面的解压缩代码调用,进入该阶段要求:

MMU = off, D-cache = off, I-cache = dont care,r0 = 0, r1 = machine id.

所有的机器ID列表保存在arch/arm/tools/mach-types 文件中,在编译时会将这些机器ID按照统一的格式链接到基本内核映像文件vmlinux的__arch_info_begin和__arch_info_end之间的段中。存储格式定义在include/asm-arm/mach/arch.h文件中的结构体struct machine_desc {}。这两个结构体的内容最终会被连接到基本内核映像vmlinux中的两个段内,分别是*(.proc.info.init)和*(.arch.info.init),可以参考下面的连接脚本。

链接脚本:arch/arm/kernel/vmlinux.lds

*****************************链接脚本**************************************

SECTIONS

{

. = TEXTADDR;

.init : { /* 初始化代码段*/

_stext = .;

_sinittext = .;

*(.init.text)

_einittext = .;

__proc_info_begin = .;

*(.proc.info.init)

__proc_info_end = .;

__arch_info_begin = .;

*(.arch.info.init)

__arch_info_end = .;

__tagtable_begin = .;

*(.taglist.init)

__tagtable_end = .;

. = ALIGN(16);

__setup_start = .;

*(.init.setup)

__setup_end = .;

__early_begin = .;

*(.early_param.init)

__early_end = .;

__initcall_start = .;

*(.initcall1.init)

*(.initcall2.init)

*(.initcall3.init)

*(.initcall4.init)

*(.initcall5.init)

*(.initcall6.init)

*(.initcall7.init)

__initcall_end = .;

__con_initcall_start = .;

*(.con_initcall.init)

__con_initcall_end = .;

__security_initcall_start = .;

*(.security_initcall.init)

__security_initcall_end = .;

. = ALIGN(32);

__initramfs_start = .;

usr/built-in.o(.init.ramfs)

__initramfs_end = .;

. = ALIGN(64);

__per_cpu_start = .;

*(.data.percpu)

__per_cpu_end = .;

#ifndef CONFIG_XIP_KERNEL

__init_begin = _stext;

*(.init.data)

. = ALIGN(4096);

__init_end = .;

#endif

}

*****************************链接脚本**************************************

下面开始代码\arch\arm\kernel\head.S的注释:

开始分析前先看下一点基础知识:

1. kernel运行的史前时期和内存布局

在arm平台下,zImage.bin压缩镜像是由bootloader加载到物理内存,然后跳到zImage.bin里一段程序,它专门于将被压缩的kernel解压缩到KERNEL_RAM_PADDR开始的一段内存中,接着跳进真正的kernel去执行。该kernel的执行起点是stext函数,定义于arch/arm/kernel/head.S。此时内存的布局如下图所示

在开发板3c2410中,SDRAM连接到内存控制器的Bank6中,它的开始内存地址是0x30000000,大小为64M,即0x20000000。 ARM Linux kernel将SDRAM的开始地址定义为PHYS_OFFSET。经bootloader加载kernel并由自解压部分代码运行后,最终kernel被放置到KERNEL_RAM_PADDR(=PHYS_OFFSET + TEXT_OFFSET,即0x30008000)地址上的一段内存,经此放置后,kernel代码以后均不会被移动。

在进入kernel代码前,即bootloader和自解压缩阶段,ARM未开启MMU功能。因此kernel启动代码一个重要功能是设置好相应的页表,并开启MMU功能。为了支持MMU功能,kernel镜像中的所有符号,包括代码段和数据段的符号,在链接时都生成了它在开启MMU时,所在物理内存地址映射到的虚拟内存地址。

以arm kernel第一个符号(函数)stext为例,在编译链接,它生成的虚拟地址是0xc0008000,而放置它的物理地址为0x30008000(还记得这是PHYS_OFFSET+TEXT_OFFSET吗?)。实际上这个变换可以利用简单的公式进行表示:va = pa – PHYS_OFFSET + PAGE_OFFSET。Arm linux最终的kernel空间的页表,就是按照这个关系来建立。

之所以较早提及arm linux 的内存映射,原因是在进入kernel代码,里面所有符号地址值为清一色的0xCXXXXXXX地址,而此时ARM未开启MMU功能,故在执行stext函数第一条执行时,它的PC值就是stext所在的内存地址(即物理地址,0x30008000)。因此,下面有些代码,需要使用地址无关技术。

__HEAD  /*该宏定义了下面的代码位于".head.text"段内*/

.type stext, %function                           /*声明stext为函数*/

ENTRY(stext)                                      /*第二阶段的入口地址*/

setmode PSR_F_BIT | PSR_I_BIT | SVC_MODE, r9  @ ensure svc mode and irqs disabled 进入超级权限模式,关中断

/*从协处理器CP15,C0读取CPU ID,然后在__proc_info_begin开始的段中进行查找,如果找到,则返回对应处理器相关结构体在物理地址空间的首地址到r5,最后保存在r10中*/

mrc p15, 0, r9, c0, c0                  @ get processor id 取出cpu id

bl __lookup_processor_type           @ r5=procinfo r9=cpuid

/**********************************************************************/

__lookup_processor_type函数的具体解析开始(\arch\arm\kernel\ head-common.S)

/**********************************************************************/

在讲解该程序段之前先来看一些相关知识,内核所支持的每一种CPU 类型都由结构体proc_info_list来描述。

该结构体在文件arch/arm/include/asm/procinfo.h 中定义:

struct proc_info_list {

unsigned int cpu_val;

unsigned int cpu_mask;

unsigned long __cpu_mm_mmu_flags; /* used by head.S */

unsigned long __cpu_io_mmu_flags; /* used by head.S */

unsigned long __cpu_flush;        /* used by head.S */

const char *arch_name;

const char *elf_name;

unsigned int elf_hwcap;

const char *cpu_name;

struct processor *proc;

struct cpu_tlb_fns *tlb;

struct cpu_user_fns *user;

struct cpu_cache_fns *cache;

};

对于 arm920 来说,其对应结构体在文件 linux/arch/arm/mm/proc-arm920.S 中初始化。

.section ".proc.info.init", #alloc, #execinstr /*定义了一个段,下面的结构体存放在该段中*/

.type __arm920_proc_info,#object              /*声明一个结构体对象*/

__arm920_proc_info:                            /*为该结构体赋值*/

.long 0x41009200

.long 0xff00fff0

.long  PMD_TYPE_SECT | \

PMD_SECT_BUFFERABLE | \

PMD_SECT_CACHEABLE | \

PMD_BIT4 | \

PMD_SECT_AP_WRITE | \

PMD_SECT_AP_READ

.long  PMD_TYPE_SECT | \

PMD_BIT4 | \

PMD_SECT_AP_WRITE | \

PMD_SECT_AP_READ

b __arm920_setup

…………………………………

.section ".proc.info.init"表明了该结构在编译后存放的位置。在链接文件 arch/arm/kernel/vmlinux.lds 中:

SECTIONS

{

#ifdef CONFIG_XIP_KERNEL

. = XIP_VIRT_ADDR(CONFIG_XIP_PHYS_ADDR);

#else

. = PAGE_OFFSET + TEXT_OFFSET;

#endif

.text.head : {

_stext = .;

_sinittext = .;

*(.text.head)

}

.init : { /* Init code and data */

INIT_TEXT

_einittext = .;

__proc_info_begin = .;

*(.proc.info.init)

__proc_info_end = .;

__arch_info_begin = .;

*(.arch.info.init)

__arch_info_end = .;

__tagtable_begin = .;

*(.taglist.init)

__tagtable_end = .;

………………………………

}

所有CPU类型对应的被初始化的 proc_info_list结构体都放在 __proc_info_begin和__proc_info_end之间。

/ *

* r9 = cpuid

*  Returns:

* r5 = proc_info pointer in physical address space

* r9 = cpuid (preserved)

*/

__lookup_processor_type:

adr r3, 3f                     @r3存储的是标号 3 的物理地址(由于没有启用 mmu ,所以当前肯定是物理地址)

ldmia r3, {r5 - r7}              @ R5=__proc_info_begin,r6=__proc_info_end,r7=标号4处的虚拟地址,即4: .long . 处的地址

add r3, r3, #8                 @ 得到4处的物理地址,刚好是跳过两条指令

sub r3, r3, r7       @ get offset between virt&phys得到虚拟地址和物理地址之间的offset

/*利用offset ,将 r5 和 r6 中保存的虚拟地址转变为物理地址*/

add r5, r5, r3 @ convert virt addresses to

add r6, r6, r3 @ physical address space

1: ldmia r5, {r3, r4} @ value, mask  r3= cpu_val , r4= cpu_mask

and r4, r4, r9 @ mask wanted bits;r9 中存放的是先前读出的 processor ID ,此处屏蔽不需要的位

teq r3, r4                      @ 查看代码和CPU 硬件是否匹配( 比如想在arm920t上运行为cortex-a8编译的内核?不让)

beq 2f                          @ 如果相等则跳转到标号2处,执行返回指令

add r5, r5, #PROC_INFO_SZ @ sizeof(proc_info_list结构的长度,在这等于48)如果没找到, 跳到下一个proc_info_list 处

cmp r5, r6                             @ 判断是不是到了该段的结尾

blo 1b                                 @ 如果没有,继续跳到标号1处,查找下一个

mov r5, #0        @ unknown processor ,如果到了结尾,没找到匹配的,就把0赋值给r5,然后返回

2: mov pc, lr                             @ 找到后返回,r5指向找到的结构体

ENDPROC(__lookup_processor_type)

.align 2

3: .long __proc_info_begin

.long __proc_info_end

4: .long .                                  @“.”表示当前这行代码编译连接后的虚拟地址

.long __arch_info_begin

.long __arch_info_end

/**********************************************************************/

__lookup_processor_type函数的具体解析结束(\arch\arm\kernel\ head-common.S)

/**********************************************************************/

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

beq __error_p @ yes, error 'p'

/*机器 ID是由u-boot引导内核是通过thekernel第二个参数传递进来的,现在保存在r1中,在__arch_info_begin开始的段中进行查找,如果找到,则返回machine对应相关结构体在物理地址空间的首地址到r5,最后保存在r8中。

bl __lookup_machine_type @ r5=machinfo

/**********************************************************************/

__lookup_machine_type函数的具体解析开始(\arch\arm\kernel\ head-common.S)

/**********************************************************************/

每一个CPU 平台都可能有其不一样的结构体,描述这个平台的结构体是 machine_desc 。

这个结构体在文件arch/arm/include/asm/mach/arch.h 中定义:

struct machine_desc {

unsigned int nr;          /* architecture number */

unsigned int phys_io; /* start of physical io */

………………………………

};

对于平台smdk2410 来说其对应 machine_desc 结构在文件linux/arch/arm/mach-s3c2410/mach-smdk2410.c中初始化:

MACHINE_START(SMDK2410, "SMDK2410")

.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 = smdk2410_init,

.timer = &s3c24xx_timer,

MACHINE_END

对于宏MACHINE_START 在文件 arch/arm/include/asm/mach/arch.h 中定义:

#define MACHINE_START(_type,_name) /

static const struct machine_desc __mach_desc_##_type /

__used /

__attribute__((__section__(".arch.info.init"))) = { /

.nr = MACH_TYPE_##_type, /

.name = _name,

#define MACHINE_END /

};

__attribute__((__section__(".arch.info.init")))表明该结构体在并以后存放的位置。

在链接文件 链接脚本文件 arch/arm/kernel/vmlinux.lds 中

SECTIONS

{

#ifdef CONFIG_XIP_KERNEL

. = XIP_VIRT_ADDR(CONFIG_XIP_PHYS_ADDR);

#else

. = PAGE_OFFSET + TEXT_OFFSET;

#endif

.text.head : {

_stext = .;

_sinittext = .;

*(.text.head)

}

.init : { /* Init code and data */

INIT_TEXT

_einittext = .;

__proc_info_begin = .;

*(.proc.info.init)

__proc_info_end = .;

__arch_info_begin = .;

*(.arch.info.init)

__arch_info_end = .;

………………………………

}

在__arch_info_begin和 __arch_info_end之间存放了linux内核所支持的所有平台对应的 machine_desc 结构体。

/*

*  r1 = machine architecture number

* Returns:

*  r5 = mach_info pointer in physical address space

*/

__lookup_machine_type:

adr r3, 4b                      @ 把标号4处的地址放到r3寄存器里面

ldmia r3, {r4, r5, r6}            @ R 4 = 标号4处的虚拟地址 ,r 5 = __arch_info_begin ,r 6= __arch_info_end

sub r3, r3, r4 @ get offset between virt&phys 计算出虚拟地址与物理地址的偏移

/*利用offset ,将 r5 和 r6 中保存的虚拟地址转变为物理地址*/

add r5, r5, r3 @ convert virt addresses to

add r6, r6, r3 @ physical address space

/*读取machine_desc结构的 nr 参数,对于smdk2410 来说该值是 MACH_TYPE_SMDK2410,这个值在文件linux/arch/arm/tools/mach-types 中:

smdk2410    ARCH_SMDK2410 SMDK2410  193 */

1: ldr r3, [r5, #MACHINFO_TYPE] @ get machine type

teq r3, r1 @ matches loader number?把取到的machine id和从uboot中传过来的machine id(存放r1中)相比较

beq 2f @ found 如果相等,则跳到标号2处,返回

add r5, r5, #SIZEOF_MACHINE_DESC@ next machine_desc 没有找到,则继续找下一个,加上该结构体的长度

cmp r5, r6                      @ 判断是否已经到该段的末尾

blo 1b                          @ 如果没有,则跳转到标号1处,继续查找

mov r5, #0 @ unknown machine 如果已经到末尾,并且没找到,则返回值r5寄存器赋值为0

2: mov pc, lr                      @ 返回原函数,且r5作为返回值

ENDPROC(__lookup_machine_type)

.align 2

3: .long __proc_info_begin

.long __proc_info_end

4: .long .                                  @“.”表示当前这行代码编译连接后的虚拟地址

.long __arch_info_begin

.long __arch_info_end

/**********************************************************************/

__lookup_machine_type函数的具体解析结束(\arch\arm\kernel\ head-common.S)

/**********************************************************************/

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

beq __error_a @ yes, error 'a'

/*检查 bootloader传入的参数列表 atags 的 合法性*/

bl __vet_atags

/**********************************************************************/

__vet_atags函数的具体解析开始(\arch\arm\kernel\ head-common.S)

/**********************************************************************/

关于参数链表:

内核参数链表的格式和说明可以从内核源代码目录树中的\arch\arm\include\asm\setup.h中找到,参数链表必须以ATAG_CORE开始,以ATAG_NONE结束。这里的 ATAG_CORE,ATAG_NONE是各个参数的标记,本身是一个32 位值,例如: ATAG_CORE=0x54410001 。 其它的参数标记还包括: ATAG_MEM32  ,  ATAG_INITRD  ,  ATAG_RAMDISK  , ATAG_COMDLINE 等。每个参数标记就代表一个参数结构体,由各个参数结构体构成了参数链表。参数结构体的定义如下:

struct tag { 
      struct  tag_header  hdr; 
      union { 
             struct tag_core  core; 
             struct tag_mem32  mem; 
          struct tag_videotext videotext; 
          struct tag_ramdisk   ramdisk; 
          struct tag_initrd    initrd; 
          struct tag_serialnr  serialnr; 
          struct tag_revision  revision; 
          struct tag_videolfb  videolfb; 
          struct tag_cmdline   cmdline; 
          struct tag_acorn     acorn; 
          struct tag_memclk    memclk; 
        } u; 
};

参数结构体包括两个部分,一个是 tag_header 结构体 , 一个是 u 联合体。

tag_header结构体的定义如下:

struct tag_header {

u32 size;

u32 tag;

};

其中 size :表示整个  tag  结构体的大小 ( 用字的个数来表示,而不是字节的个数 ) ,等于tag_header的大小加上  u 联合体的大小,例如,参数结构体  ATAG_CORE  的size=(sizeof(tag->tag_header)+sizeof(tag->u.core))>>2,一般通过函数  tag_size(struct * tag_xxx) 来获得每个参数结构体的 size 。其中  tag :表示整个  tag  结构体的标记,如: ATAG_CORE 等。

/* r8  = machinfo

* Returns:

*  r2 either valid atags pointer, or zero

*/

__vet_atags:

tst r2, #0x3 @ aligned? r2指向该参数链表的起始位置,此处判断它是否字对齐

bne 1f                          @ 如果没有对齐,跳到标号1处直接返回,并且把r2的值赋值为0,作为返回值

ldr r5, [r2, #0] @ is first tag ATAG_CORE? 获取第一个 tag 结构的 size

cmp r5, #ATAG_CORE_SIZE         @ 判断该 tag 的长度是否合法

cmpne r5, #ATAG_CORE_SIZE_EMPTY

bne 1f                          @ 如果不合法,异常返回

ldr r5, [r2, #4]                @ 获取第一个 tag 结构体的标记

ldr r6, =ATAG_CORE              @ 取出标记ATAG_CORE的内容

cmp r5, r6                      @ 判断该标记是否等于ATAG_CORE

bne 1f                          @ 如果不等,异常返回

mov pc, lr @ atag pointer is ok,如果都相等,则正常返回

1: mov r2, #0                      @ 异常返回值

mov pc, lr @ 异常返回

ENDPROC(__vet_atags)

/**********************************************************************/

__vet_atags函数的具体解析结束(\arch\arm\kernel\ head-common.S)

/**********************************************************************/

/*创建内核初始化页表*/

bl __create_page_tables

/**********************************************************************/

__create_page_tables函数的具体解析开始(\arch\arm\kernel\ head.S)

/**********************************************************************/

/*

* r8  = machinfo

* r9  = cpuid

* r10 = procinfo

* Returns:

*  r4 = physical page table address

*/

/*在该文件的开头有如下宏定义*/

#define KERNEL_RAM_PADDR (PHYS_OFFSET + TEXT_OFFSET)

.macro pgtbl, rd

ldr\rd, =(KERNEL_RAM_PADDR - 0x4000)

.endm

其中:PHYS_OFFSET在arch/arm/mach-s3c2410/include/mach/memory.h定义,为UL(0x30000000),而TEXT_OFFSET在arch/arm/Makefile中定义,为内核镜像在内存中到内存开始位置的偏移(字节),为$(textofs-y) textofs-y也在文件arch/arm/Makefile中定义,为textofs-y   := 0x00008000,r4 = 30004000为临时页表的起始地址,首先即是初始化16K的页表,高12位虚拟地址为页表索引,每个页表索引占4个字节,所以为4K*4 = 16K,大页表,每一个页表项,映射1MB虚拟地址.

__create_page_tables:

/*为内核代码存储区域创建页表,首先将内核起始地址-0x4000到内核起始地址之间的16K存储器清0 ,将创建的页表存于此处*/

pgtbl r4 @ r4中存放的为页表的基地址,最终该地址会写入cp15的寄存器c2,这个值必须是 16K 对齐的

mov r0, r4                      @ 把页表的基地址存放到r0中

mov r3, #0                      @ 把r3清0

add r6, r0, #0x4000             @ r6指向16K的末尾

1: str r3, [r0], #4                @ 把16K的页表空间清0

str r3, [r0], #4

str r3, [r0], #4

str r3, [r0], #4

teq r0, r6

bne 1b

/*从proc_info_list结构中获取字段 __cpu_mm_mmu_flags ,该字段包含了存储空间访问权限等, 此处指令执行之后r7=0x00000c1e*/

ldr r7, [r10, #PROCINFO_MM_MMUFLAGS] @ mm_mmuflags

/*为内核的第一MB创建一致的映射,以为打开MMU做准备,这个映射将会被paging_init()移除,这里使用程序计数器来获得相应的段的基地址*/

mov r6, pc

mov r6, r6, lsr #20 @ start of kernel section

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

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

/* MMU是通过 C2 中基地址(高 18 位)与虚拟地址的高 12 位组合成物理地址,在转换表中查找地址条目。 R4 中存放的就是这个基地址 0x30004000*/

add r0, r4,  #(KERNEL_START & 0xff000000) >> 18   @ r0 = 0x30007000 r0存放的是转换表的起始位置

str r3, [r0, #(KERNEL_START & 0x00f00000) >> 18]! @ r3存放的是内核镜像代码段的起始地址

ldr r6, =(KERNEL_END - 1)                         @ 获取内核的尾部虚拟地址存于r6中

add r0, r0, #4                                    @ 第一个地址条目存放在 0x30007004 处,以后依次递增

add r6, r4, r6, lsr #18                           @ 计算最后一个地址条目存放的位置

1: cmp r0, r6                                        @ 填充这之间的地址条目

/*每一个地址条目代表了 1MB 空间的地址映射。物理地址将从0x30100000开始映射。0X30000000 开始的 1MB 空间将在下面映射*/

add r3, r3, #1 << 20

strls r3, [r0], #4

bls 1b

…………………………………

…………………………………………

/*为了使用启动参数,将物理内存的第一MB映射到内核虚拟地址空间的第一个MB,r4存放的是页表的地址。映射0X30000000开始的 1MB 空间PAGE_OFFSET = 0XC0000000,PHYS_OFFSET = 0X30000000, r0 =  0x30007000, 上面是从 0x30007004开始存放地址条目的*/

add r0, r4, #PAGE_OFFSET >> 18

orr r6, r7, #(PHYS_OFFSET & 0xff000000)  @ r6= 0x30000c1e

.if (PHYS_OFFSET & 0x00f00000)

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

.endif

str r6, [r0]                            @ 将0x30000c1e 存于0x30007000处。

………………………

………………………………

mov pc, lr                              @子程序返回

ENDPROC(__create_page_tables)

/**********************************************************************/

__create_page_tables函数的具体解析结束(\arch\arm\kernel\ head.S)

/**********************************************************************/

/*把__switch_data标号处的地址放入r13寄存器,当执行完__enable_mmu函数时会把r13寄存器的值赋值给pc,跳转到__switch_data 处执行*/

ldr r13, __switch_data @ address to jump to after mmu has been enabled

/*把__enable_mmu函数的地址值,赋值给lr寄存器,当执行完__arm920_setup时,返回后执行__enable_mmu */

adr lr, BSYM(__enable_mmu) @ return (PIC) address

/**********************************************************************/

__enable_mmu函数的具体解析开始(\arch\arm\kernel\ head.S)

/**********************************************************************/

__enable_mmu:

#ifdef CONFIG_ALIGNMENT_TRAP

orr r0, r0, #CR_A   //使能地址对齐错误检测

#else

bic r0, r0, #CR_A

#endif

#ifdef CONFIG_CPU_DCACHE_DISABLE

bic r0, r0, #CR_C   //禁止数据 cache

#endif

#ifdef CONFIG_CPU_BPREDICT_DISABLE

bic r0, r0, #CR_Z

#endif

#ifdef CONFIG_CPU_ICACHE_DISABLE

bic r0, r0, #CR_I  //禁止指令 cache

#endif             //配置相应的访问权限并存入 r5 中

mov r5, #(domain_val(DOMAIN_USER, DOMAIN_MANAGER) | /

domain_val(DOMAIN_KERNEL, DOMAIN_MANAGER) | /

domain_val(DOMAIN_TABLE, DOMAIN_MANAGER) | /

domain_val(DOMAIN_IO, DOMAIN_CLIENT))

mcr p15, 0, r5, c3, c0, 0 //将访问权限写入协处理器

mcr p15, 0, r4, c2, c0, 0 //将页表基地址写入基址寄存器 C2 , 0X30004000

b __turn_mmu_on          //跳转到程序段去打开 MMU

ENDPROC(__enable_mmu)

文件linux/arch/arm/kernel/head.S 中

__turn_mmu_on:

mov r0, r0

mcr p15, 0, r0, c1, c0, 0 //打开 MMU 同时打开 cache 等。

mrc p15, 0, r3, c0, c0, 0 @ read id reg 读取 id 寄存器

mov r3, r3

mov r3, r3    //两个空操作,等待前面所取的指令得以执行。

mov pc, r13  //程序跳转

ENDPROC(__turn_mmu_on)

/**********************************************************************/

__enable_mmu函数的具体解析结束(\arch\arm\kernel\ head.S)

/**********************************************************************/

/*执行__arm920_setup函数(\arch\arm\mm\ proc-arm920.S),该函数完成对数据cache,指令cache,write buffer等初始化操作*/

ARM( add pc, r10, #PROCINFO_INITFUNC )

/**********************************************************************/

__arm920_setup函数的具体解析开始(\arch\arm\mm\ proc-arm920.S)

/**********************************************************************/

在上面程序段.section ".text.head", "ax" 的最后有这样几行:

add pc, r10, #PROCINFO_INITFUNC

R10中存放的是在函数 __lookup_processor_type 中成功匹配的结构体 proc_info_list。对于arm920 来说在文件 linux/arch/arm/mm/proc-arm920.S 中有:

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

.type  __arm920_proc_info,#object

__arm920_proc_info:

.long 0x41009200

.long 0xff00fff0

.long   PMD_TYPE_SECT | /

PMD_SECT_BUFFERABLE | /

PMD_SECT_CACHEABLE | /

PMD_BIT4 | /

PMD_SECT_AP_WRITE | /

PMD_SECT_AP_READ

.long   PMD_TYPE_SECT | /

PMD_BIT4 | /

PMD_SECT_AP_WRITE | /

PMD_SECT_AP_READ

b __arm920_setup

………………………………

add pc, r10, #PROCINFO_INITFUNC的意思跳到函数 __arm920_setup去执行。

.type __arm920_setup, #function  //表明这是一个函数

__arm920_setup:

mov r0, #0                      //设置 r0 为 0 。

mcr p15, 0, r0, c7, c7          //使数据 cahche,  指令 cache 无效。

mcr p15, 0, r0, c7, c10, 4      //使 write buffer 无效。

#ifdef CONFIG_MMU

mcr p15, 0, r0, c8, c7          //使数据 TLB, 指令 TLB 无效。

#endif

adr r5, arm920_crval            //获取 arm920_crval 的地址,并存入 r5 。

ldmia r5, {r5, r6}              //获取 arm920_crval 地址处的连续 8 字节分别存入 r5,r6 。

mrc p15, 0, r0, c1, c0          //获取 CP15 下控制寄存器的值,并存入 r0 。

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

orr r0, r0, r6                  //设置 r0 中的相关位,即为 mmu 做相应设置。

mov pc, lr                      //上面有操作 adr lr, __enable_mmu ,此处将跳到程序段 __enable_mmu 处。

.size __arm920_setup, . - __arm920_setup

.type arm920_crval, #object

arm920_crval:

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

/**********************************************************************/

__arm920_setup函数的具体解析结束(\arch\arm\mm\ proc-arm920.S)

/**********************************************************************/

ENDPROC(stext)

接着往下分析linux/arch/arm/kernel/head-common.S中:

.type __switch_data, %object      @定义__switch_data为一个对象

__switch_data:

.long __mmap_switched

.long __data_loc @ r4

.long _data @ r5

.long __bss_start @ r6

.long _end @ r7

.long processor_id @ r4

.long __machine_arch_type @ r5

.long __atags_pointer @ r6

.long cr_alignment @ r7

.long init_thread_union + THREAD_START_SP @ sp

/*

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

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

*  r0  = cp#15 control register

*  r1  = machine ID

*  r2  = atags pointer

*  r9  = processor ID

*/

/*其中上面的几个段的定义是在文件arch/arm/kernel/vmlinux.lds 中指定*/

********************************** vmlinux.lds开始*******************************************

SECTIONS

{

……………………

#ifdef CONFIG_XIP_KERNEL

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

. = PAGE_OFFSET + TEXT_OFFSET;

#else

. = ALIGN(THREAD_SIZE);

__data_loc = .;

#endif

.data : AT(__data_loc) {  //此处数据存储在上面__data_loc处。

_data = .; /* address in memory */

*(.data.init_task)

…………………………

.bss : {

__bss_start = .; /* BSS */

*(.bss)

*(COMMON)

_end = .;

}

………………………………

}

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

union thread_union init_thread_union __attribute__((__section__(".init.task"))) = { INIT_THREAD_INFO(init_task) };

对照 vmlnux.lds.S 中,我们可以知道init task是存放在 .data 段的开始8k, 并且是THREAD_SIZE(8k)对齐的 */

********************************** vmlinux.lds结束*******************************************

__mmap_switched:

adr r3, __switch_data + 4

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

……………………

………………………………

mov fp, #0 @ 清除bss段

1: cmp r6, r7

strcc fp, [r6],#4

bcc 1b

ARM( ldmia r3, {r4, r5, r6, r7, sp})  /*把__machine_arch_type变量值放入r5中,把__atags_pointer变量的值放入r6中*/

str r9, [r4] @ Save processor ID 保存处理器id到processor_id所在的地址中

str r1, [r5] @ Save machine type 保存machine  id到__machine_arch_type中

str r2, [r6] @ Save atags pointer 保存参数列表首地址到__atags_pointer中

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

stmia r7, {r0, r4} @ Save control register values

b start_kernel                @程序跳转到函数 start_kernel 进入 C 语言部分。

ENDPROC(__mmap_switched)

到处我们的启动的第二阶段分析完毕。

后面会接着分析第三阶段。第三阶段完全是C语言代码,从start_kernel函数开始。

Linux内核启动流程分析(二)【转】的更多相关文章

  1. Linux内核启动代码分析二之开发板相关驱动程序加载分析

    Linux内核启动代码分析二之开发板相关驱动程序加载分析 1 从linux开始启动的函数start_kernel开始分析,该函数位于linux-2.6.22/init/main.c  start_ke ...

  2. Linux内核启动流程分析(一)【转】

    转自:http://blog.chinaunix.net/uid-25909619-id-3380535.html 很久以前分析的,一直在电脑的一个角落,今天发现贴出来和大家分享下.由于是word直接 ...

  3. linux 内核启动流程分析,移植

    分析 linux-2.6.22.6 内核启动流程 移植 linux-3.4.2 到 JZ2440 开发板 Linux内核源码百度云链接: https://pan.baidu.com/s/1m1ymGl ...

  4. 【内核】linux内核启动流程详细分析

    Linux内核启动流程 arch/arm/kernel/head-armv.S 该文件是内核最先执行的一个文件,包括内核入口ENTRY(stext)到start_kernel间的初始化代码, 主要作用 ...

  5. 【内核】linux内核启动流程详细分析【转】

    转自:http://www.cnblogs.com/lcw/p/3337937.html Linux内核启动流程 arch/arm/kernel/head-armv.S 该文件是内核最先执行的一个文件 ...

  6. linux 内核启动流程

    Linux内核启动流程详细分析: http://www.linuxidc.com/Linux/2014-10/108034.htm ARM Linux内核启动过程: http://blog.csdn. ...

  7. Linux内核启动流程(简介)

    1. vmlinux.lds 首先分析 Linux 内核的连接脚本文件 arch/arm/kernel/vmlinux.lds,通过链接脚本可以找到 Linux 内核的第一行程序是从哪里执行的: 第 ...

  8. Linux内核启动流程与模块机制

    本文旨在简单的介绍一下Linux的启动流程与模块机制: Linux启动的C入口位于/Linux.2.6.22.6/init/main.c::start_kernel() 下图简要的描述了一下内核初始化 ...

  9. Tiny4412 Linux 内核启动流程

    Linux内核的启动分为压缩内核和非压缩内核两种,这里我们以压缩内核为例.压缩内核运行时,将运行一段解压缩程序,得到真正的内核镜像,然后跳转到内核镜像运行.此时,Linux进入非压缩内核入口,在非压缩 ...

随机推荐

  1. 派生类&简单工厂模式

    派生类&简单工厂模式 git链接: Operation3.1.1 题目描述的代码部分的解释 首先是声明一个Rand类作为父类,然后两个子类RandNumber类和RandOperation类, ...

  2. PAT----1001. A+B Format (20)解题过程

    1001. A+B Format (20) github链接 时间限制 400 ms 内存限制 65536 kB 代码长度限制 16000 B Calculate a + b and output t ...

  3. ElasticSearch 2 (16) - 深入搜索系列之近似度匹配

    ElasticSearch 2 (16) - 深入搜索系列之近似度匹配 摘要 标准的全文搜索使用TF/IDF处理文档.文档里的每个字段或一袋子词.match 查询可以告诉我们哪个袋子里面包含我们搜索的 ...

  4. Linux命令常用

    数据库:查看日志 vim /var/log/mysqld.log

  5. Alpha冲刺第6天

    Alpha第六天 1.团队成员 郑西坤 031602542 (队长) 陈俊杰 031602504 陈顺兴 031602505 张胜男 031602540 廖钰萍 031602323 雷光游 03160 ...

  6. vue中npm run dev运行项目不能自动打开浏览器! 以及 webstorm跑vue项目jshint一直提示错误问题的解决方法!

    vue中npm run dev运行项目不能自动打开浏览器!以及 webstorm跑vue项目jshint一直提示错误问题的解决方法! 1.上个项目结束就很久没有使用vue了,最近打算用vue搭建自己的 ...

  7. vue.js 添加 fastclick的支持 处理移动端click事件300毫秒延迟

    fastclick:处理移动端click事件300毫秒延迟. 1,先执行安装fastclick的命令 npm install fastclick 2,在main.js中引入,并绑定到body. imp ...

  8. BZOJ4036 HAOI2015按位或(概率期望+容斥原理)

    考虑min-max容斥,改为求位集合内第一次有位变成1的期望时间.求出一次操作选择了S中的任意1的概率P[S],期望时间即为1/P[S]. 考虑怎么求P[S].P[S]=∑p[s] (s&S& ...

  9. Elastic-Job-Lite 源码分析 —— 运维平台

    本文基于 Elastic-Job V2.1.5 版本分享 1. 概述 2. Maven模块 elastic-job-common-restful 3. Maven模块 elastic-job-cons ...

  10. c++函数写的都对,还是说incompatible或者not found的解决办法

    vs2010,c++,定义了一个函数如下,在BianHuanYuDib.h文件中: 在BianHuanYuDib.cpp中: 写的完全正确,但还是会报错: 很明显,连std都报错了,一般不是真的有很大 ...