Linux内存初始化-启动阶段的内存初始化

这篇具有很好参考价值的文章主要介绍了Linux内存初始化-启动阶段的内存初始化。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

本文代码基于ARM64平台, Linux kernel 5.15

在加载kernel 之前, kernel对于系统是有一定要求的,明确规定了boot阶段必须要把MMU关闭:

arch/arm64/kernel/head.S
 /*
  * Kernel startup entry point.
  * ---------------------------
  *
  * The requirements are:
  *   MMU = off, D-cache = off, I-cache = on or off,
  *   x0 = physical address to the FDT blob.

那么在进入kernel之后, 就必须有一个使能MMU, 建立映射的过程, 本文描述kernel启动阶段进行内存初始化相关的操作。

流程

在初始化阶段,我们mapping二段地址,一段是identity mapping,其实就是把物理地址mapping到物理地址上去,在打开MMU的时候需要这样的mapping(ARM ARCH强烈推荐这么做的)。第二段是kernel image mapping,内核代码欢快的执行当然需要将kernel running需要的地址(kernel txt、dernel rodata、data、bss等等)进行映射了。映射之后, 系统内存的状态大致如图所示:

(图中的地址为实验机器的地址, 仅供参考)

Linux内存初始化-启动阶段的内存初始化

启动入口

kernel的 启动入口是_text , 它定义在.head.text 中

arch/arm64/kernel/vmlinux.lds.S
.....
ENTRY(_text)
.....
 SECTIONS
{
         . = KIMAGE_VADDR;

         .head.text : {
                 _text = .;
                 HEAD_TEXT
         }
         .....
}

arch/arm64/kernel/head.S
         __HEAD
         /*
          * DO NOT MODIFY. Image header expected by Linux boot-loaders.
          */
         efi_signature_nop                       // special NOP to identity as PE/COFF executable
         b       primary_entry                   // branch to kernel start, magic
         .quad   0                               // Image load offset from start of RAM, little-endian
         le64sym _kernel_size_le                 // Effective size of kernel image, little-endian
         .....


SYM_CODE_START(primary_entry)
         bl      preserve_boot_args
         bl      init_kernel_el                  // w0=cpu_boot_mode
         adrp    x23, __PHYS_OFFSET
         and     x23, x23, MIN_KIMG_ALIGN - 1    // KASLR offset, defaults to 0
         bl      set_cpu_boot_mode_flag
         bl      __create_page_tables

kernel 启动后, 最终会调用到__create_page_tables这个函数, 这是创建启动页表的关键函数。

调用过程如下_text ->primary_entry->__create_page_tables

初始化准备

arch/arm64/kernel/head.S
        SYM_FUNC_START_LOCAL(__create_page_tables)
         mov     x28, lr

         /*
          * Invalidate the init page tables to avoid potential dirty cache lines
          * being evicted. Other page tables are allocated in rodata as part of
          * the kernel image, and thus are clean to the PoC per the boot
          * protocol.
          */
         adrp    x0, init_pg_dir
         adrp    x1, init_pg_end
         sub     x1, x1, x0
         bl      __inval_dcache_area
         /*
          * Clear the init page tables.
          */
         adrp    x0, init_pg_dir
         adrp    x1, init_pg_end
         sub     x1, x1, x0
 1:      stp     xzr, xzr, [x0], #16
         stp     xzr, xzr, [x0], #16
         stp     xzr, xzr, [x0], #16
         stp     xzr, xzr, [x0], #16
         subs    x1, x1, #64
         b.ne    1b

__create_page_tables 初始化时, 执行的时候会把init_pg_dir 对应区域的cache清空, 然后把对应区域的内存清零。

init_pg_dir 就是启动阶段用来映射kernel text的页表, 它的本身是位于 kenrel 的bss段

arch/arm64/kernel/vmlinux.lds.S
        BSS_SECTION(0, 0, 0)

         . = ALIGN(PAGE_SIZE);
         init_pg_dir = .;
         . += INIT_DIR_SIZE;
         init_pg_end = .;

映射IDMAP_TEXT

identity mapping实际上就是建立了整个内核使能MMU相关代码的一致性mapping,就是将物理地址所在的虚拟地址段mapping到物理地址上去。为什么这么做呢?ARM ARM文档中有一段话:

If the PA of the software that enables or disables a particular stage of address translation differs from its VA, speculative instruction fetching can cause complications. ARM strongly recommends that the PA and VA of any software that enables or disables a stage of address translation are identical if that stage of translation controls translations that apply to the software currently being executed.

由于打开MMU操作的时候,内核代码欢快的执行,这时候有一个地址映射ON/OFF的切换过程,这种一致性映射可以保证在在打开MMU那一点附近的程序代码可以平滑切换。

下面是__create_page_tables函数中处理IDMAP的部分

arch/arm64/kernel/head.S
 SYM_FUNC_START_LOCAL(__create_page_tables)
                .......
       /*
          * Create the identity mapping.
          */
         adrp    x0, idmap_pg_dir
         adrp    x3, __idmap_text_start          // __pa(__idmap_text_start)
         ldr_l   x4, idmap_ptrs_per_pgd
         mov     x5, x3                          // __pa(__idmap_text_start)
         adr_l   x6, __idmap_text_end            // __pa(__idmap_text_end)

         // 根据下面的注释可以看到, x3, x6是需要映射的虚拟地址的起始和结束地址(第三个和第四个参数),这里穿的是__idmap_text_start和__idmap_text_end对应的物理地址, 同时映射的目的物理地址也传的是x3(第六个参数)
         map_memory x0, x1, x3, x6, x7, x3, x4, x10, x11, x12, x13, x14


 /*
  * Map memory for specified virtual address range. Each level of page table needed supports
  * multiple entries. If a level requires n entries the next page table level is assumed to be
  * formed from n pages.
  *
  *      tbl:    location of page table
  *      rtbl:   address to be used for first level page table entry (typically tbl + PAGE_SIZE)
  *      vstart: start address to map
  *      vend:   end address to map - we map [vstart, vend]
  *      flags:  flags to use to map last level entries
  *      phys:   physical address corresponding to vstart - physical memory is contiguous
  *      pgds:   the number of pgd entries
  *
  * Temporaries: istart, iend, tmp, count, sv - these need to be different registers
  * Preserves:   vstart, vend, flags
  * Corrupts:    tbl, rtbl, istart, iend, tmp, count, sv
  */
         .macro map_memory, tbl, rtbl, vstart, vend, flags, phys, pgds, istart, iend, tmp, count, sv

在map的时候, 实际上是map的idmap_text_start到idmap_text_end这一段地址。那么这段地址里面有哪些内容呢?

#define IDMAP_TEXT                                      \
         . = ALIGN(SZ_4K);                               \
         __idmap_text_start = .;                         \
         *(.idmap.text)                                  \
         __idmap_text_end = .;

arch/arm64/kernel/vmlinux.lds.S
         ...........
       .text : ALIGN(SEGMENT_ALIGN) {  /* Real text segment            */
               _stext = .;             /* Text and read-only data      */
                       IRQENTRY_TEXT
                       SOFTIRQENTRY_TEXT
                       ENTRY_TEXT
                       TEXT_TEXT
                       SCHED_TEXT
                       CPUIDLE_TEXT
                       LOCK_TEXT
                       KPROBES_TEXT
                       HYPERVISOR_TEXT
                       IDMAP_TEXT
                       HIBERNATE_TEXT
                       TRAMP_TEXT
                       *(.fixup)
                       *(.gnu.warning)
               . = ALIGN(16);
               *(.got)                 /* Global offset table          */
       }

可以看到, .idmap.text 是被放在.text段中的

$ nm vmlinux | grep __idmap_text_end
ffff800010d8b760 T __idmap_text_end

$ nm vmlinux | grep __idmap_text_start
ffff800010d8b000 T __idmap_text_start
arch/arm64/kernel/head.S
        .section ".idmap.text","awx"

 /*
  * Starting from EL2 or EL1, configure the CPU to execute at the highest
  * reachable EL supported by the kernel in a chosen default state. If dropping
  * from EL2 to EL1, configure EL2 before configuring EL1.
  *
  * Since we cannot always rely on ERET synchronizing writes to sysregs (e.g. if
  * SCTLR_ELx.EOS is clear), we place an ISB prior to ERET.
  *
  * Returns either BOOT_CPU_MODE_EL1 or BOOT_CPU_MODE_EL2 in w0 if
  * booted in EL1 or EL2 respectively.

在head.S文件中, 定义了这个段, 很多内存初始化的代码都被放到了这个段里, 比如enable_mmu,primary_switch

$ nm vmlinux | grep __enable_mmu
ffff800010d8b268 T __enable_mmu

$ nm vmlinux | grep __primary_switch
ffff800010d8b330 t __primary_switch
ffff800011530330 t __primary_switched

上面可以看到, 在和使能mmu相关的代码, 实际上都被放到了__idmap_text 里面, 保证切换MMU时能够平滑切换。

Idmap 实际上被映射了两次,既映射到了其kernle text的虚拟地址, 又映射到了它的物理地址。

映射kernel

创建IDMAP之后, 还会将kernel text相关的内容进行映射, 保证kernel可以正常运行

arch/arm64/kernel/head.S
 SYM_FUNC_START_LOCAL(__create_page_tables)
                    /*
          * Map the kernel image (starting with PHYS_OFFSET).
          */
         adrp    x0, init_pg_dir
         mov_q   x5, KIMAGE_VADDR                // compile time __va(_text)
         add     x5, x5, x23                     // add KASLR displacement
         mov     x4, PTRS_PER_PGD
         adrp    x6, _end                        // runtime __pa(_end)
         adrp    x3, _text                       // runtime __pa(_text)
         sub     x6, x6, x3                      // _end - _text
         add     x6, x6, x5                      // runtime __va(_end)

         map_memory x0, x1, x5, x6, x7, x3, x4, x10, x11, x12, x13, x14

可以看到, 这里是将_text_end 这段物理地址映射到对应的虚拟地址上。

使能MMU

arch/arm64/kernel/head.S
 SYM_FUNC_START(__enable_mmu)
         mrs     x2, ID_AA64MMFR0_EL1
         ubfx    x2, x2, #ID_AA64MMFR0_TGRAN_SHIFT, 4
         cmp     x2, #ID_AA64MMFR0_TGRAN_SUPPORTED
         b.ne    __no_granule_support
         update_early_cpu_boot_status 0, x2, x3
         adrp    x2, idmap_pg_dir                                                                                               ---------------(1)
         phys_to_ttbr x1, x1
         phys_to_ttbr x2, x2
         msr     ttbr0_el1, x2                   // load TTBR0                                      ---------------(2)
         offset_ttbr1 x1, x3
         msr     ttbr1_el1, x1                   // load TTBR1                                      ---------------(3)
         isb    
         msr     sctlr_el1, x0                                                                                                      ---------------(4)
         isb
         /*
          * Invalidate the local I-cache so that any instructions fetched
          * speculatively from the PoC are discarded, since they may have
          * been dynamically patched at the PoU.
          */
         ic      iallu
         dsb     nsh
         isb
         ret
 SYM_FUNC_END(__enable_mmu)

(1)(2) 这里吧idmap_pg_dir的地址传给了ttbr0_el1;这里需要说明下, arm64 会在MMU时, 0x0000 0000 0000 0000 ~ 0xFFFF 0000 0000 0000 地址空间的内容会用ttbr0_el1 进行转换, 此时由于都是直接运行的物理地址, 所以IDMAP相关的映射全部都会走ttbr0_el1

(3) 将x1 的值赋给ttbr1_el1, 0xFFFF 0000 0000 0000 ~ 0xFFFF FFFF FFFF FFFF 空间的地址映射用用到这个寄存器, 其实就是kernel space相关的地址。在启动阶段调用__enable_mmu时, x1传的是init_pg_dir的地址。

arch/arm64/kernel/head.S
 SYM_FUNC_START_LOCAL(__primary_switch)
 #ifdef CONFIG_RANDOMIZE_BASE
         mov     x19, x0                         // preserve new SCTLR_EL1 value
         mrs     x20, sctlr_el1                  // preserve old SCTLR_EL1 value
 #endif

         adrp    x1, init_pg_dir
         bl      __enable_mmu

(4) 使能MMU, x0的值在调用__enable_mmu之前的__cpu_setup 函数中就设置好了

 文章来源地址https://www.toymoban.com/news/detail-474919.html

到了这里,关于Linux内存初始化-启动阶段的内存初始化的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包赞助服务器费用

相关文章

  • 【Vue2.0源码学习】生命周期篇-初始化阶段(initInjections)

    本篇文章介绍生命周期初始化阶段所调用的第四个初始化函数—— initInjections 。从函数名字上来看,该函数是用来初始化实例中的 inject 选项的。说到 inject 选项,那必然离不开 provide 选项,这两个选项都是成对出现的,它们的作用是:允许一个祖先组件向其所有子孙后代注

    2024年02月09日
    浏览(10)
  • 【仿写spring之ioc篇】四、实现bean的初始化阶段

    【仿写spring之ioc篇】四、实现bean的初始化阶段

    在Bean的初始化阶段有前置和后置方法,这个方法是通过BeanPostProcessor来管理的,下面我们对原有的项目结构做小小的更改。 对启动类作出修改,先检查有没有BeanPostProcessor的实现类,有的话就使用,没有就使用默认的。 第二次循环先检查是不是postProcessor,是的话就跳过就行

    2024年02月10日
    浏览(10)
  • 【Vue2.0源码学习】生命周期篇-初始化阶段(initState)

    【Vue2.0源码学习】生命周期篇-初始化阶段(initState)

    本篇文章介绍生命周期初始化阶段所调用的第五个初始化函数—— initState 。 从函数名字上来看,这个函数是用来初始化实例状态的,那么什么是实例的状态呢?在前面文章中我们略有提及,在我们日常开发中,在 Vue 组件中会写一些如 props 、 data 、 methods 、 computed 、 watc

    2024年02月09日
    浏览(12)
  • Centos /Linux环境下利用Docker 安装mysql5.7镜像(含离线安装),启动mysql镜像并初始化数据库

    Centos /Linux环境下利用Docker 安装mysql5.7镜像(含离线安装),启动mysql镜像并初始化数据库

    使用有网的服务器下载好镜像 保存下载好的镜像成tar 将镜像上传到所需服务器(放在固定位置) 导入镜像 注意名称,这里的mysql5.7和上面打包 docker save imageid mysql5.7 的名称一致,本例未改名 查看导入的镜像,并重新命名镜像 创建本地数据库目录、配置文件以及日志目录(

    2024年02月10日
    浏览(48)
  • Matlab和Python的初始化内存开销

    Matlab矩阵初始化: A=zeros(10000,10000); A=zeros(20000,20000); A=zeros(30000,30000); 错误使用 zeros 请求的 30000x30000 (6.7GB)数组超过预设的最大数组大小。创建大于此限制的数组可能需要较长时间,并且会导致 MATLAB 无响应。有关详细信息,请参阅 array size limit 或预设面板。 维数为30000及以上

    2024年02月05日
    浏览(15)
  • 初始化k8s(启动)

    初始化k8s(启动)

    一位普通的程序员,慢慢在努力变强! 温馨提示:初始化k8s的前置配置,请查看以下连接! k8s初始化前的基础配置 提示:如果你是单机,需要开启污点 本章节完成了,各位正在努力的程序员们,如果你们觉得本文章对您有用的话,或者是你学到了一些东西,希望用您那漂亮

    2024年02月11日
    浏览(10)
  • 启动Flink显示初始化状态怎么解决?

    启动Flink显示初始化状态怎么解决?

    Flink On Yarn模式 问题 思路 具体实现思路 脚本如下 flinkInitDeal.sh

    2024年02月14日
    浏览(15)
  • arm64内核内存布局-之vmemmap(page初始化)

    arm64内核内存布局-之vmemmap(page初始化)

            vmemmap是内核中page 数据的虚拟地址。针对sparse内存模型。内核申请page获取的page地址从此开始。 section的概念: SPARSEMEM内存模型引入了section的概念,可以简单将它理解为struct page的集合(数组)。内核使用struct mem_section去描述section,定义如下: 其中的section_mem_map成员

    2024年02月15日
    浏览(11)
  • 从内核源码看 slab 内存池的创建初始化流程

    从内核源码看 slab 内存池的创建初始化流程

    在上篇文章 《细节拉满,80 张图带你一步一步推演 slab 内存池的设计与实现 》中,笔者从 slab cache 的总体架构演进角度以及 slab cache 的运行原理角度为大家勾勒出了 slab cache 的总体架构视图,基于这个视图详细阐述了 slab cache 的内存分配以及释放原理。 slab cache 机制确实比

    2023年04月12日
    浏览(34)
  • 深度学习参数初始化(二)Kaiming初始化 含代码

    深度学习参数初始化(二)Kaiming初始化 含代码

    目录 一、介绍 二、基础知识 三、Kaiming初始化的假设条件  四、Kaiming初始化的简单的公式推导 1.前向传播 2.反向传播 五、Pytorch实现 深度学习参数初始化系列: (一)Xavier初始化 含代码 (二)Kaiming初始化 含代码         Kaiming初始化论文地址:https://arxiv.org/abs/1502.01

    2024年02月04日
    浏览(9)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包