linux内核head.S分析
文章目录
- 1.1内核链接脚本分析出内核第一条指令
- 1.2head.s的分析
- 1.3内核为什么在一开始不用C,而是先用汇编语言再跳转至C程序
1.1内核链接脚本分析出内核第一条指令
链接文件怎么读可以参考下面的文章
https://blog.csdn.net/zhjica/article/details/52995536
路径:arch/arm/kernel/vmlinux.lds.S 部分内容如下
OUTPUT_ARCH(arm) 输出文件的架构
ENTRY(stext) 指的是执行的第一条指令的地址,这个是定义在head.S里面SECTIONS
{
///DISCARD/ 表示这个段内的内容不会出现在输出文件中/DISCARD/ : {...}#ifdef CONFIG_XIP_KERNEL //这里没有定义. = XIP_VIRT_ADDR(CONFIG_XIP_PHYS_ADDR);
#else. = PAGE_OFFSET + TEXT_OFFSET;//所以起始地址就是这个//PAGE_OFFSET 实际上指的内核空间的起始地址,对于32位的ARM来说,如果我们定义内核空间为1个G的话,这个PAGE_OFFSET = 0xc0000000,TEXT_OFFSET 实际上是在arc/arm/Makefile中定义的 = 0x00008000
#endif.head.text : {_text = .;HEAD_TEXT}
arc/arm/Makefile
textofs-y := 0x00008000
TEXT_OFFSET := $(textofs-y)
所以我们查看编译好的system.map的stext就是卫浴0xc0008000地址处,也就是物理地址向上32KB的地方就是内核代码段开始的地方
1.2head.s的分析
根据上面的分析内核执行的第一条代码应该是在head.S中的stext处
启动内核必须要满足下面的一些条件
bootloader必须保证MMU是关闭的,必须保证DATA_CASH 是关闭的
arc/arm/kernel/head.S 部分代码
/*
* Kernel startup entry point.
* ---------------------------
** This is normally called from the decompressor code. The requirements
* are: MMU = off, D-cache = off, I-cache = dont care, r0 = 0,
* r1 = machine nr, r2 = atags or dtb pointer.
*
* This code is mostly position independent, so if you link the kernel at
* 0xc0008000, you call this at __pa(0xc0008000).
*
* See linux/arch/arm/tools/mach-types for the complete list of machine
* numbers for r1.
*
* We're trying to keep crap to a minimum; DO NOT add any machine specific
* crap here - that's what the boot loader (or in extreme, well justified
* circumstances, zImage) is for.
*/.arm__HEAD
ENTRY(stext)ARM_BE8(setend be ) @ ensure we are in BE8 modeTHUMB( 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: )#ifdef CONFIG_ARM_VIRT_EXTbl __hyp_stub_install
#endif
#跳转到SVC模式,关闭所有中断,内核刚启动的时候有关于中断的状态还没有准备好,这个时候来中断可能会让程序崩溃
#safe_svcmode_maskall位于/arch/arm/include/asm/assembler.h中@ ensure svc mode and all interrupts maskedsafe_svcmode_maskall r9
接下来看一个比较重要的函数 bl __create_page_tables
内核在打开MMU的过程中会创建一个虚拟地址等于物理地址的映射用于过度过程。
__create_page_tables:pgtbl r4, r8 @ page table address 在下面会有分析
先分析一下r4和r8分别代表什么
#ifndef CONFIG_XIP_KERNELadr r3, 2f r3==>运行时的pc+标号2的偏移地址 = 比如当前运行的地址是0x6000_0000 那么 r3 = 标号2的相对位置 r3 = 0x6000807cldmia r3, {r4, r8} r4 == > 标号2绝对位置 0xc000_807c r8 = 0xc000_0000sub r4, r3, r4 @ (PHYS_OFFSET - PAGE_OFFSET) r4 = 0x6000_807c - 0xc0000_807c = -0x6000_0000链接地址和运行地址的偏差add r8, r8, r4 @ PHYS_OFFSET r8 = 0xc000_0000 + (-0x6000_0000) =>0x6000_0000
#elseldr r8, =PLAT_PHYS_OFFSET @ always constant in this case
#endif
标号2在下面定义了
#ifndef CONFIG_XIP_KERNEL
2: .long ..long PAGE_OFFSET
#endif
/*.macro pgtbl, rd, phys
add \rd, \phys, #TEXT_OFFSET //rd =0x60000000+0x8000 32KB
sub \rd, \rd, #PG_DIR_SIZE //rd=0x60008000-0x4000 16KB
.endm
所以 r4 = 0x60004000
//对0x60008000~0x60004000这段空间进行清零根据上面计算得到 ,这里其实就是运行时的物理地址r8 = 0x6000_0000
接着分析 __create_page_tables
__create_page_tables:pgtbl r4, r8 @ page table address 在下面会有分析* Clear the swapper page table/mov r0, r4 ==>r0 = 0x60004000mov r3, #0 ==>r3 = 0add r6, r0, #PG_DIR_SIZE ==>r6 = 0x600080001: str r3, [r0], #4 ==>0写入0x60004000 并且 r0= r0 + 4str r3, [r0], #4 ==>0写入0x60004004 并且 r0= r0 + 4str r3, [r0], #4 ==>0写入0x60004008 并且 r0= r0 + 4str r3, [r0], #4 ==>0写入0x6000400c 并且 r0= r0 + 4teq r0, r6bne 1b//把MMU的标志位加载到R7寄存器中ldr r7, [r10, #PROCINFO_MM_MMUFLAGS] @ mm_mmuflags r7 = 0xc0e接下来就是创建虚拟地址和物理地址相等的映射关系/** Create identity mapping to cater for __enable_mmu.This identity mapping will be removed by paging_init().*///下面这一块代码就是把打开MMU(__turn_mmu_on_loc),这一个代码段一一映射到内核空间上,物理地址==虚拟地址。内核在刚开始的时候还没有开始内存管理,是使用段的内存管理方式,ARM的段大概是1M的空间大小首先将打开MMU那段代码的section的起始地址放到R5,结束地址放到R6adr r0, __turn_mmu_on_loc //相对地址 r0 = 0x60008130ldmia r0, {r3, r5, r6} r3 = __turn_mmu_on_loc链接地址 r5 = __turn_mmu_on链接地址 r6= __turn_mmu_on_end虚拟地址(连接后的地址)sub r0, r0, r3 @ virt->phys offset r0 = 相对位置 -链接位置 = 地址偏差 = -0x6000_0000add r5, r5, r0 @ phys __turn_mmu_on r5 = r5 + __turn_mmu_on链接地址 = __turn_mmu_on物理地址add r6, r6, r0 @ phys __turn_mmu_on_end r6 = r6 + __turn_mmu_on_end = __turn_mmu_on_end物理地址mov r5, r5, lsr #SECTION_SHIFT r5右移1M = (这里的SECTION_SHIFT为1M大小) r5= 0x60bmov r6, r6, lsr #SECTION_SHIFT r6右移1M (1M对齐) r6= 0x60b1: orr r3, r7, r5, lsl #SECTION_SHIFT @ flags + kernel base 获得First-level descriptor 一级页表描述符 r3 = 0x60b00c0estr r3, [r4, r5, lsl #PMD_ORDER] @ identity mapping 将0x60b00c0e写入0x6000_582Ccmp r5, r6addlo r5, r5, #1 @ next sectionblo 1b
下图就描述了ARMv11中虚拟地址到物理地址的转换过程。这里就是虚拟地址到物理地址一一对应的关系。首先一级页表的基地址放在0x60004000这个地方。
在内核刚开始阶段是段映射的方式,只有一级页表。下图是turn_mmu这一段代码和物理地址一一映射的分析图。
接下来是内核image代码段映射到物理地址上。/** Map our RAM from the start to the end of the kernel .bss section./add r0, r4, #PAGE_OFFSET >> (SECTION_SHIFT - PMD_ORDER)ldr r6, =(_end - 1)orr r3, r8, r7add r6, r4, r6, lsr #(SECTION_SHIFT - PMD_ORDER)1: str r3, [r0], #1 << PMD_ORDERadd r3, r3, #1 << SECTION_SHIFTcmp r0, r6bls 1b
下面图片就是分析内核代码虚拟地址到物理地址转换的过程
接下来是映射启动参数
/** Then map boot params address in r2 if specified.* We map 2 sections in case the ATAGs/DTB crosses a section boundary.*/
mov r0, r2, lsr #SECTION_SHIFT
movs r0, r0, lsl #SECTION_SHIFT
subne r3, r0, r8
addne r3, r3, #PAGE_OFFSET
addne r3, r4, r3, lsr #(SECTION_SHIFT - PMD_ORDER)
orrne r6, r7, r0
strne r6, [r3], #1 << PMD_ORDER
addne r6, r6, #1 << SECTION_SHIFT
strne r6, [r3]
映射关系如下如所示
创建完临时页表之后 会把__mmap_switched 函数指针存在r13中
ldr r13, =__mmap_switched @ address to jump to after
之后就会 b __enable_mmu
在这个函数中最后会跳转到R13,也就是__mmap_switched函数中
在这个函数中会跳转到start_kernel中。
__mmap_switched在arc/arm/kernel/head-common.S这个文件中定义
arc/arm/kernel/head-common.S
.align 2.type __mmap_switched_data, %object
__mmap_switched_data:.long __data_loc @ r4.long _sdata @ r5.long __bss_start @ r6.long _end @ r7.long processor_id @ r4.long __machine_arch_type @ r5.long __atags_pointer @ r6
#ifdef CONFIG_CPU_CP15.long cr_alignment @ r7
#else.long 0 @ r7
#endif.long init_thread_union + THREAD_START_SP @ sp.size __mmap_switched_data, . - __mmap_switched_data__mmap_switched:
//__mmap_switched_data 把上面很多值装到相应寄存器中adr r3, __mmap_switched_dataldmia r3!, {r4, r5, r6, r7}cmp r4, r5 @ Copy data segment if needed
1: cmpne r5, r6ldrne fp, [r4], #4strne fp, [r5], #4bne 1b
//清零BSS段mov fp, #0 @ Clear BSS (and zero fp)
1: cmp r6, r7strcc fp, [r6],#4bcc 1bARM( ldmia r3, {r4, r5, r6, r7, sp})THUMB( ldmia r3, {r4, r5, r6, r7} )THUMB( ldr sp, [r3, #16] )str r9, [r4] @ Save processor IDstr r1, [r5] @ Save machine typestr r2, [r6] @ Save atags pointercmp r7, #0strne r0, [r7] @ Save control register values//接下来就会跳转到start_kernel函数b start_kernel
ENDPROC(__mmap_switched)
至此程序会跳转到start_kernel处。
1.3内核为什么在一开始不用C,而是先用汇编语言再跳转至C程序
C语言的执行需要有堆栈。使用汇编语言设置C程序的堆栈,也就是SP,然后才可以跳转到C语言处执行
如果觉得对你有帮助,可以关注微信公众号 死磕linux 获取更多精彩内容。
linux内核head.S分析相关推荐
- Linux系统 proc self,Linux内核源代码情形分析-特殊文件系统/proc-对/proc/self/cwd的访问...
Linux内核源代码情景分析-特殊文件系统/proc-对/proc/self/cwd的访问 继上篇文章Linux内核源代码情景分析-特殊文件系统/proc,我们对/proc/loadavg访问后,这篇 ...
- 2017-2018-1 20179215《Linux内核原理与分析》第二周作业
20179215<Linux内核原理与分析>第二周作业 这一周主要了解了计算机是如何工作的,包括现在存储程序计算机的工作模型.X86汇编指令包括几种内存地址的寻址方式和push.pop.c ...
- Linux内核源码分析--内核启动之(4)Image内核启动(setup_arch函数)(Linux-3.0 ARMv7)【转】...
原文地址:Linux内核源码分析--内核启动之(4)Image内核启动(setup_arch函数)(Linux-3.0 ARMv7) 作者:tekkamanninja 转自:http://blog.c ...
- 2018-2019-1 20189213《Linux内核原理与分析》第四周作业
<Linux内核原理与分析>第四周学习总结: 1.课本知识总结: 本章内容并不多,首先是介绍了一些Linux内核源代码的目录结构,并基于Linux内核源代码构造一个简单的操作系统MenuO ...
- linux 虚拟文件系统 源码,Linux内核源代码情状分析-虚拟文件系统
Linux内核源代码情景分析-虚拟文件系统 我们先来看两张图: 第一张是VFS与具体文件系统的关系示意图: 第二张是Linux文件系统的层次结构: 特殊文件:用来实现"管道"的文件 ...
- Linux内核源码分析—从用户空间复制数据到内核空间
Linux内核源码分析-从用户空间复制数据到内核空间 本文主要参考<深入理解Linux内核>,结合2.6.11.1版的内核代码,分析从用户空间复制数据到内核空间函数. 1.不描述内核同步. ...
- [漏洞分析] CVE-2022-0847 Dirty Pipe linux内核提权分析
CVE-2022-0847 Dirty Pipe linux内核提权分析 文章目录 CVE-2022-0847 Dirty Pipe linux内核提权分析 漏洞简介 环境搭建 漏洞原理 漏洞发生点 ...
- Linux内核LED模块分析(二)
Linux内核LED模块分析(二) 上次分析到那里后,还是有些同志说看不懂,那我就继续分析一把我认为不需要继续分析的东西吧.上回分析了 led_cdev和trigger的关系后就没有继续说了.有同志还 ...
- Linux内核源码分析《进程管理》
Linux内核源码分析<进程管理> 前言 1. Linux 内核源码分析架构 2. 进程原理分析 2.1 进程基础知识 2.2 Linux进程四要素 2.3 进程描述符 task_stru ...
- Linux内核汇编代码分析
Linux内核汇编代码分析 1.vmlinux.lds.S文件分析 1.2 vmlinux.lds.S文件总体框架 1.3 代码段 1.4 只读数据段 1.5 init段 1.6 数据段 1.7 未初 ...
最新文章
- OpenCV学习(20) grabcut分割算法
- 苹果 开发者账号区别
- Spring Boot + Elasticsearch
- java对象复制到新对象_java – 使用新生成的ID将Hibernate复制对象值复制到新对象中...
- [0630]Tyvj 1063 数字串
- [Leetcode][第632题][JAVA][最小区间][堆][滑动窗口]
- Host key verification failed. fatal: Could not read from remote repository.Please make sure you have
- 《深入理解 Java 虚拟机》把这个知识点讲错了?
- 利用java实现浏览器功能 jdic
- JAVA-面向对象-多态
- 苹果Mac Final Cut Pro更新后,如何将视频分享到YouTube?
- 网页设计Dreamweaver【1】
- 计算机桌面提示区,win7如何把电脑桌面分成四个区域?电脑分区域显示方法
- 带你玩转IntelliJ IDEA 使用教程(2019图文版)
- 星外、云谷、ZKEYS系统大比拼,哪个比较好用
- Ambari安装和汉化(转)
- 计算机后端维护,机房智能交通后台系统运行维护内容.doc
- 自学编程到底有多难?
- [云原生专题-45]:Kubesphere云治理-基于Kubernetes 构建的企业级容器平台简介与总体架构
- matlab获取2的整数次幂,如何快速判断正整数是2的N次幂
热门文章
- 基于Asp.net、SVG技术的WebGIS研究与实现
- Tomcat与Eclipse连接
- Electron-vue dialog.messageBox 组件使用
- 面向对象3大特征和5大原则
- 通过下拉框中的随机选中项,来决定中午吃什么
- 计算机主机配置最好的,组装台式电脑配置清单有哪些 台式电脑什么配置好
- std::queue
- 适合大学生搜题的公众号
- 机器人植入情感芯片利与弊_一个机器人被植入有感情的芯片,喜欢上教授的妻子,什么电影...
- 字节12年测试经验,从零基础软件测试到功能测试到自动化测试到测试开发,我整理了这二份8000字入门到入职的学习指南