Linux内核启动流程分析二.docx

上传人:b****7 文档编号:10550970 上传时间:2023-02-21 格式:DOCX 页数:27 大小:39.94KB
下载 相关 举报
Linux内核启动流程分析二.docx_第1页
第1页 / 共27页
Linux内核启动流程分析二.docx_第2页
第2页 / 共27页
Linux内核启动流程分析二.docx_第3页
第3页 / 共27页
Linux内核启动流程分析二.docx_第4页
第4页 / 共27页
Linux内核启动流程分析二.docx_第5页
第5页 / 共27页
点击查看更多>>
下载资源
资源描述

Linux内核启动流程分析二.docx

《Linux内核启动流程分析二.docx》由会员分享,可在线阅读,更多相关《Linux内核启动流程分析二.docx(27页珍藏版)》请在冰豆网上搜索。

Linux内核启动流程分析二.docx

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 = 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 @ mat

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 医药卫生 > 基础医学

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1