linux内核启动过程分析(二)

linux/arch/arm/kernel/head.S是linux内核映像解压后执行的第一个文件。

//PAGE_OFFSET=0xc0000000;TEXT_OFFSET=0x00008000;

//PHYS_OFFSET=0x30000000;

#defineKERNEL_RAM_VADDR(PAGE_OFFSET+TEXT_OFFSET)

#defineKERNEL_RAM_PADDR(PHYS_OFFSET+TEXT_OFFSET)

/*

链接脚本文件arch/arm/kernel/vmlinux.lds指定了编译时程序段存放的位置。

divS

{

#ifdefCONFIG_XIP_KERNEL

.=XIP_VIRT_ADDR(CONFIG_XIP_PHYS_ADDR);

#else

.=PAGE_OFFSET+TEXT_OFFSET;

#endif

.text.head:{

_stext=.;

_sinittext=.;

*(.text.head)

}

.init:{

……………………

}

*/

.div".text.head","ax"

ENTRY(stext)

msrcpsr_c,#PSR_F_BIT|PSR_I_BIT|SVC_MODE//禁止FIQ、IRQ,设定SVC模式

mrcp15,0,r9,c0,c0@获取processorid

/*判断CPU类型,查找运行的CPUID值与Linux编译支持的ID值是否支持*/

bl__lookup_processor_type@r5=procinfor9=cpuid

movsr10,r5@invalidprocessor(r5=0)?

///*判断如果r10的值为0,跳转到出错处理,*/

beq__error_p@yes,error’p’

//查询machineID并检查合法性

bl__lookup_machine_type@r5=machinfo

movsr8,r5@invalidmachine(r5=0)?

beq__error_a@yes,error’a’

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

bl__create_page_tables//创建初始页表

ldrr13,__switch_data//将列表__switch_data存到r13中后面会跳到该列表出

adrlr,__enable_mmu//将程序段__enable_mmu的地址存到lr中。

//此命令将导致程序段__arm920_setup的执行,后面会将到。

//r10中存放的基地址是从__lookup_processor_type中得到的,如上面movsr10,r5

addpc,r10,#PROCINFO_INITFUNC

ENDPROC(stext)

接下来将对上面遇到的几个程序段展开分析。

__lookup_processor_type

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

在讲解该程序段之前先来看一些相关知识。

内核做支持的每一种CPU类型都由结构体proc_info_list来描述。

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

structproc_info_list{

unsignedintcpu_val;

unsignedintcpu_mask;

unsignedlong__cpu_mm_mmu_flags;/*usedbyhead.S*/

unsignedlong__cpu_io_mmu_flags;/*usedbyhead.S*/

unsignedlong__cpu_flush;/*usedbyhead.S*/

constchar*arch_name;

constchar*elf_name;

unsignedintelf_hwcap;

constchar*cpu_name;

structprocessor*proc;

structcpu_tlb_fns*tlb;

structcpu_user_fns*user;

structcpu_cache_fns*cache;

};

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

初始化。

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

.type__arm920_proc_info,#object

__arm920_proc_info:

.long0x41009200

.long0xff00fff0

.longPMD_TYPE_SECT|\

PMD_SECT_BUFFERABLE|\

PMD_SECT_CACHEABLE|\

PMD_BIT4|\

PMD_SECT_AP_WRITE|\

PMD_SECT_AP_READ

.longPMD_TYPE_SECT|\

PMD_BIT4|\

PMD_SECT_AP_WRITE|\

PMD_SECT_AP_READ

b__arm920_setup

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

.div".proc.info.init"表明了该结构在编译后存放的位置。

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

divS

{

#ifdefCONFIG_XIP_KERNEL

.=XIP_VIRT_ADDR(CONFIG_XIP_PHYS_ADDR);

#else

.=PAGE_OFFSET+TEXT_OFFSET;

#endif

.text.head:{

_stext=.;

_sinittext=.;

*(.text.head)

}

.init:{/*Initcodeanddata*/

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之间。

__lookup_processor_type:

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

adrr3,3f

//R5=__proc_info_begin,r6=__proc_info_end,r7=标号3处的虚拟地址。

ldmdar3,{r5-r7}

subr3,r3,r7//得到虚拟地址和物理地址之间的offset

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

addr6,r6,r3@physicaladdressspace

1:ldmiar5,{r3,r4}//r3=cpu_val,r4=cpu_mask,

andr4,r4,r9//r9中存放的是先前读出的processorID,此处屏蔽不需要的位。

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

teqr3,r4

beq2f//如果匹配成功就返回

//PROC_INFO_SZ(proc_info_list结构的长度,在这等于48),跳到下一个proc_info_list处

addr5,r5,#PROC_INFO_SZ

//判断是否已经到了结构体proc_info_list存放区域的末尾__proc_info_end,

cmpr5,r6

blo1b

//如果没有匹配成功就将r5清零,如果匹配成功r5中放的是该CPU类型对应的结构体//proc_info_list的基地址。

movr5,#0

2:movpc,lr//子程序返回。

ENDPROC(__lookup_processor_type)

.long__proc_info_begin

.long__proc_info_end

3:.long.

.long__arch_info_begin

.long__arch_info_end

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

__lookup_machine_type

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

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

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

structmachine_desc{

unsignedintnr;/*architecturenumber*/

unsignedintphys_io;/*startofphysicalio*/

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

};

对于平台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中定义:

#defineMACHINE_START(_type,_name)\

staticconststructmachine_desc__mach_desc_##_type\

__used\

__attribute__((__div__(".arch.info.init")))={\

.nr=MACH_TYPE_##_type,\

.name=_name,

#defineMACHINE_END\

};

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

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

divS

{

#ifdefCONFIG_XIP_KERNEL

.=XIP_VIRT_ADDR(CONFIG_XIP_PHYS_ADDR);

#else

.=PAGE_OFFSET+TEXT_OFFSET;

#endif

.text.head:{

_stext=.;

_sinittext=.;

*(.text.head)

}

.init:{/*Initcodeanddata*/

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结构体。

.long__proc_info_begin

.long__proc_info_end

3:.long.

.long__arch_info_begin

.long__arch_info_end

__lookup_machine_type:

adrr3,3b////r3存储的是标号3的物理地址

////R4=标号3处的虚拟地址,r5=__arch_info_begin,r6=__arch_info_end。

ldmiar3,{r4,r5,r6}

subr3,r3,r4@getoffsetbetweenvirt&phys

addr5,r5,r3@convertvirtaddressesto

addr6,r6,r3@physicaladdressspace

//读取machine_desc结构的nr参数,对于smdk2410来说该值是MACH_TYPE_SMDK2410。

//这个值在文件linux/arch/arm/tools/mach-types中://smdk2410ARCH_SMDK2410SMDK2410193

1:ldrr3,[r5,#MACHINFO_TYPE]@getmachinetype

teqr3,r1//r1就是bootloader传递过来的机器码,就是上面的平台编号。

beq2f//如果匹配成功就返回

addr5,r5,#SIZEOF_MACHINE_DESC//没匹配成功就跳到下一个结构体machine_desc

cmpr5,r6

blo1b

movr5,#0//如果匹配失败就将r5清零。

2:movpc,lr//子程序返回。

ENDPROC(__lookup_machine_type)

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

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

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

关于参数链表:

内核参数链表的格式和说明可以从内核源代码目录树中的include/asm-arm/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结构体的定义如下:

structtag_header{

u32size;

u32tag;

};

其中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

等。

__vet_atags:

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

bne1f

ldrr5,[r2,#0]//获取第一个tag结构的size

//#defineATAG_CORE_SIZE((2*4+3*4)>>2)判断该tag的长度是否合法

subsr5,r5,#ATAG_CORE_SIZE

bne1f

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

ldrr6,=ATAG_CORE

cmpr5,r6//判断第一个tag结构体的标记是不是ATAG_CORE

bne1f

movpc,lr//正常退出

1:movr2,#0

movpc,lr//参数连表不正确

ENDPROC(__vet_atags)

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

__create_page_tables

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

.macropgtbl,rd

ldr\rd,=(KERNEL_RAM_PADDR-0x4000)

.endm

__create_page_tables:

//r4=0x30004000这是转换表的物理基地址,最终将写入CP15的寄存器2,C2。

//这个值必须是16K对齐的。

pgtblr4

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

//存储器清0,将创建的页表存于此处。

movr0,r4

movr3,#0

addr6,r0,#0x4000

1:strr3,[r0],#4

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

/*

此处建立一个物理地址到物理地址的平板映射,这个映射将在函数paging_init().被清除。

r6=0x300r3=0x30000c1e[0x30004c00]=0x30000c1e

*/

movr6,pc,lsr#20@startofkerneldiv

orrr3,r7,r6,lsl#20@flags+kernelbase

strr3,[r4,r6,lsl#2]//字对齐

/*

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

KERNEL_START的高12位。KERNEL_START是内核存放的起始地址,为0X30008000。

*/

addr0,r4,#(KERNEL_START&0xff000000)>>18//r0=0x30007000

strr3,[r0,#(KERNEL_START&0x00f00000)>>18]!//r0存放的是转换表的起始位置

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

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

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

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

addr3,r3,#1<<20//每一个地址条目代表了1MB空间的地址映射。物理地址将从

//0x30100000开始映射。0X30000000开始的1MB空间将在下面映射。

strlsr3,[r0],#4

bls1b

#ifdefCONFIG_XIP_KERNEL

//如果是XIP就进行以下映射,这只是将内核代码存储的空间重新映射,

orrr3,r7,#(KERNEL_RAM_PADDR&0xff000000)

.if(KERNEL_RAM_PADDR&0x00f00000)

orrr3,r3,#(KERNEL_RAM_PADDR&0x00f00000)

.endif

addr0,r4,#(KERNEL_RAM_VADDR&0xff000000)>>18

strr3,[r0,#(KERNEL_RAM_VADDR&0x00f00000)>>18]!

ldrr6,=(_end-1)

addr0,r0,#4

addr6,r4,r6,lsr#18

1:cmpr0,r6

addr3,r3,#1<<20

strlsr3,[r0],#4

bls1b

#endif

/*

映射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处。

#ifdefCONFIG_DEBUG_LL//下面是为了调试而做的相关映射,跳过。

ldrr7,[r10,#PROCINFO_IO_MMUFLAGS]@io_mmuflags

/*

*MapinIOspaceforserialdebugging.

*Thisallowsdebugmessagestobeoutput

*viaaserialconsolebeforepaging_init.

*/

ldrr3,[r8,#MACHINFO_PGOFFIO]

addr0,r4,r3

rsbr3,r3,#0x4000@PTRS_PER_PGD*sizeof(long)

cmpr3,#0x0800@limitto512MB

movhir3,#0x0800

addr6,r0,r3

ldrr3,[r8,#MACHINFO_PHYSIO]

orrr3,r3,r7

1:strr3,[r0],#4

addr3,r3,#1<<20

teqr0,r6

bne1b

#ifdefined(CONFIG_ARCH_NETWINDER)||defined(CONFIG_ARCH_CATS)

/*

*Ifwe’reusingtheNetWinderorCATS,wealsoneedtomap

*inthe16550-typeserialportforthedebugmessages

*/

addr0,r4,#0xff000000>>18

orrr3,r7,#0x7c000000

strr3,[r0]

#endif

#ifdefCONFIG_ARCH_RPC

/*

*Mapinscreenat0x02000000&SCREEN2_BASE

*Similarreasonshere-fordebug.Thisis

*onlyforAcornRiscPCarchitectures.

*/

addr0,r4,#0x02000000>>18

orrr3,r7,#0x02000000

strr3,[r0]

addr0,r4,#0xd8000000>>18

strr3,[r0]

#endif

#endif

movpc,lr//子程序返回。

ENDPROC(__create_page_tables)

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

__arm920_setup

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

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

ldrr13,__switch_data@addresstojumptoafter

@mmuhasbeenenabled

adrlr,__enable_mmu@return(PIC)address

addpc,r10,#PROCINFO_INITFUNC

R10中存放的是在函数__lookup_processor_type中成功匹配的结构体proc_info_list。

对于arm920来说在文件linux/arch/arm/mm/proc-arm920.S中有:

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

.type__arm920_proc_info,#object

__arm920_proc_info:

.long0x41009200

.long0xff00fff0

.longPMD_TYPE_SECT|\

PMD_SECT_BUFFERABLE|\

PMD_SECT_CACHEABLE|\

PMD_BIT4|\

PMD_SECT_AP_WRITE|\

PMD_SECT_AP_READ

.longPMD_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//使writebuffer无效。

#ifdefCONFIG_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。

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

bicr0,r0,r5

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

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

__enable_mmu:

#ifdefCONFIG_ALIGNMENT_TRAP

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

#else

bicr0,r0,#CR_A

#endif

#ifdefCONFIG_CPU_DCACHE_DISABLE

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

#endif

#ifdefCONFIG_CPU_BPREDICT_DISABLE

bicr0,r0,#CR_Z

#endif

#ifdefCONFIG_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@readidreg//读取id寄存器

movr3,r3

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

movpc,r13//程序跳转,如下面解释。

ENDPROC(__turn_mmu_on)

在前面有过这样的指令操作ldrr13,__switch_data,

movpc,r13就是将跳转到__switch_data处。

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

.type__switch_data,%object//定义一个对象

__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

/*

__data_loc是数据存放的位置

_data是数据开始的位置

__bss_start是bss开始的位置

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

.data段,后面的AT(__data_loc)的意思是这部分的内容是在__data_loc中存储的(要注意,储存的位置和链接的位置是可以不相同的).

这几个字段在文件arch/arm/kernel/vmlinux.lds中指定位置如下:

divS

{

……………………

#ifdefCONFIG_XIP_KERNEL

__data_loc=ALIGN(4);/*locationinbinary*/

.=PAGE_OFFSET+TEXT_OFFSET;

#else

.=ALIGN(THREAD_SIZE);

__data_loc=.;

#endif

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

_data=.;/*addressinmemory*/

*(.data.init_task)

…………………………

.bss:{

__bss_start=.;/*BSS*/

*(.bss)

*(COMMON)

_end=.;

}

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

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

00033:unionthread_unioninit_thread_union

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

00035:{INIT_THREAD_INFO(init_task)};

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

*/

__mmap_switched:

adrr3,__switch_data+4

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

cmpr4,r5@Copydatasegmentifneeded

1:cmpner5,r6//将__data_loc处数据搬移到_data处

ldrnefp,[r4],#4

strnefp,[r5],#4

bne1b

movfp,#0//清除BSS段内容

1:cmpr6,r7

strccfp,[r6],#4

bcc1b

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

strr9,[r4]@SaveprocessorID

strr1,[r5]@Savemachinetype

strr2,[r6]@Saveatagspointer

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

stmiar7,{r0,r4}@Savecontrolregistervalues

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

ENDPROC(__mmap_switched)

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

http://chxxxyg.blog.163.com/blog/static/150281193201072775034875/

最有效的资本是我们的信誉,它24小时不停为我们工作。

linux内核启动过程分析(二)

相关文章:

你感兴趣的文章:

标签云: