内核启动的汇编阶段——head.S文件
来源:互联网 发布:mysql 修改sql语句 编辑:程序博客网 时间:2024/06/05 19:30
以下内容源于朱友鹏《物联网大讲堂》课程的学习,如有侵权,请告知删除。
前奏
1、Makefile分析
(1)Makefile中刚开始定义了kernel的内核版本号。这个版本号在模块化驱动安装时会需要用到。
(2)在make编译内核时,可以通过命令行给内核makefile传参。譬如make O=xxx可以指定到另外一个单独文件夹下编译。
(3)kernel的顶层Makefile中定义了2个变量,一个是ARCH,一个是CROSS_COMPILE。
- ARCH决定当前配置编译的路径,譬如ARCH = arm的时候,将来在源码目录下去操作的arch/arm目录。
- CROSS_COMPILE用来指定交叉编译工具链的路径和前缀。
- CROSS_COMPILE = xxx,ARCH = xxx,O=xxx,这些都可以在make时,通过命令行传参的方式传给顶层Makefile。
- 因此makefile可以什么都不改,而是在命令行里面输入相应的参数(因为用了?=(如果定义了,则使用定义的,否则用默认的))。编译内核时,make O=/tmp/mykernel ARCH=arm CROSS_COMPILE=/usr/local/arm/arm-2009q3/bin/arm-none-linux-gnueabi-
2、链接脚本分析
(1)分析连接脚本的目的就是找到整个程序的entry。
(2)kernel的连接脚本并不是直接提供的,而是提供了一个汇编文件vmlinux.lds.S,然后在编译的时候再去编译这个汇编文件得到真正的链接脚本vmlinux.lds。
(3)vmlinux.lds.S在arch/arm/kernel/目录下。
(4)思考:为什么linux kernel不直接提供vmlinux.lds而要提供一个vmlinux.lds.S然后在编译时才去动态生成vmlinux.lds呢?
- .lds文件中只能写死,不能用条件编译。
- 在kernel中链接脚本时,有条件编译的需求(但是lds格式又不支持)。
- kernel工作者把vmlinux.lds写成一个汇编文件,然后汇编器处理的时候顺便把条件编译给处理了,得到一个不需要条件编译的vmlinux.lds。
(6)head.S是启用了MMU情况下的kernel启动文件,相当于uboot中的start.S。head-nommu.S是未使用mmu情况下的kernel启动文件。
head.S文件分析
1、内核运行的物理地址与虚拟地址
(1)KERNEL_RAM_VADDR(VADDR就是virtual address),这个宏定义了内核运行时的虚拟地址。值为0xC0008000
(2)KERNEL_RAM_PADDR(PADDR就是physical address),这个宏定义内核运行时的物理地址。值为0x30008000
总结:内核运行的物理地址是0x30008000,对应的虚拟地址是0xC0008000。
2、内核的真正入口 & 内核运行的硬件条件
(1)__HEAD定义了段名为.head.text的段;
(2)内核的真正入口就是ENTRY(stext)处;
(3)解压代码调用内核的起始部分代码。
- 回忆之前讲zImage的时候,uboot启动内核后,实际调用zImage前面的那段未经压缩的解压代码,解压代码运行时先将zImage后面的部分解压开,然后再去调用运行真正的内核入口。
(4)内核启动需要一定的先决条件,这个条件由启动内核的bootloader(比如uboot)来构建保证。
(5)ARM体系中,函数调用时实际是通过寄存器传参的(函数调用时传参有两种设计:一种是寄存器传参,另一种是栈内存传参)。
- uboot中最后theKernel (0, machid, bd->bi_boot_params);执行内核时,运行时实际把0放入r0中,machid放入到了r1中,bd->bi_boot_params放入到了r2中。ARM的这种处理技巧刚好满足了kernel启动的条件和要求。
(6)kernel启动时MMU是关闭的,因此硬件上需要的是物理地址。但是内核是一个整体(zImage)只能被连接到一个地址(不能分散加载),这个连接地址肯定是虚拟地址。因此head.S文件中尚未开启MMU之前的代码必须是位置无关码,而且其中涉及到操作硬件寄存器等时必须使用物理地址。
3、__lookup_processor_type与__lookup_machine_type
(1)cp15协处理器的c0寄存器中读取出硬件的CPU ID号,然后调用这个函数来进行合法性检验。
- 如果合法则继续启动,如果不合法则停止启动,转向__error_p启动失败。
(2)__lookup_processor_type检验cpu id合法性的方法
- 内核会维护一个本内核支持的CPU ID号码的数组,然后该函数将从硬件中读取到的cpu id号码和数组中存储的各个id号码依次对比,如果没有一个相等则不合法,如果有一个相等的则合法。
(3)内核启动时设计这个校验,也是为了内核启动的安全性着想。
(4)__lookup_machine_type函数的设计理念和思路和上面校验cpu id的函数一样的,不同之处是本函数校验的是机器码。
4、__vet_atags
(1)该函数的设计理念和思路和上面2个一样,不同之处是用来校验uboot给内核的传参ATAGS格式是否正确。
- 这里说的传参指的是uboot通过tag给内核传的参数(主要是板子的内存分布memtag、uboot的bootargs)
(2)uboot给内核传参的部分如果不对,会导致内核启动不起来。譬如uboot的bootargs设置不正确内核可能就会不启动。
5、__create_page_tables
(1)此函数用来建立页表。
(2)linux内核本身被连接在虚拟地址处,因此kernel希望尽快建立页表并且启动MMU进入虚拟地址工作状态。但是kernel本身工作起来后页表体系是非常复杂的。
(3)kernel建立页表分为2步。
- 第一步,kernel先建立一个段式页表(和uboot中建立的页表一样,页表以1MB为单位来区分)。此函数就是建立段式页表的。段式页表本身比较好建立(段式页表1MB一个映射,4GB空间需要4096个页表项,每个页表项4字节,因此一共需要16KB内存来做页表),但不能精细管理内存;
- 第二步,建立一个细页表(4kb为单位的细页表),然后启用新的细页表,并废除第一步建立的段式映射页表。
(4)内核启动的早期建立段式页表,并在内核启动前期使用;内核启动后期就会再次建立细页表并启用。等内核工作起来之后就只有细页表了。
6、__switch_data
(1)建立了段式页表后进入了__switch_data部分,这东西是个函数指针数组。
(2)分析得知下一步要执行__mmap_switched函数。
- 复制数据段、清除bss段(目的是构建C语言运行环境)。
- 保存起来cpu id号、机器码、tag传参的首地址。
- b start_kernel跳转到C语言运行阶段。
总结:汇编阶段主要就是校验启动合法性、建立段式映射的页表并开启MMU以方便使用内存、跳入C阶段。
- 内核启动的汇编阶段——head.S文件
- linux 内核启动 arm64 汇编 head.s
- Linux内核---4.产生内核head.s反汇编文件
- 内核启动汇编阶段分析
- linux内核启动head.s
- ARM架构内核启动分析-head.S(1.2、stext分析之准备阶段)
- <Linux>Linux内核启动分析(一)——head.S
- 内核启动的C语言阶段——start_kernel函数
- android内核启动分析之head.S
- 嵌入式 arm平台kernel启动第一阶段汇编head.s分析
- Davinci DM6446 Linux 内核分析——head.S(二)
- Linux 内核启动过程--head.S(arch/xxx/kernel下的)
- Linux内核启动过程(一):head.S学习
- 内核启动分析(三)——zImage 解压缩阶段
- Linux启动分析(2)— bootsect.S、setup.S、head.S分析
- Linux启动分析(2)— bootsect.S、setup.S、head.S分析
- Linux启动分析(2)— bootsect.S、setup.S、head.S分析
- Linux启动分析— bootsect.S、setup.S、head.S分析
- HDFS一些概念的理解
- linux_011之库函数string.c
- linux_011之库函数wait.c
- sockaddr与sockaddr_in,sockaddr_un结构体详解
- Android之ActivityLifecycleCallbacks的得到当前的activity的状态
- 内核启动的汇编阶段——head.S文件
- js和jQurey中获取select标签选中的值
- PyCharm最新2017激活码
- ES6为数组做了哪些扩展?
- Java入门学习-理解super,this,@override的用法
- 内核启动的C语言阶段——start_kernel函数
- SpringMVC和Struts2区别比较
- 简易蜘蛛池网站开发
- CF