linux-2.6.22.6内核启动分析之head.S引导段代码
学习目标:
了解arch/arm/kernel/head.S作为内核启动的第一个文件所实现的功能!
前面通过对内核Makefile的分析,可以知道arch/arm/kernel/head.S是内核启动的第一个文件。另外,U-boot调用内核时,r1寄存器中存储“机器类型ID”,内核会使用它。
打开arch/arm/kernel/head.S文件,可以看到stext函数是内核入口函数,函数内容如下:
.section ".text.head", "ax" /* 定义一个.text.head段,段的属性a是允许段,x是可执行 */
.type stext, %function /* 定义u-boot进入内核的入口函数 */
ENTRY(stext) /* 入口地址stext函数 */
msr cpsr_c, #PSR_F_BIT | PSR_I_BIT | SVC_MODE /* 关中断,设置CPU工作在管理模式 */ mrc p15, , r9, c0, c0 /* 获取CPU的ID */
bl __lookup_processor_type /* 调用函数,输入参数r9=cpuid,返回值r5=procinfo */
movs r10, r5 /* 不支持当前CPU,则返回值r5=0 */
beq __error_p /* 如果r5=0,则打印错误 */
bl __lookup_machine_type /* 调用函数,返回值r5=machinfo */
movs r8, r5 /* 不支持当前开发板,返回值r5=machinfo */
beq __error_a /* 如果r5=0,则打印错误 */
bl __create_page_tables /* 创建页表 */
先来大概介绍入口函数stext每条语句实现的功能,然后再详细分析__lookup_processor_type和__lookup_machine_type这两个函数:
第79行通过设置CPSR寄存器来确保处理器进入管理模式,并且禁止中断。
第81行读取协处理器CP15的寄存器C0获得CPU ID。
第82行调用__lookup_processor_type函数,检测内核是否支持当前CPU。如果支持,r5寄存器返回一个用来描述处理器结构的地址,否则r5的值为0。
第85行调用__lookup_machine_type函数,确定内核是否支持当前开发板。如果支持,r5寄存器返回一个用来描述这个开发板的结构的地址,否则r5的值为0。
第88行调用__create_page_table函数,其中的__create_page_table函数用来创建以及页表以建立虚拟地址到物理地址的映射关系,它用到__lookup_processer_type函数返回的proc_info_list结构。
如果__lookup_processor_type、__lookup_machine_type这两个函数中有一个返回值为0,则内核不能启动,如果配置内核时使能了CONFIG_DEBUG_LL,还会打印错误提示信息。
在介绍__lookup_processor_type和__lookup_machine_type这两个函数之前,还要先插讲一些内容。我们在前面简要的说过__lookup_processor_type和__lookup_machine_type这两个函数分别是用来检测内核是否支持当前架构的处理器、是否支持当前开发板,如果内核想实现检测功能,那么内核中一定会存放自己所支持的处理器架构信息以及所支持的开发板信息,下面先来找到内核中这些信息是如何被定义的。
内核中,定义了若干个pro_info_list结构,表示它所支持的CPU。对于ARM架构的CPU,这些结构体的源码在arch/arm/mm/目录下,例如proc-arm920.S中的如下代码,它表示arm920架构CPU的pro_info_list结构。
.section ".proc.info.init", #alloc, #execinstr .type __arm920_proc_info,#object
__arm920_proc_info:
.long 0x41009200 /* cpu val */
.long 0xff00fff0 /* cpu mask */
内核所支持每个处理器架构都有自己的pro_info_list结构体用来保存自己CPU信息,这些不同处理器使用的pro_info_list结构体都被强制定义在“.proc_info_init”段中。在连接内核时,这些结构体被组织在一起,起始地址为__proc_info_begin,结束地址为__proc_info_end。可以从连接脚本arch/arm/kernel/vmlinux.lds中看出来。
__proc_info_begin = .; #.proc_info_init段起始地址(连接程序是动态确定)
*(.proc.info.init)
__proc_info_end = .; #.proc_info_init结束地址(连接程序时动态确定)
再来看内核中支持开发板信息内容,是如何被定义和存放的。内核中对于每种所支持的开发板都会使用宏MACHINE_START、MACHINE_END来定义一个machine_desc结构,这个结构定义了开发板相关的一些属性和函数,比如机器ID、起始I/O物理地址、Bootloader传入参数的地址、中断初始化函数等等。例如SMDK2410开发板,在arch/arm/mach-sc32410/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) >> ) & 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
在内核的include/asm/-arm/mach/arch.h文件中找到第198、202行的宏MACHINE_START、MACHINE_END定义,这两个宏定义如下:
#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 \
};
按照上述宏定义,将198行~208行代码展开如下所示:
static const struct machine_desc __mach_desc_SMDK2410 \
__used \
__attribute__((__section__(".arch.info.init"))) = { \
.nr = MACH_TYPE_SMDK2410, \
.name = "SMDK2410",
.phys_io = S3C2410_PA_UART,
.io_pg_offst = (((u32)S3C24XX_VA_UART) >> ) & 0xfffc,
.boot_params = S3C2410_SDRAM_PA + 0x100,
.map_io = smdk2410_map_io,
.init_irq = s3c24xx_init_irq,
.init_machine = smdk2410_init,
.timer = &s3c24xx_timer,
};
将198~208行的代码展开,可以看出这段内容定义了一个静态常量的machine_desc类型结构体__mach_desc_SMDK2410。其中的MACH_TYPE_SMDK2410在arch/arm/tools/mach-types中定义,最后这个文件会被转换成一个头文件include/asm/arm/mach-type.h被其它文件包含。machine_des结构体在include/asm-arm/mach/arch.h文件中定义。__attribute__((__section__(".arch.info.init")))语句表示将所有的machine_desc结构都存放在“.arch.info.init”段中,在连接内核时,它们被组织到一起,开始地址为__arch_info_begin,结束地址为__arch_info_end。可以从连接脚本arch/arm/kernel/vmlinux.lds中看出来。
__arch_info_begin = .; #.arch.info.init段起始地址(连接时动态确定)
*(.arch.info.init)
__arch_info_end = .; #.arch.info.init段结束地址(连接时动态确定)
有了上面的插讲内容作为铺垫,下面对 __lookup_processor_type和__lookup_machine_type这两个函数如何去实现各自功能理解就会更加方便了。先来看 __lookup_processor_type函数,在arch/arm/kernel/head-common.S文件中定义如下:
.type __lookup_processor_type, %function
__lookup_processor_type:
adr r3, 3f
ldmda r3, {r5 - r7}
sub r3, r3, r7 @ get offset between virt&phys
add r5, r5, r3 @ convert virt addresses to
add r6, r6, r3 @ physical address space
: ldmia r5, {r3, r4} @ value, mask
and r4, r4, r9 @ mask wanted bits
teq r3, r4
beq 2f
add r5, r5, #PROC_INFO_SZ @ sizeof(proc_info_list)
cmp r5, r6
blo 1b
mov r5, # @ unknown processor
: mov pc, lr
.long __proc_info_begin
.long __proc_info_end
: .long .
.long __arch_info_begin
.long __arch_info_end
在调用__enable_mmu函数之前使用的都是物理地址,而内核却以虚拟地址链接的。所以在访问pro_info_list结构之前,先将它的虚拟地址转换为物理地址,上面代码的147行~151行就是实现上述的转换。
第147行先获得178行物理地址。adr指令基于pc寄存器计算地址,此时MMU功能关闭,PC寄存器中使用的还是物理地址,所以执行“adr r3,3f”后,r3内存放的是178行代码的物理地址, 指令中的3f的f是forward的意思,意思是跳到程序的后面(往下)。
第148行用来获取第176行~178行定义的数据:__proc_info_begin、__pro_info_end和"."。前两个变量是在连接内核时确定,它们是虚拟地址,在前面插讲中我们对这__proc_info_begin、__pro_info_end已经做出了详细介绍,"."表示当前的代码在编译链接后的虚拟地址。ldmda r3, {r5-r7}指令,从源地址[r3]读取4个字节数据放到寄存器中,每读一次r3-4,而指令执行后r3内容不变,数据存放到寄存器规则是低地址对于低寄存器编号,高地址对于高寄存器编号。
第149行计算物理地址和虚拟地址的差值,第150~151根据这个差值计算__pro_info_begin、__pro_info_end的物理地址。
下面的代码一次读取存放在“.proc_info_init”段中每个cpu架构的pro_info_list结构体前面两个成员,判断cpu_val是否等于r9&cpu_mask,r9是读取head.S中获取的CPU ID.如果比较相等,则表示内核支持当前CPU,直接返回这个结构地址。如果“.proc_info_init”段所有pro_info_list结构都不支持这个CPU,则返回0。
第160行是子函数调用返回语句。
接着再来分析__lookup_machine_type这个函数,同样的这个函数也在在arch/arm/kernel/head-common.S文件中定义,其代码如下所示:
.type __lookup_machine_type, %function
__lookup_machine_type:
adr r3, 3b @address of 3b, Physical address
ldmia r3, {r4, r5, r6} @r4="." virtual address of 3b,r5=__arch_info_begin ,r6=__arch_info_end
sub r3, r3, r4 @ get offset between virt&phys
add r5, r5, r3 @ convert virt addresses to
add r6, r6, r3 @ physical address space
: ldr r3, [r5, #MACHINFO_TYPE] @ get machine type
teq r3, r1 @ matches loader number?
beq 2f @ found
add r5, r5, #SIZEOF_MACHINE_DESC @ next machine_desc
cmp r5, r6
blo 1b
mov r5, # @ unknown machine
: mov pc, lr
第195行先获得178行物理地址。adr指令基于pc寄存器计算地址,此时MMU功能关闭,PC寄存器中使用的还是物理地址,所以执行“adr r3,3f”后,r3内存放的是178行代码的物理地址, 指令中的3b的b是backward的意思,意思是跳到程序的前面(往上)。
第196行用来获取第178行~180行定义的数据:__arch_info_begin、__arch_info_end和"."。前两个变量都是在连接内核时确定,它们是虚拟地址,在前面插讲中我们对这__arch_info_begin、__arch_info_end已经做出了详细介绍,"."表示当前的代码在编译链接后的虚拟地址。ldmia r3, {r5-r7}指令,从源地址[r3]读取4个字节数据放到寄存器中,每读一次r3+4,而指令执行后r3内容不变,数据存放到寄存器规则是低地址对于低寄存器编号,高地址对于高寄存器编号。
第197行计算物理地址和虚拟地址的差值,第198~199行根据这个差值计算__arch_info_begin、__arch_info_end的物理地址。
下面的代码读取存放在“.arch_info_init”段中每个machine_des结构体机器类型ID,判断uboot传入机器ID(通过r1寄存器传入)是否等于内核中存放的不同machine_des结构体的机器类型ID。如果比较相等,则表示内核支持当前开发板,直接返回这个结构地址。如果“.arch_info_init”段所有存放machine_des结构都不支持这个开发板,则返回0。
第207行是子函数调用返回语句。
继续分析arch/arm/kernel/head.S代码
ldr r13, __switch_data @ address to jump to after
@ mmu has been enabled
adr lr, __enable_mmu @ return (PIC) address
add pc, r10, #PROCINFO_INITFUNC
第97行把__switch_data函数地址存放到r13寄存器中,存放的地址为虚拟地址。
第99行把__enable_mmu函数地址存放到lr(r4)寄存器中,存放地址为物理地址。
第100行#PROCINFO_INITFUNC为16,程序计数器pc值=r10+16,由上面分析可以知道r10内容为内核中描述当前处理器结构的地址,也就是arch/arm/mm/pro-arm920.S文件中__arm920_proc_info入口地址,pc=r10+16,即跳转下面代码第502处执行__arm920_setup函数。
__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
.long cpu_arch_name
...................
#endif
.size __arm920_proc_info, . - __arm920_proc_info
__arm920_setup函数内容如下:
.type __arm920_setup, #function
__arm920_setup:
mov r0, #
mcr p15, , r0, c7, c7 @ invalidate I,D caches on v4
mcr p15, , r0, c7, c10, @ drain write buffer on v4
#ifdef CONFIG_MMU
mcr p15, , r0, c8, c7 @ invalidate I,D TLBs on v4
#endif
adr r5, arm920_crval
ldmia r5, {r5, r6}
mrc p15, , r0, c1, c0 @ get control register v4
bic r0, r0, r5
orr r0, r0, r6
mov pc, lr
.size __arm920_setup, . - __arm920_setup
__arm920_setup函数功能是禁止ICache、DCache、数据Cache、指令Cache,最后将lr寄存器内容传送给PC程序计数器,由上面知道lr寄存器存放为__enable_mmu函数地址,此时程序将跳转到__enable_mmu函数处执行。__enable_mmu函数在arch/arm/kernel/head.S文件中,内容如下:
.type __enable_mmu, %function
__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
#endif
#ifdef CONFIG_CPU_BPREDICT_DISABLE
bic r0, r0, #CR_Z
#endif
#ifdef CONFIG_CPU_ICACHE_DISABLE
bic r0, r0, #CR_I
#endif
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, , r5, c3, c0, @ load domain access register
mcr p15, , r4, c2, c0, @ load page table pointer
b __turn_mmu_on
__turn_mmu_on代码如下:
.align
.type __turn_mmu_on, %function
__turn_mmu_on:
mov r0, r0
mcr p15, , r0, c1, c0, @ write control reg
mrc p15, , r3, c0, c0, @ read id reg
mov r3, r3
mov r3, r3
mov pc, r13
__enable_mmu函数用来使能MMU,最后将r13寄存器内容赋给PC寄存器,由上面分析可知r13寄存器内容为__switch_data函数地址(此处地址为虚拟地址,因为已经开启了MMU功能),最终程序跳转到arm/arch/kernel/head-common.S文件__switch_data入口地址。__switch_data地址存放内容如下:
.type __switch_data, %object
__switch_data:
.long __mmap_switched
.long __data_loc @ r4
.long __data_start @ r5
.long __bss_start @ r6
.long _end @ r7
.long processor_id @ r4
.long __machine_arch_type @ r5
.long cr_alignment @ r6
.long init_thread_union + THREAD_START_SP @ sp
__switch_data地址处存放内容为__mmap_switched地址,PC跳转到__mmp_switched处执行,__mmp_swirched函数内容如下:
.type __mmap_switched, %function
__mmap_switched:
adr r3, __switch_data + ldmia r3!, {r4, r5, r6, r7}
cmp r4, r5 @ Copy data segment if needed
: cmpne r5, r6
ldrne fp, [r4], #
strne fp, [r5], #
bne 1b mov fp, # @ Clear BSS (and zero fp)
: cmp r6, r7
strcc fp, [r6],#
bcc 1b ldmia r3, {r4, r5, r6, sp}
str r9, [r4] @ Save processor ID
str r1, [r5] @ Save machine type
bic r4, r0, #CR_A @ Clear 'A' bit
stmia r6, {r0, r4} @ Save control register values
b start_kernel
第36~43行实现复制数据段。
第45~48行实现清除BSS段。
第50行设置栈指针。
第51行保存CPU ID。
第52行存机器类型ID。
第55行跳转到start_kernel函数执行。
总结:引导阶段代码做了以下内容
1、首先检查内核是否支持当前架构处理器,然后检测是否支持当前开发板,若支持执行后续操作。
2、设置页表、使能MMU
3、执行调用内核执行第一个C函数start_kernel之前的常规工作,包括复制数据段、清除BSS段等
linux-2.6.22.6内核启动分析之head.S引导段代码的更多相关文章
- linux-2.6.22.6内核启动分析之Makefile文件
学习目标 分析Makefile文件,了解内核中的哪些文件被编译,如何被编译,连接时顺序如何确定! Linux内核源码中包含很多的Makefile文件,这些Makefile文件又包含其它的一些文件,比如 ...
- linux-2.6.22.6内核启动分析之配置
配置过程最终结果是生成.config文件,我们想要对配置的目的有很清楚的了解,必须先对.config文件进行分析.通过cd命令切换到linux-2.6.22.6内核目录,输入vi .config 可以 ...
- linux-2.6.22.6内核启动分析之编译体验
1 解压缩.打补丁操作 1.1 打开ubuntu,通过FTP将windows相应文件夹下的linux-2.6.22.6.tar.bz2和补丁文件linux-2.6.22.6-jz2440.patch上 ...
- Linux内核启动分析过程-《Linux内核分析》week3作业
环境搭建 环境的搭建参考课件,主要就是编译内核源码和生成镜像 start_kernel 从start_kernel开始,才真正进入了Linux内核的启动过程.我们可以把start_kernel看做平时 ...
- 第3阶段——内核启动分析之start_kernel初始化函数(5)
内核启动分析之start_kernel初始化函数(init/main.c) stext函数启动内核后,就开始进入start_kernel初始化各个函数, 下面只是浅尝辄止的描述一下函数的功能,很多函数 ...
- mkimage工具 加载地址和入口地址 内核启动分析
第三章第二节 mkimage工具制作Linux内核的压缩镜像文件,需要使用到mkimage工具.mkimage这个工具位于u-boot-2013. 04中的tools目录下,它可以用来制作不压缩或者压 ...
- Linux内核启动分析
张超<Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 我的代码可见https://www.shiyanlo ...
- linux内核启动分析(2)
-----以下内容为从网络上整理所得------ 主要介绍kernel_init线程(函数),这个线程在rest_init函数中被创建,kernel_init函数将完成设备驱动程序的初始化,并调用in ...
- Linux内核启动分析笔记
一.驱动加载 1.驱动加载调用关系 start_kernel //init/main.c rest_init //最后执行它 kernel_init //使用kernel_thread创建一个进程执行 ...
随机推荐
- Vscode rg.exe cpu 占用过高
文件-> 首选项 -> 设置 -> 搜索search.followSymlinks 或者 修改settings.json 添加 "search.followSymlinks ...
- Windows Server、 Windows 区别
今天脑补了普通Windows 操作系统与Windows Server区别,感觉清楚了很多. Microsoft WindowsServer,是美国微软公司研制的一套操作体系,它面世于1985年,起先仅 ...
- vi编辑器的命令详情
选定文本块,使用v复制选定块到缓冲区,使用y复制整行,用yy在同一编辑窗打开第二个文件,用:sp [filename]在多个编辑文件之间切换,用^ww剪切块,用d剪切整行用dd粘贴缓冲区中的内容,用p ...
- 程序人生:02我来告诉你,一个草根程序员如何进入BAT
本文摘自左潇龙博客,原文出处:http://www.cnblogs.com/zuoxiaolong/p/life54.html 引言 首先声明,不要再问LZ谁是林萧,林萧就是某著名程序员小说的主角名字 ...
- Hadoop学习之路(二十五)MapReduce的API使用(二)
学生成绩---增强版 数据信息 computer,huangxiaoming,85,86,41,75,93,42,85 computer,xuzheng,54,52,86,91,42 computer ...
- nginx下配置多个web服务
参考 nginx配置详解 nginx反向代理与负载均衡详解 一.nginx简介: Nginx("engine x")是一款是由俄罗斯的程序设计师Igor Sysoev所开发高性能 ...
- SpringMVC DELETE,PUT请求报错 添加支持Http的DELETE、PUT请求
SpringMVC删除与修改操作需要用DELETE,PUT请求方式提交. 但要知道浏览器form表单只支持GET与POST请求,而DELETE.PUT等method并不支持. spring3.0添加了 ...
- Dubbo实践(十二)Refer
Spring在启动Dubbo客户端应用时,会实例化ReferenceBean<T>并设置配置属性,然后调用ReferenceConfig中的get方法: public synchroniz ...
- PAT——1044. 火星数字
火星人是以13进制计数的: 地球人的0被火星人称为tret. 地球人数字1到12的火星文分别为:jan, feb, mar, apr, may, jun, jly, aug, sep, oct, no ...
- pymongo的安装和使用
1.安装 MongoDB的python接口pymongo的安装方法有多种,如源码.easy_install.pip都可以.采用pip安装,很简单. pip install pymongo 安装完成后可 ...