文章目录

  • 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处

  1. 启动内核必须要满足下面的一些条件

    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
  1. 接下来看一个比较重要的函数 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分析相关推荐

  1. Linux系统 proc self,Linux内核源代码情形分析-特殊文件系统/proc-对/proc/self/cwd的访问...

    Linux内核源代码情景分析-特殊文件系统/proc-对/proc/self/cwd的访问 继上篇文章Linux内核源代码情景分析-特殊文件系统/proc,我们对/proc/loadavg访问后,这篇 ...

  2. 2017-2018-1 20179215《Linux内核原理与分析》第二周作业

    20179215<Linux内核原理与分析>第二周作业 这一周主要了解了计算机是如何工作的,包括现在存储程序计算机的工作模型.X86汇编指令包括几种内存地址的寻址方式和push.pop.c ...

  3. Linux内核源码分析--内核启动之(4)Image内核启动(setup_arch函数)(Linux-3.0 ARMv7)【转】...

    原文地址:Linux内核源码分析--内核启动之(4)Image内核启动(setup_arch函数)(Linux-3.0 ARMv7) 作者:tekkamanninja 转自:http://blog.c ...

  4. 2018-2019-1 20189213《Linux内核原理与分析》第四周作业

    <Linux内核原理与分析>第四周学习总结: 1.课本知识总结: 本章内容并不多,首先是介绍了一些Linux内核源代码的目录结构,并基于Linux内核源代码构造一个简单的操作系统MenuO ...

  5. linux 虚拟文件系统 源码,Linux内核源代码情状分析-虚拟文件系统

    Linux内核源代码情景分析-虚拟文件系统 我们先来看两张图: 第一张是VFS与具体文件系统的关系示意图: 第二张是Linux文件系统的层次结构: 特殊文件:用来实现"管道"的文件 ...

  6. Linux内核源码分析—从用户空间复制数据到内核空间

    Linux内核源码分析-从用户空间复制数据到内核空间 本文主要参考<深入理解Linux内核>,结合2.6.11.1版的内核代码,分析从用户空间复制数据到内核空间函数. 1.不描述内核同步. ...

  7. [漏洞分析] CVE-2022-0847 Dirty Pipe linux内核提权分析

    CVE-2022-0847 Dirty Pipe linux内核提权分析 文章目录 CVE-2022-0847 Dirty Pipe linux内核提权分析 漏洞简介 环境搭建 漏洞原理 漏洞发生点 ...

  8. Linux内核LED模块分析(二)

    Linux内核LED模块分析(二) 上次分析到那里后,还是有些同志说看不懂,那我就继续分析一把我认为不需要继续分析的东西吧.上回分析了 led_cdev和trigger的关系后就没有继续说了.有同志还 ...

  9. Linux内核源码分析《进程管理》

    Linux内核源码分析<进程管理> 前言 1. Linux 内核源码分析架构 2. 进程原理分析 2.1 进程基础知识 2.2 Linux进程四要素 2.3 进程描述符 task_stru ...

  10. Linux内核汇编代码分析

    Linux内核汇编代码分析 1.vmlinux.lds.S文件分析 1.2 vmlinux.lds.S文件总体框架 1.3 代码段 1.4 只读数据段 1.5 init段 1.6 数据段 1.7 未初 ...

最新文章

  1. OpenCV学习(20) grabcut分割算法
  2. 苹果 开发者账号区别
  3. Spring Boot + Elasticsearch
  4. java对象复制到新对象_java – 使用新生成的ID将Hibernate复制对象值复制到新对象中...
  5. [0630]Tyvj 1063 数字串
  6. [Leetcode][第632题][JAVA][最小区间][堆][滑动窗口]
  7. Host key verification failed. fatal: Could not read from remote repository.Please make sure you have
  8. 《深入理解 Java 虚拟机》把这个知识点讲错了?
  9. 利用java实现浏览器功能 jdic
  10. JAVA-面向对象-多态
  11. 苹果Mac Final Cut Pro更新后,如何将视频分享到YouTube?
  12. 网页设计Dreamweaver【1】
  13. 计算机桌面提示区,win7如何把电脑桌面分成四个区域?电脑分区域显示方法
  14. 带你玩转IntelliJ IDEA 使用教程(2019图文版)
  15. 星外、云谷、ZKEYS系统大比拼,哪个比较好用
  16. Ambari安装和汉化(转)
  17. 计算机后端维护,机房智能交通后台系统运行维护内容.doc
  18. 自学编程到底有多难?
  19. [云原生专题-45]:Kubesphere云治理-基于Kubernetes 构建的企业级容器平台简介与总体架构
  20. matlab获取2的整数次幂,如何快速判断正整数是2的N次幂

热门文章

  1. 基于Asp.net、SVG技术的WebGIS研究与实现
  2. Tomcat与Eclipse连接
  3. Electron-vue dialog.messageBox 组件使用
  4. 面向对象3大特征和5大原则
  5. 通过下拉框中的随机选中项,来决定中午吃什么
  6. 计算机主机配置最好的,组装台式电脑配置清单有哪些 台式电脑什么配置好
  7. std::queue
  8. 适合大学生搜题的公众号
  9. 机器人植入情感芯片利与弊_一个机器人被植入有感情的芯片,喜欢上教授的妻子,什么电影...
  10. 字节12年测试经验,从零基础软件测试到功能测试到自动化测试到测试开发,我整理了这二份8000字入门到入职的学习指南