当前位置: 首页 > news >正文

Memory Pre-init

Ref:

http://www.wowotech.net/memory_management/mm-init-1.html

启动之前

kernel在执行第一条语句之前的内存状态:

:在 Linux 内核中,TEXT_OFFSET 是一个与内核镜像加载地址相关的概念,主要用于指定内核代码段(text segment)在物理内存中的偏移量。它通常在 ARM 和 ARM64 架构中被使用。

kernel image是否一定位于起始的main memory呢?也不一定,但是对于kernel而言,低于kernel image的内存,kernel是不会纳入到自己的内存管理系统中的。(TODO: 待确认)

汇编时代

一旦跳转到linux kernel执行,内核则完全掌控了内存系统的控制权。

在体系结构相关的汇编初始化阶段,我们会准备二段地址的页表:一段是identity mapping,其实就是把地址等于物理地址的那些虚拟地址mapping到物理地址上去,打开MMU相关的代码需要这样的mapping(别的CPU不知道,但是ARM ARCH强烈推荐这么做的)。第二段是kernel image mapping,内核代码欢快的执行当然需要将kernel running需要的地址(kernel txt、rodata、data、bss等等)进行映射了。具体的映射情况可以参考下图:

turn on MMU相关的代码被放入到一个特别的section,名字是.idmap.text,实际上对应上图中物理地址空间的IDMAP_TEXT这个block。这个区域的代码被mapping了两次,做为kernel image的一部分,它被映射到了__idmap_text_start开始的虚拟地址上去,此外,假设IDMAP_TEXT block的物理地址是A地址,那么它还被映射到了A地址开始的虚拟地址上去。虽然上图中表示的A地址似乎要大于PAGE_OFFSET,不过实际上不一定需要这样的关系,这和具体处理器的实现有关。

注:

KIMAGE_VADDR = PAGE_OFFSET + TEXT_OFFSET,KIMAGE_VADDR就是_text地址(虚拟地址)。在arm64架构中,PAGE_OFFSET一般等于0xffff800000000000,而TEXT_OFFSET一般等于0x10000000,如下图:

编译器感知的是kernel image的虚拟地址(左侧)在内核的链接脚本中定义了若干的符号,都是虚拟地址。但是在内核刚开始,没有打开MMU之前,这些代码实际上是运行在物理地址上的,因此,内核起始刚开始的汇编代码基本上是PIC的,首先需要定位到页表的位置,然后在页表中填入kernel image mapping和identity mapping的页表项。页表的起始位置比较好定(bss段之后),但是具体的size还是需要思考一下的。我们要选择一个合适的size,确保能够覆盖kernel image mapping和identity mapping的地址段,然后又不会太浪费。我们以kernel image mapping为例,描述确定Tranlation table size的思考过程。假设48 bit的虚拟地址配置,4k的page size,这时候需要4级映射,地址被分成9(level 0 or PGD) + 9(level 1 or PUD) + 9(level 2 or PMD) + 9(level 3 or PTE) + 12(page offset),假设我们分配4个page分别保存Level 0到level 3的translation table,那么可以建立的最大的地址映射范围是512(level 3中有512个entry) X 4k = 2M。2M这个size当然不理想,无法容纳kernel image的地址区域,怎么办?使用section mapping,让PMD执行block descriptor,这样使用3个page就可以mapping 512 X 2M = 1G的地址空间范围。当然,这种方法有一点副作用就是:PAGE_OFFSET必须2M对齐。对于16K或者64K的page size,使用section mapping就有点不合适了,因为这时候对齐的要求太高了,对于16K page size,需要32M对齐,对于64K page size,需要512M对齐。不过,这也没有什么,毕竟这时候page size也变大了,不使用section mapping也能覆盖很大区域。例如,对于16K page size,一个16K page size中可以保存2K个entry,因此能够覆盖2K X 16K = 32M的地址范围。对于64K page size,一个64K page size中可以保存8K个entry,因此能够覆盖8K X 64K = 512M的地址范围。32M和512M基本是可以满足需求的。

结论:

swapper进程(内核空间)需要预留页表的size是和page table level相关,如果使用了section mapping,那么需要预留PGTABLE_LEVELS - 1个page。如果不使用section mapping,那么需要预留PGTABLE_LEVELS 个page。

#define IDMAP_TEXT                          \

        ALIGN_FUNCTION();                   \

        __idmap_text_start = .;                 \

        *(.idmap.text)                      \

        __idmap_text_end = .;                   \

        . = ALIGN(PAGE_SIZE);                   \

        __hyp_idmap_text_start = .;             \

        *(.hyp.idmap.text)                  \

        __hyp_idmap_text_end = .;

 

看见DTB

 内核要了解整个内存空间,arm架构是通过dtb。

 整个虚拟地址空间那么大,可以被平均分成两半(PAGE_OFFSET为界),上半部分的虚拟地址空间主要各种特定的功能,而下半部分主要用于物理内存的直接映射。对于DTB而言,我们借用了fixed-mapped address这个概念。fixed map是被linux kernel用来解决一类问题的机制,这类问题的共同特点是:(1)在很早期的阶段需要进行地址映射,而此时,由于内存管理模块还没有完成初始化,不能动态分配内存,也就是无法动态分配创建映射需要的页表内存空间。(2)物理地址是固定的,或者是在运行时就可以确定的。对于这类问题,内核定义了一段固定映射的虚拟地址,让使用fix map机制的各个模块可以在系统启动的早期就可以创建地址映射,当然,这种机制不是那么灵活,因为虚拟地址都是编译时固定分配的。

好,我们可以考虑创建第三段地址映射了,当然,要创建地址映射就要创建各个level中描述符。对于fixed-mapped address这段虚拟地址空间,由于也是位于内核空间,因此PGD当然就是复用swapper进程的PGD了(其实整个系统就一个PGD),而其他level的Translation table则是静态定义的(arch/arm64/mm/mmu.c),位于内核bss段,由于所有的Translation table都在kernel image mapping的范围内,因此内核可以毫无压力的访问,并创建fixed-mapped address这段虚拟地址空间对应的PUD、PMD和PTE的entry。所有中间level的Translation table都是在early_fixmap_init函数中完成初始化的,最后一个level则是在各个具体的模块进行的,对于DTB而言,这发生在fixmap_remap_fdt函数中。

系统对dtb的size有要求,不能大于2M,这个要求主要是要确保在创建地址映射(create_mapping)的时候不能分配其他的translation table page,也就是说,所有的translation table都必须静态定义。为什么呢?因为这时候内存管理模块还没有初始化,即便是memblock模块(初始化阶段分配内存的模块)都尚未初始化(没有内存布局的信息),不能动态分配内存。

early ioremap

除了DTB,在启动阶段,还有其他的模块也想要创建地址映射,当然,对于这些需求,内核统一采用了fixmap的机制来应对,fixmap的具体信息如下图所示:

从上面这个图片可以看出fix-mapped虚拟地址分成两段,一段是permanent fix map,一段是temporary fixmap。所谓permanent表示映射关系永远都是存在的,例如FDT区域,一旦完成地址映射,内核可以访问DTB之后,这个映射关系一直都是存在的。而temporary fixmap则不然,一般而言,某个模块使用了这部分的虚拟地址之后,需要尽快释放这段虚拟地址,以便给其他模块使用

你可能会很奇怪,因为传统的驱动模块中,大家通常使用ioremap函数来完成地址映射,为啥还有一个early IO remap呢?其实ioremap函数的使用需要一定的前提条件的,在地址映射过程中,如果某个level的Translation tabe不存在,那么该函数需要调用伙伴系统模块的接口来分配一个page size的内存来创建某个level的Translation table,但是在启动阶段,内存管理的伙伴系统还没有ready,其实这时候,内核连系统中有多少内存都不知道的。而early io remap则在early_ioremap_init之后就可以被使用了。更具体的信息请参考mm/early_ioremap.c文件。

结论:如果想要在伙伴系统初始化之前进行设备寄存器的访问,那么可以考虑early IO remap机制

内存布局

完成DTB的映射之后,内核可以访问这一段的内存了,通过解析DTB中的内容,内核可以勾勒出整个内存布局的情况(参照memblock相关文章):

看到内存

了解到了当前的物理内存的布局,但是内核仍然只是能够访问部分内存(kernel image mapping和DTB那两段内存,上图中黄色block),大部分的内存仍然处于黑暗中,等待光明的到来,也就是说需要创建这些内存的地址映射。

在这个时间点上,创建内存的地址映射有一个悖论:创建地址映射需要分配内存,但是这时候伙伴系统没有ready,无法动态分配。也许你会说,memblock不是已经ready了吗,不可以调用memblock_alloc进行物理内存的分配吗?当然可以,memblock_alloc分配的物理内存仍然需要通过虚拟地址访问(也需要分配pte页),而这些pte也还没有创建地址映射,因此内核一旦访问它,悲剧就会发生。

怎么办呢?内核采用了一个巧妙的办法:那就是控制创建地址映射,并控制memblock_alloc分配页表内存的顺序。也就是说刚开始的时候创建的地址映射不需要页表内存的分配,当内核需要调用memblock_alloc进行页表物理地址分配的时候,很多已经创建映射的内存已经ready了,这样,在调用create_mapping的时候不需要分配页表内存。更具体的解释参考下面的图片:

我们知道,在内核编译的时候,在BSS段之后分配了几个page用于swapper进程地址空间(内核空间)的映射,当然,由于kernel image不需要mapping那么多的地址,因此swapper进程translation table的最后一个level中的entry不会全部的填充完毕。换句话说:swapper进程页表可以支持远远大于kernel image mapping那一段的地址区域,实际上,它可以支持的地址段的size是SWAPPER_INIT_MAP_SIZE。为(PAGE_OFFSET,PAGE_OFFSET+SWAPPER_INIT_MAP_SIZE)这段虚拟内存创建地址映射,mapping到(PHYS_OFFSET,PHYS_OFFSET+SWAPPER_INIT_MAP_SIZE)这段物理内存的时候,调用create_mapping不会发生内存分配,因为所有的页表都已经存在了,不需要动态分配。

一旦完成了(PHYS_OFFSET,PHYS_OFFSET+SWAPPER_INIT_MAP_SIZE)这段物理内存的地址映射,这时候,终于可以自由使用memblock_alloc进行内存分配了,当然,要进行限制,确保分配的内存位于(PHYS_OFFSET,PHYS_OFFSET+SWAPPER_INIT_MAP_SIZE)这段物理内存中。完成所有memory type类型的memory region的地址映射之后,可以解除限制,任意分配memory了。而这时候,所有memory type的地址区域(上上图中绿色block)都已经可见,而这些宝贵的内存资源就是内存管理模块需要管理的对象。具体代码请参考paging_init--->map_mem函数的实现。

1

新版内核中分离了swapper_pg_dirinit_idmap_pg_dir;上面讲的swapper_pg_dir是在paging_init中创建好kernel和memory的映射之后,才切换的;而这之前,还是用的init_idmap_pg_dir页表!

2

新版内核(v6.1)貌似已经改进了memblock内存的映射方式。采用了temporary fixmap来临时创建pte页表的方式访问为swapper_pg_dir分配的页表页(pud/pmd/pte)。其在early_ioremap下方增加了几个用于分配页表项的条目,如下:

enum fixed_addresses {

    FIX_HOLE,

……

    __end_of_permanent_fixed_addresses,

    /*

     * Temporary boot-time mappings, used by early_ioremap(),

     * before ioremap() is functional.

     */

#define NR_FIX_BTMAPS       (SZ_256K / PAGE_SIZE)

#define FIX_BTMAPS_SLOTS    7

#define TOTAL_FIX_BTMAPS    (NR_FIX_BTMAPS * FIX_BTMAPS_SLOTS)

    FIX_BTMAP_END = __end_of_permanent_fixed_addresses,

    FIX_BTMAP_BEGIN = FIX_BTMAP_END + TOTAL_FIX_BTMAPS - 1,

    /*

     * Used for kernel page table creation, so unmapped memory may be used

     * for tables.

     */

    FIX_PTE,

    FIX_PMD,

    FIX_PUD,

    FIX_PGD,

    __end_of_fixed_addresses

};

这样,对memblock中的normal内存段进行页表映射时,memblock分配出来的页表物理页(pud/pmd/pte,就可以利用fixmapFIX_PTE来临时建立映射,之后可以随意访问,填充完页表entry,然后再释放fixmap供下一次页表映射使用!

http://www.dtcms.com/a/420888.html

相关文章:

  • 在国外做网站网站犯法吗成都旧房改造装修公司哪家好
  • Java并发编程: 探索synchronized的奥秘
  • 网站流量太高 如何做负载均衡wordpress英文版登陆
  • C/C++ 指针详解与各种指针定义
  • 物流企业网站建设规划书王野天个人简历
  • 重庆平台网站建设工作建站网站建设
  • 网站管家wordpress 菜单状态
  • 网站开发教程wordpress 分类目录下不显示文章
  • 网站建设最好的公司排名WordPress添加百度联盟
  • 网站建站免费空间wordpress 前端个人中心 ajax 订单 支付宝
  • 深圳设计网站公司网站wordpress foxplayer
  • 创建自己的网站要钱吗广西建设厅网站招 标 信 息
  • wordpress会员vip插件昆山网站排名优化
  • 网站建设大企业房地产企业网站开发
  • Linux进程 --- 2
  • 柳市网站设计推广网站怎么做微信接口
  • 注册自己的网站需要多少钱网站 备案查询
  • 网站界面设计策划书怎么做招远网站建设公司
  • 网站规划的原则北京建设教育协会
  • 班级网站建设的系统概述合肥建站
  • 软考 系统架构设计师系列知识点之杂项集萃(159)
  • 外国ps修图网站开发网站流程
  • 做最好的网站新新常州企业自助建站系统
  • 天津市住房和城乡建设局网站派代网
  • 大连营商环境建设局网站代理记账如何获取客户
  • 网站开发什么技术拼团网站建设
  • 朝阳网站建设公司电话上海知名装修公司排名榜
  • 中华住房和城乡建设局网站网站网页不对称
  • 广州网站优化公司金融投资网站源码
  • 亚马逊卖家可以做促销的网站网站开发实战作业答案