linux内核启动第一个进程,Linux内核启动流程分析(二)

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 = machineid.

所有的机器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。此时内存的布局如下图所示

66b0ec6bdaf0b24e739769b907e08e9e.png

在开发板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"段内*/

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

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

setmodePSR_F_BIT | PSR_I_BIT | SVC_MODE, r9@ ensure svc modeand irqs disabled进入超级权限模式,关中断

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

mrcp15, 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 intcpu_val;

unsigned intcpu_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 intelf_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:/*为该结构体赋值*/

.long0x41009200

.long0xff00fff0

.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:

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

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

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

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

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

addr5, r5, r3@ convert virt addresses to

addr6, r6, r3@ physical address space

1:ldmiar5, {r3, r4}@ value, maskr3=cpu_val,r4=cpu_mask

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

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

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

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

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

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

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

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

ENDPROC(__lookup_processor_type)

.align2

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)

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

movsr10, 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 intnr;/* architecture number*/

unsigned intphys_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:

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

ldmiar3, {r4, r5, r6}@R4=标号4处的虚拟地址,r5=__arch_info_begin,r6=__arch_info_end

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

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

addr5, r5, r3@ convert virt addresses to

addr6, r6, r3@ physical address space

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

smdk2410ARCH_SMDK2410SMDK2410193*/

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

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

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

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

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

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

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

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

ENDPROC(__lookup_machine_type)

.align2

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)

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

movsr8, 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:

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

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

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

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

cmpner5, #ATAG_CORE_SIZE_EMPTY

bne1f@ 如果不合法,异常返回

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

ldrr6, =ATAG_CORE@ 取出标记ATAG_CORE的内容

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

bne1f@ 如果不等,异常返回

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

1:movr2, #0@ 异常返回值

movpc, 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)

.macropgtbl, 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,将创建的页表存于此处*/

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

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

movr3, #0@ 把r3清0

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

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

strr3, [r0], #4

strr3, [r0], #4

strr3, [r0], #4

teqr0, r6

bne1b

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

ldrr7, [r10, #PROCINFO_MM_MMUFLAGS] @ mm_mmuflags

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

movr6, pc

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

orrr3, r7, r6, lsl #20@ flags + kernel base

strr3, [r4, r6, lsl #2]@ identity mapping

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

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

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

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

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

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

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

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

addr3, r3, #1 <

strlsr3, [r0], #4

bls1b

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

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

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

addr0, r4, #PAGE_OFFSET >> 18

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

.if(PHYS_OFFSET & 0x00f00000)

orrr6, r6, #(PHYS_OFFSET & 0x00f00000)

.endif

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

………………………

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

movpc, lr@子程序返回

ENDPROC(__create_page_tables)

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

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

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

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

ldrr13, __switch_data@ address to jump to aftermmu has been enabled

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

adrlr, BSYM(__enable_mmu)@ return (PIC) address

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

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

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

__enable_mmu:

#ifdef CONFIG_ALIGNMENT_TRAP

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

#else

bicr0, r0, #CR_A

#endif

#ifdef CONFIG_CPU_DCACHE_DISABLE

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

#endif

#ifdef CONFIG_CPU_BPREDICT_DISABLE

bicr0, r0, #CR_Z

#endif

#ifdef CONFIG_CPU_ICACHE_DISABLE

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

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

movr5, #(domain_val(DOMAIN_USER, DOMAIN_MANAGER) | /

domain_val(DOMAIN_KERNEL, DOMAIN_MANAGER) | /

domain_val(DOMAIN_TABLE, DOMAIN_MANAGER) | /

domain_val(DOMAIN_IO, DOMAIN_CLIENT))

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

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

b__turn_mmu_on          //跳转到程序段去打开MMU

ENDPROC(__enable_mmu)

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

__turn_mmu_on:

movr0, r0

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

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

movr3, r3

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

movpc, 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(addpc, r10, #PROCINFO_INITFUNC)

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

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

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

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

addpc, 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:

.long0x41009200

.long0xff00fff0

.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

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

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

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

__arm920_setup:

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

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

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

#ifdef CONFIG_MMU

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

#endif

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

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

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

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

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

movpc, lr                      //上面有操作 adrlr, __enable_mmu ,此处将跳到程序段__enable_mmu处。

.size__arm920_setup, . - __arm920_setup

.typearm920_crval, #object

arm920_crval:

crvalclear=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

.longprocessor_id@ r4

.long__machine_arch_type@ r5

.long__atags_pointer@ r6

.longcr_alignment@ r7

.longinit_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:

adrr3, __switch_data + 4

ldmiar3!, {r4, r5, r6, r7}

……………………

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

movfp, #0@清除bss段

1:cmpr6, r7

strccfp, [r6],#4

bcc1b

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

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

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

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

bicr4, r0, #CR_A@ Clear 'A' bit

stmiar7, {r0, r4}@ Save control register values

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

ENDPROC(__mmap_switched)

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

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
完整版:https://download.csdn.net/download/qq_27595745/89522468 【课程大纲】 1-1 什么是java 1-2 认识java语言 1-3 java平台的体系结构 1-4 java SE环境安装和配置 2-1 java程序简介 2-2 计算机中的程序 2-3 java程序 2-4 java类库组织结构和文档 2-5 java虚拟机简介 2-6 java的垃圾回收器 2-7 java上机练习 3-1 java语言基础入门 3-2 数据的分类 3-3 标识符、关键字和常量 3-4 运算符 3-5 表达式 3-6 顺序结构和选择结构 3-7 循环语句 3-8 跳转语句 3-9 MyEclipse工具介绍 3-10 java基础知识章节练习 4-1 一维数组 4-2 数组应用 4-3 多维数组 4-4 排序算法 4-5 增强for循环 4-6 数组和排序算法章节练习 5-0 抽象和封装 5-1 面向过程的设计思想 5-2 面向对象的设计思想 5-3 抽象 5-4 封装 5-5 属性 5-6 方法的定义 5-7 this关键字 5-8 javaBean 5-9 包 package 5-10 抽象和封装章节练习 6-0 继承和多态 6-1 继承 6-2 object类 6-3 多态 6-4 访问修饰符 6-5 static修饰符 6-6 final修饰符 6-7 abstract修饰符 6-8 接口 6-9 继承和多态 章节练习 7-1 面向对象的分析与设计简介 7-2 对象模型建立 7-3 类之间的关系 7-4 软件的可维护与复用设计原则 7-5 面向对象的设计与分析 章节练习 8-1 内部类与包装器 8-2 对象包装器 8-3 装箱和拆箱 8-4 练习题 9-1 常用类介绍 9-2 StringBuffer和String Builder类 9-3 Rintime类的使用 9-4 日期类简介 9-5 java程序国际化的实现 9-6 Random类和Math类 9-7 枚举 9-8 练习题 10-1 java异常处理 10-2 认识异常 10-3 使用try和catch捕获异常 10-4 使用throw和throws引发异常 10-5 finally关键字 10-6 getMessage和printStackTrace方法 10-7 异常分类 10-8 自定义异常类 10-9 练习题 11-1 Java集合框架和泛型机制 11-2 Collection接口 11-3 Set接口实现类 11-4 List接口实现类 11-5 Map接口 11-6 Collections类 11-7 泛型概述 11-8 练习题 12-1 多线程 12-2 线程的生命周期 12-3 线程的调度和优先级 12-4 线程的同步 12-5 集合类的同步问题 12-6 用Timer类调度任务 12-7 练习题 13-1 Java IO 13-2 Java IO原理 13-3 流类的结构 13-4 文件流 13-5 缓冲流 13-6 转换流 13-7 数据流 13-8 打印流 13-9 对象流 13-10 随机存取文件流 13-11 zip文件流 13-12 练习题 14-1 图形用户界面设计 14-2 事件处理机制 14-3 AWT常用组件 14-4 swing简介 14-5 可视化开发swing组件 14-6 声音的播放和处理 14-7 2D图形的绘制 14-8 练习题 15-1 反射 15-2 使用Java反射机制 15-3 反射与动态代理 15-4 练习题 16-1 Java标注 16-2 JDK内置的基本标注类型 16-3 自定义标注类型 16-4 对标注进行标注 16-5 利用反射获取标注信息 16-6 练习题 17-1 顶目实战1-单机版五子棋游戏 17-2 总体设计 17-3 代码实现 17-4 程序的运行与发布 17-5 手动生成可执行JAR文件 17-6 练习题 18-1 Java数据库编程 18-2 JDBC类和接口 18-3 JDBC操作SQL 18-4 JDBC基本示例 18-5 JDBC应用示例 18-6 练习题 19-1 。。。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值