2.16.3.内核启动的汇编阶段

参考https://blog.csdn.net/skyflying2012/article/details/41344377

本节是内核启动的汇编阶段剩余内容,主要是cpu的校验、机器码的校验、传参tag的校验、页表的创建、各种段的处理等。

2.16.3.1、__lookup_processor_type

(1)我们从cp15协处理器的c0寄存器中读取出硬件的CPU ID号,然后调用这个函数来进行合法性检验。如果合法则继续启动,如果不合法则停止启动,转向__error_p启动失败。

(2)该函数检验cpu id的合法性方法是:内核会维护一个本内核支持的CPU ID号码的数组,然后该函数所做的就是将从硬件中读取的cpu id号码和数组中存储的各个id号码依次对比,如果没有一个相等则不合法,如果有一个相等的则合法。

(3)内核启动时设计这个校验,也是为了内核启动的安全性着想。

总结:校验cpu id为什么链接地址和运行地址不一致?

在stext中,首先调用到__loopup_processor_type,kernel代码将所有CPU信息的定义放到.proc.info.init段中,因此可以认为.proc.info.init就是一个数组,每个元素定义了一个/一种CPU的信息。目前__loopup_processor_type使用该元素的前两个字段cpuid和mask来匹配当前CPUID,如果满足CPUID&mask == cpuid,则找到当前CPU的定义并返回。

因为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行汇编程序的链接地址与运行地址为什么要一致?我的理解,链接确定程序运行绝对地址,也确定了其中变量及函数的绝对地址,加载运行地址不是其链接地址,变量实际存储的地址就变了。这时如果对变量进行寻址,就会有不可知的结果,这是我能想到的原因。平时我们编译链接都是一些C语言编写程序,难免会定义一些全局变量,如果链接和运行地址不一致,就不能正常寻址。如果想运行和链接地址不一致,我能想到的办法,只能是汇编中尽量不去涉及一些绝对地址,使用PIC位置无关代码。uboot在relocation之后,kernel在开启MMU之前,都实现了链接地址和运行地址不一致

* uboot在relocation时修改rel.dyn段[存储所有变量地址],实现将所有变量地址重定位到新运行地址

* kernel在开启MMU之前,计算运行地址[物理地址]和链接地址[虚拟地址]的偏移,对变量寻址时进行物理转,从而正常找到变量。开启MMU之后,利用硬件机制,来实现链接和运行地址统一

所以说,链接地址一定要等于运行地址吗?不一定,嵌入式最著名的uboot kernel就是例子!

2.16.3.2、__lookup_machine_type

(1)该函数的设计理念和思路和上面校验cpu id的函数一样的。不同之处是本函数校验的是机器码。uboot传递机器码,这个函数校验总结:校验机器码

2.16.3.3、__vet_atags

(1)该函数的设计理念和思路和上面2个一样,不同之处是用来校验uboot给内核的传参ATAGS格式是否正确。这里说的传参指的是uboot通过tag给内核传的参数(主要是板子的内存分布memtag、uboot的bootargs)

(2)内核认为如果uboot给我的传参格式不正确,那么我就不启动。

(3)uboot给内核传参的部分如果不对,是会导致内核不启动的。譬如uboot的bootargs设置不正确内核可能就会不启动。

2.16.3.4、__create_page_tables

(1)顾名思义,这个函数用来建立页表。

(2)linux内核本身被连接在虚拟地址处,因此kernel希望尽快建立页表并且启动MMU进入虚拟地址工作状态。但是kernel本身工作起来后页表体系是非常复杂的,建立起来也不是那么容易的。kernel想了一个好办法(3)kernel建立页表其实分为2步。第一步,kernel先建立了一个段式页表(和uboot中之前建立的页表一样,页表以1MB为单位来区分的),这里的函数就是建立段式页表的。段式页表本身比较好建立(段式页表1MB一个映射,4GB空间需要4096个页表项,每个页表项4字节,因此一共需要16KB内存来做页表),坏处是比较粗不能精细管理内存;第二步,再去建立一个细页表(4kb为单位的细页表),然后启用新的细页表废除第一步建立的段式映射页表。

(4)内核启动的早期建立段式页表,并在内核启动前期使用;内核启动后期就会再次建立细页表并启用。等内核工作起来之后就只有细页表了。create_page_table完成了3种地址映射的页表空间填写:(1)turn_mmu_on所在1M空间的平映射

(2)kernel image的线性映射

(2)atags所在1M空间的线性映射物理地址空间和虚拟地址空间映射关系图如下:

(1)为什么turn_mmu_on要做平映射?

turn_mmu_on我会在下一篇博文中分析,主要是完成开启MMU的操作。

那为什么将turn_mmu_on处做一个平映射?可以想象,执行开启MMU指令之前,CPU取指是在0x80008000附近turn_mmu_on中。如果只是做kernel image的线性映射,执行开启MMU指令后,CPU所看到的地址就全变啦。turn_mmu_on对于CPU来说在0xc0008000附近,0x80008000附近对于CPU来说已经不可预知了。但是CPU不知道这些,它只管按照地址一条条取指令,执行指令。所以不做turn_mmu_on的平映射(virt addr = phy addr),turn_mmu_on在开启MMU后的运行是完全不可知。完成turn_mmu_on的平映射,我们可以在turn_mmu_on末尾MMU已经开启稳定后,修改PC到0xc0008000附近,就可以解决从0x8xxxxxxx到0xcxxxxxxx的跳转。(

2)kernel image加载地址为什么会在0x****8000?

分析了kernel image线性映射部分,这个就好理解了:kernel编译链接时的入口地址在0xc0008000(PAGE_OFFSET + TEXT_OFFSET),但其物理地址不等于其链接的虚拟地址,image的线性映射实现其运行地址等于链接地址。kernel的每一页表映射1M,所以入口处在(0x80000000-->0xc0000000)映射页表中完成映射。物理地址和虚拟地址的1M内偏移必须一致呀。kernel定义的TEXT_OFFSET = 0x8000.所以加载的物理地址必须为0x****8000.这样,开启MMU后,访问0xc0008000附近指令,MMU根据TLB才能正确映射找到0x****8000附近的指令。

(3)atags跟kernel入口是在同一1M空间内,bootparams的线性映射操作是否多余?根据第二个问题的分析,kernel image可以加载到任何sdram地址空间的0x****8000即可。atags地址是有bootloader中指定,然后告诉kernel。那就有这样一种情况,加入sdram起始地址为0x80000000,atags起始地址为0x80000100。但kernel image我加载到0x81008000,可以看出,这时atags跟kernel image就在不同一1M空间啦atags单独的线性映射操作还是很有必要的。之前分析到__create_page_tables在内核代码区TEXT_OFF下部的16KB区域内进行页表的配置,完成turn_mmu_on的平映射以及kernel image的线性映射。接下来就需要开启MMU,让整个CPU进入虚拟地址运行的新阶段。head.S中stext最后一段代码如下:

看注释也可以明白接下来要完成的2件工作:执行CPU特定处理代码,开启MMU。

r10中存储着本cpu的proc_info_list首地址。在kernel image中定义有一个.proc.info.init的段。在arch/arm/kernel/vmlinux.lds.S中。具体见:[kernel 启动流程] (第三章)第一阶段之——proc info的获取

2.6.3.5、__switch_data

(1)建立了段式页表后进入了__switch_data部分,这东西是个函数指针数组。

(2)分析得知下一步要执行__mmap_switched函数

(3)复制数据段、清除bss段(目的是构建C语言运行环境)

(4)保存起来cpu id号、机器码、tag传参的首地址。

(5)b start_kernel跳转到C语言运行阶段。

总结:汇编阶段其实也没干啥,主要原因是uboot干了大部分活。汇编阶段主要就是校验启动合法性、建立段式映射的页表并开启MMU以方便使用内存、跳入C阶段。

原文地址:https://www.cnblogs.com/Ocean-Star/p/9235962.html

时间: 2024-10-11 07:28:01

2.16.3.内核启动的汇编阶段的相关文章

2.16.4.内核启动的C语言阶段1

本节讲述内核学习的学习思路.学习方法和主体线路.本节课程的学习目的是让大家对内核的特点和不同的学习思路有个认识. 2.16.4.1.这一块的学习思路 (1)抓大放小,不深究. (2)感兴趣可以就某个话题去网上搜索资料学习 (3)重点局部深入分析 2.16.4.2.具体学习方法 (1)顺着代码执行路径抓全.这是我们的学习主线. (2)对照内核启动的打印信息进行分析. 2.16.4.3.几条学习线路 (1)分析uboot给kernel传参的影响和实现 (2)硬件初始化与驱动加载 (3)内核启动后的结

2.16.6.内核启动的C语言阶段3

本节讲解setup_arch函数中的machine查找的部分,初步分析了内核对机器码的定义和存储方式.比对方式.获取方式. 2.16.6.1.setup_arch函数简介 setup_arch(&command_line); (1)从名字看,这个函数是CPU架构相关的一些创建过程. (2)实际上这个函数是用来确定我们当前内核的机器(arch.machine)的.我们的linux内核会支持一种CPU的运行,CPU+开发板就确定了一个硬件平台,然后我们当前配置的内核就在这个平台上可以运行.之前说过的

tiny4412 串口驱动分析七 --- log打印的几个阶段之内核启动阶段(earlyprintk)

作者:彭东林 邮箱:[email protected] 开发板:tiny4412ADK+S700 4GB Flash 主机:Wind7 64位 虚拟机:Vmware+Ubuntu12_04 u-boot:U-Boot 2010.12 Linux内核版本:linux-3.0.31 Android版本:android-4.1.2 下面要分析的是内核Log打印的几个阶段 自解压阶段 内核启动阶段 内核启动完全以后 shell终端下 在这个阶段内核log打印可以调用printk和printascii,同

第3阶段——内核启动分析之start_kernel初始化函数(5)

内核启动分析之start_kernel初始化函数(init/main.c) stext函数启动内核后,就开始进入start_kernel初始化各个函数, 下面只是浅尝辄止的描述一下函数的功能,很多函数真正理解需要对linux相关体系有很深的了解后才能明白 代码如下: asmlinkage void __init start_kernel(void) { char * command_line; extern struct kernel_param __start___param[], __sto

第3阶段——内核启动分析之挂载根文件系统和mtd分区介绍(6)

内核启动并初始化后,最终目的是像Windows一样能启动应用程序 在windows中每个应用程序都存在C盘.D盘等 而linux中每个应用程序是存放在根文件系统里面 那么挂载根文件系统在哪里,怎么实现最终目的运行应用程序? 1.进入stext函数启动内核 2.进入strat_kernel(): ... setup_arch(&command_line);           //解析uboot传入的启动参数 setup_command_line(command_line);    //解析ubo

tiny4412 串口驱动分析八 --- log打印的几个阶段之内核启动阶段(printk tiny4412串口驱动的注册)

作者:彭东林 邮箱:[email protected] 开发板:tiny4412ADK+S700 4GB Flash 主机:Wind7 64位 虚拟机:Vmware+Ubuntu12_04 u-boot:U-Boot 2010.12 Linux内核版本:linux-3.0.31 Android版本:android-4.1.2 在arch/arm/mach-exynos/mach-tiny4412.c中: MACHINE_START(TINY4412, "TINY4412") .boot

linux内核启动汇编部分详解

参考文档:https://blog.csdn.net/haoge921026/article/details/46785995 找到入口ENTRY(stext)开始分析 setmode PSR_F_BIT | PSR_I_BIT | SVC_MODE, r9      @ ensure svc mode     @ and irqs disabled 设置cpu为svc模式,禁止中断 mrc p15, 0, r9, c0, c0 @ get processor id 读取ARM协处理器cp15的

第3阶段——内核启动分析之创建si工程和启动内核分析(3)

目标: (1)创建Source Insight 工程,方便后面分析如何启动内核的 (2)分析uboot传递参数,链接脚本如何进入stext的 (3) 分析stext函数如何启动内核 1 创建内核source sight 工程 1.1 点击 "add all" 添加所有文件,后面再慢慢删去Arch目录和Include目录中与2440芯片没用的文件. 1.2 点击Remove Tree 删除Arch文件夹,再添加与2440相关的硬件核心代码以及其它公用的代码 Arch:包含了平台,处理器相

Tiny4412 Linux 内核启动流程

Linux内核的启动分为压缩内核和非压缩内核两种,这里我们以压缩内核为例.压缩内核运行时,将运行一段解压缩程序,得到真正的内核镜像,然后跳转到内核镜像运行.此时,Linux进入非压缩内核入口,在非压缩内核入口中,完成各种初始化操作后跳转到C语言入口处运行.主要流程如下所示. 1.解压缩内核镜像 解压缩程序通常在arch/arm/boot/compressed/目录中 ├── atags_to_fdt.c ├── big-endian.S ├── decompress.c ├── head.S ├