arm-linux内核start_kernel之前启动分析(1)-接过bootloader的衣钵
Uboot启动过程分析博文连接例如以下:
http://blog.csdn.net/skyflying2012/article/details/25804209
移植内核时kernel启动过程须要我们改动的地方比較少。研究这个对于编写driver也没有多大帮助,但对了解整个linux架构,各种机制还是非常实用。
仅仅有知道kernel怎样启动,我们才干真正的去理解kernel
作为一个嵌入式工作者,我想不能仅仅局限于某个module driver。而应深入到kernel的汪洋大海中去傲游!
学习启动过程,我本着打破沙锅问究竟的原则,希望能研究的明明确白,但也鉴于水平有限。还是有非常多纰漏之处
共享博文。希望大家多多交流指正,辛苦整理。如需转载,还请注明出处。
对于arm linux,start_kernel之前都是汇编代码,区区上百行汇编,可是却蕴含着非常多精髓。
这部分代码分3篇来分析,另外两篇链接地址例如以下:
http://blog.csdn.net/skyflying2012/article/details/41447843
http://blog.csdn.net/skyflying2012/article/details/48054417
今天先来学习前几十行!
Kernel版本:3.4.55
在arch/arm/kernel/head.S中。例如以下:
.arm
__HEAD
ENTRY(stext)
THUMB( adr r9, BSYM(1f) ) @ Kernel is always entered in ARM.
THUMB( bx r9 ) @ If this is a Thumb-2 kernel,
THUMB( .thumb ) @ switch to Thumb now.
THUMB(1: )
//处理器进入svc模式。关闭中断
setmode PSR_F_BIT | PSR_I_BIT | SVC_MODE, r9 @ ensure svc mode
@ and irqs disabled
//获取处理器ID
mrc p15, 0, r9, c0, c0 @ get processor id
bl __lookup_processor_type @ r5=procinfo r9=cpuid
//将proc_type_list pointer存在r10中。假设为NULL,则error_p
movs r10, r5 @ invalid processor (r5=0)?
THUMB( it eq ) @ force fixup-able long branch encoding
beq __error_p @ yes, error 'p'
//CONFIG_ARM_LPAE不太明确含义。我使用处理器配置文件没有选择该项,感兴趣朋友能够研究下
#ifdef CONFIG_ARM_LPAE
mrc p15, 0, r3, c0, c1, 4 @ read ID_MMFR0
and r3, r3, #0xf @ extract VMSA support
cmp r3, #5 @ long-descriptor translation table format?
THUMB( it lo ) @ force fixup-able long branch encoding
blo __error_p @ only classic page table format
#endif
#ifndef CONFIG_XIP_KERNEL
//获取物理地址与虚拟地址的offset。存在r8中
adr r3, 2f
ldmia r3, {r4, r8}
sub r4, r3, r4 @ (PHYS_OFFSET - PAGE_OFFSET)
add r8, r8, r4 @ PHYS_OFFSET
#else
//定义CONFIG_XIP_KERNEL,offset为PHYS_OFFSET
ldr r8, =PHYS_OFFSET @ always constant in this case
#endif
/*
* r1 = machine no, r2 = atags or dtb,
* r8 = phys_offset, r9 = cpuid, r10 = procinfo
*/
//对bootloader传来的tags參数进行检查
bl __vet_atags
Kernel的入口函数是哪个,入口地址在哪,须要依据连接脚本来确定。
在arch/arm/kernel/vmlinux.lds.S,例如以下:
OUTPUT_ARCH(arm)
ENTRY(stext) #ifndef __ARMEB__
jiffies = jiffies_64;
#else
jiffies = jiffies_64 + 4;
#endif SECTIONS
{
........
#ifdef CONFIG_XIP_KERNEL
. = XIP_VIRT_ADDR(CONFIG_XIP_PHYS_ADDR);
#else
. = PAGE_OFFSET + TEXT_OFFSET;
#endif
}
入口函数是head.S中的stext。不採用XIP技术,入口地址是PAGE_OFFSET+TEXT_OFFSET。
./arch/arm/include/asm/memory.h中:
#define PAGE_OFFSET UL(CONFIG_PAGE_OFFSET)
Menuconfig中CONFIG_PAGE_OFFSET = 0xc0000000
./arch/arm/Makefile中:
textofs-y := 0x00008000
textofs-$(CONFIG_ARCH_CLPS711X) := 0x00028000
# We don't want the htc bootloader to corrupt kernel during resume
textofs-$(CONFIG_PM_H1940) := 0x00108000
# SA1111 DMA bug: we don't want the kernel to live in precious DMA-able memory
ifeq ($(CONFIG_ARCH_SA1100),y)
textofs-$(CONFIG_SA1111) := 0x00208000
endif
textofs-$(CONFIG_ARCH_MSM7X30) := 0x00208000
textofs-$(CONFIG_ARCH_MSM8X60) := 0x00208000
textofs-$(CONFIG_ARCH_MSM8960) := 0x00208000
......
# The byte offset of the kernel image in RAM from the start of RAM.
TEXT_OFFSET := $(textofs-y)
入口地址是0xc0008000.
可是实际操作中,kernel是载入到0x80008000地址执行的。
(我使用处理器sdram物理起始地址是0x80000000)
为什么链接地址和执行地址不一致?
学习完start_kernel之前的汇编,就会明确原因了。
在stext中。首先调用到__lookup_processor_type,Kernel代码将全部CPU信息的定义都放到.proc.info.init段中。因此能够觉得.proc.info.init段就是一个数组,每一个元素都定义了一个或一种CPU的信息。
眼下__lookup_processor_type使用该元素的前两个字段cpuid和mask来匹配当前CPUID。假设满足CPUID & mask == cpuid,则找到当前cpu的定义并返回。
代码例如以下:
__CPUINIT
__lookup_processor_type:
//3行汇编,计算出物理地址与虚拟地址之间的offset。存在r3中
adr r3, __lookup_processor_type_data
ldmia r3, {r4 - r6}
sub r3, r3, r4 @ get offset between virt&phys
//获取__proc_info_begin的物理地址
add r5, r5, r3 @ convert virt addresses to
//获取__proc_info_end的物理地址
add r6, r6, r3 @ physical address space
//mask cp15读出的cpuid,与proc_type_list中value对照
1: ldmia r5, {r3, r4} @ value, mask
and r4, r4, r9 @ mask wanted bits
teq r3, r4
//一致则返回,不一致则跳到下一个proc_type_list,继续对照
beq 2f
add r5, r5, #PROC_INFO_SZ @ sizeof(proc_info_list)
cmp r5, r6
blo 1b
//匹配成功。r5存该proc_type_list指针。匹配失败,r5置0
mov r5, #0 @ unknown processor
2: mov pc, lr
ENDPROC(__lookup_processor_type) /*
* Look in <asm/procinfo.h> for information about the __proc_info structure.
*/
.align 2
.type __lookup_processor_type_data, %object
__lookup_processor_type_data:
.long .
.long __proc_info_begin
.long __proc_info_end
.size __lookup_processor_type_data, . - __lookup_processor_type_data</span>
由于kernel要开启MMU,所以kernel编译链接地址是虚拟地址(物理地址经过MMU转换后CPU看到的地址)。并非物理地址。
链接确定了变量的绝对地址(虚拟地址),但在现阶段。没开启MMU,CPU看到的sdram地址就是其物理地址(0x80000000起始)。
假设直接执行,对于变量的寻址则会出现故障(函数寻址没问题,由于arm函数寻址使用相对跳转指令b bl)
比方。kernel image中全局变量i链接地址在0xc0009000,但现阶段i物理地址是在0x80009000。对于CPU来说,仅仅能在0x80009000上才干找到i。
去0xc0009000寻址,程序执行就出错了。
这就是为什么我们所理解的,链接地址 载入地址 执行地址必须一致的原因。
kernel现阶段给出的解决方法,就是lookup_processor_type前3行汇编:
adr r3, __lookup_processor_type_data 载入__lookup_processor_type_data地址(实际执行地址,这里就是物理地址)到r3
ldmia r3。 {r4 - r6} 获取以r3 r3+4 r3+8为地址的变量到r4,r5。r6.
地址变量值是在链接时确定的,所以r4中存的是__lookup_processor_type_data的链接地址(虚拟地址)。
sub r3 ,r3 。r4 r3中存储的是物理地址与虚拟地址的偏移。
这是多么genius的操作啊!
_proc_info_begin _proc_info_end在链接脚本中定义,是.proc.info.init段的首尾。
该段中是proc_info_list struct,表示处理器相关信息,定义例如以下:
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;
};
该段是在arch/arm/mm/proc-xxx.S中填充,定义了相应arm指令集的处理器特性和初始化函数。在第三篇文章中我们还会具体来理解proc info的作用,这里先按下不表。
lookup_processor_type_data返回stext中。
接下来相同用上面的方法获取phy&virt offset,存在r8.
依据我之前分析uboot传參kernel的博文(链接例如以下:http://blog.csdn.net/skyflying2012/article/details/35787971)
r1存储machine id,r2存储atags。
stext中__vet_atags会对atags做一个主要的检查,代码例如以下:
__vet_atags:
tst r2, #0x3 @ aligned?
bne 1f ldr r5, [r2, #0]
//推断是否是dtb类型
#ifdef CONFIG_OF_FLATTREE
ldr r6, =OF_DT_MAGIC @ is it a DTB?
cmp r5, r6
beq 2f
#endif
cmp r5, #ATAG_CORE_SIZE @ is first tag ATAG_CORE?
cmpne r5, #ATAG_CORE_SIZE_EMPTY
bne 1f
ldr r5, [r2, #4]
ldr r6, =ATAG_CORE
cmp r5, r6
bne 1f //正确tags,返回
2: mov pc, lr @ atag/dtb pointer is ok //错误tags,清空r2。返回
1: mov r2, #0
mov pc, lr
ENDPROC(__vet_atags)
检查tag头4 byte(tag_core的size)和第二个4 byte(tag_core的type)是否正确。
对于stext中前几十行汇编,已经分析完毕,总结下做了哪些工作:
(1)设置CPU模式
(2)检查CPUID是否匹配
(3)获取phy&virt offset
(4)检查atags參数
这段代码就分析到这,只是引起了我对于链接地址 执行地址的思考。
教科书上是这样教的。我也一直这么觉得,链接地址 执行地址(载入地址)必须是一致。可是却没有真正去思考过为什么。
程序的链接地址与执行地址为什么要一致?
我的理解,链接确定程序执行绝对地址。也确定了当中变量及函数的绝对地址,载入执行地址不是其链接地址,变量实际存储的地址就变了。
这时假设对变量进行寻址,就会有不可知的结果,这是我能想到的原因。
平时我们编译链接都是一些C语言编敲代码,难免会定义一些全局变量。假设链接和执行地址不一致。就不能正常寻址。
假设想执行和链接地址不一致。我能想到的办法,仅仅能是汇编中尽量不去涉及一些绝对地址,使用PIC位置无关代码。
联想之前分析的uboot relocation原理(博文链接:http://blog.csdn.net/skyflying2012/article/details/37660265),
uboot在relocation之后,kernel在开启MMU之前,都实现了链接地址和执行地址不一致,看看它们用的什么方法?
(1)uboot在relocation时改动rel.dyn段(存储全部变量地址)。实现将全部变量地址重定位到新执行地址
(2)kernel在开启MMU之前,计算执行地址(物理地址)与链接地址(虚拟地址)的偏移,对变量寻址时都进行地址转换。从而正常找到变量。开启MMU之后。利用硬件机制。来实现链接和执行地址的统一
所以说,链接地址一定要等于执行地址吗?不一定。嵌入式最著名的uboot kernel就是样例!
arm-linux内核start_kernel之前启动分析(1)-接过bootloader的衣钵的更多相关文章
- ARM Linux 内核 panic 之cache 一致性 ——Cortex-A9多核cache和TLB一致性广播
ARM Linux 内核 panic 之cache 一致性 ——Cortex-A9多核cache和TLB一致性广播 Cortex-A9的多喝CPU可以接收和执行一致性广播操作,当其使能并处于SMP模式 ...
- ARM Linux 内核 panic 之cache 一致性 ——cci-400 cache一致互联
ARM Linux 内核 panic 之cache 一致性 ——cci-400 cache一致互联 CCI-400 集合了互联和一致性功能,有 2 个 ACE slave 接口和 3 个 ACE-Li ...
- Linux内核--网络栈实现分析(十一)--驱动程序层(下)
本文分析基于Linux Kernel 1.2.13 原创作品,转载请标明http://blog.csdn.net/yming0221/article/details/7555870 更多请查看专栏,地 ...
- Linux内核--网络栈实现分析(七)--数据包的传递过程(下)
本文分析基于Linux Kernel 1.2.13 原创作品,转载请标明http://blog.csdn.net/yming0221/article/details/7545855 更多请查看专栏,地 ...
- linux内核SPI总线驱动分析(一)(转)
linux内核SPI总线驱动分析(一)(转) 下面有两个大的模块: 一个是SPI总线驱动的分析 (研究了具体实现的过程) 另一个是SPI总线驱动的编写(不用研究具体的实现过程) ...
- Linux内核--网络栈实现分析(二)--数据包的传递过程--转
转载地址http://blog.csdn.net/yming0221/article/details/7492423 作者:闫明 本文分析基于Linux Kernel 1.2.13 注:标题中的”(上 ...
- Linux内核态抢占机制分析(转)
Linux内核态抢占机制分析 http://blog.sina.com.cn/s/blog_502c8cc401012pxj.html 摘 要]本文首先介绍非抢占式内核(Non-Preemptive ...
- linux内核中链表代码分析---list.h头文件分析(一)【转】
转自:http://blog.chinaunix.net/uid-30254565-id-5637596.html linux内核中链表代码分析---list.h头文件分析(一) 16年2月27日17 ...
- linux内核中链表代码分析---list.h头文件分析(二)【转】
转自:http://blog.chinaunix.net/uid-30254565-id-5637598.html linux内核中链表代码分析---list.h头文件分析(二) 16年2月28日16 ...
随机推荐
- windows上同时安装两个版本的mysql数据库
一.先停止之前安装的低版本mysql服务 二.将其他电脑上安装好的mysql拷贝过来 三.拷贝过来之后,进入该文件夹,删除掉data目录,然后打开my.ini,进行修改端口号,端口号改为3307,ba ...
- POJ 2348 Euclid's Game 博弈论
http://poj.org/problem?id=2348 顺便说,必应翻译真的好用,比谷歌翻译好用100倍. 很难判断这道题的具体博弈类型. 有两种写法,一种是找规律,一种是推理得到关系后循环(或 ...
- hdu 1402(FFT乘法 || NTT乘法)
A * B Problem Plus Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Other ...
- 【bzoj1594】猜数游戏
1594: [Usaco2008 Jan]猜数游戏 Time Limit: 10 Sec Memory Limit: 162 MBSubmit: 556 Solved: 225 Descripti ...
- [CODE FESTIVAL 2017]Poor Penguin
题意:在一个$n\times m$的网格上,每个格子是薄冰或冰山(网格外什么都没有),有一片薄冰上站着一只企鹅,对于薄冰$(i,j)$,如果不满足($(i-1,j),(i+1,j)$都有东西或$(i, ...
- 【数论】【枚举】【莫比乌斯反演】【线性筛】bzoj2818 Gcd
思路是hdu6134的简化版,只需要在外面套上一个枚举素数就行了. http://www.cnblogs.com/autsky-jadek/p/7491730.html #include<cst ...
- java笔记之方法
一.那么什么是方法呢? 所谓方法,就是用来解决一类问题的代码的有序组合,是一个功能模块 方法是解决一类问题的步骤的有序组合 方法包含于类或对象中 方法在程序中被创建,在其他地方被引用 二.方法的优点 ...
- C#-java RSA加密解密
using Org.BouncyCastle.Math; using Org.BouncyCastle.Crypto.Parameters; using Org.BouncyCastle.Securi ...
- appium+python自动化45-夜神模拟器连不上(adb server version (36) doesn't match this client (39); killing...)
前言 最新下了个最新版的夜神模拟器,然后adb devices发现连不上模拟器了,报adb server version (36) doesn't match this client (39); ki ...
- 第六章在U盘上运行openwrt(引导)--补
1.前言 前面已经把U盘挂在了703N上了,现在只需要打开路由器,使用TTL串口或者putty(ssh模式需要用户名和密码-第一章刷openwrt的时候已经设置好)登陆路由器. 2.将系统内所有文件同 ...