linux内存映射起始地址,内存初始化代码分析(三):创建系统内存地址映射

本文深入分析Linux内存初始化过程中的核心部分——paging_init函数,特别是如何通过map_mem函数创建系统内存地址映射。通过对ARM64体系结构的代码解析,了解到在内存管理模块完成初始化前,使用memblock模块进行内存管理,并逐步建立从内存布局信息到实际地址映射的过程。文章详细讲解了memblock如何收集和管理内存信息,以及在创建地址映射时如何避免分配未映射的页表内存,确保内核能够正确访问和管理系统内存。
摘要由CSDN通过智能技术生成

内存初始化代码分析(三):创建系统内存地址映射

作者:linuxer 发布于:2016-11-24 12:08

分类:内存管理

一、前言

经过内存初始化代码分析(一)和内存初始化代码分析(二)的过渡,我们终于来到了内存初始化的核心部分:paging_init。当然本文不能全部解析完该函数(那需要的篇幅太长了),我们只关注创建系统内存地址映射这部分代码实现,也就是解析paging_init中的map_mem函数。

同样的,我们选择的是4.4.6的内核代码,体系结构相关的代码来自ARM64。

二、准备阶段

在进入实际的代码分析之前,我们首先回头看看目前内存的状态。偌大的物理地址空间中,系统内存占据了一段或者几段地址空间,这些信息被保存在了memblock模块中的memory type类型的数组中,数组中每一个memory region描述了一段系统内存信息(base、size以及node id)。OK,系统内存就这么多,但是也并非所有的memory type类型的数组中区域都是free的,实际上,有些已经被使用或者保留使用的内存区域需要从memory type类型的一段或者几段地址空间中摘取下来,并定义在reserved type类型的数组中。实际上,在整个系统初始化过程中(更具体的说是内存管理模块完成初始化之前),我们都需要暂时使用memblock这样一个booting阶段的内存管理模块来暂时进行内存的管理(收集内存布局信息只是它的副业),每次分配的内存都是在reserved type数组中增加一个新的item或者扩展其中一个memory region的size而已。

通过memblock模块,我们已经收集了内存布局的信息(memory type类型的数组),也知道了free memory资源的信息(memory type类型的数组减去reserved type类型的数组,从集合论的角度看,reserved type类型的数组是memory type类型的数组的真子集),但是,要管理这些珍贵的系统内存,首先要能够访问他们啊(顺便说一句:memblock中的那些数组定义的地址都是物理地址),通过前面的分析文章,我们知道有两段内存已经是可见的(完成了地址映射),一个是kernel image段,另外一个是fdt段。而广大的系统内存区域仍然在黑暗之中,等待我们去拯救(进行地址映射)。

最后,我们思考这样一个问题:是否memory type类型的数组代表了整个的系统内存的地址空间呢?当然不是,有些驱动可能会保留一段系统内存区域为自己使用,同时也不希望OS管理这段内存(或者说对OS不可见),而是自己创建该段内存的地址映射。如果你对dts中的memory reserve节点比较熟悉的话,那么实际上这样的reserved memory region是有no-map属性的。这时候,内核初始化过程中,在解析该reserved-memory节点的时候,会将该段地址从memblock模块中移除。而在map_mem函数中,为所有memory type类型的数组创建地址映射的时候,有no-map属性的那段内存地址将不会创建地址映射,也就不在OS的控制范围内了。

三、概览

创建系统内存地址映射的代码在map_mem中,如下:

static void __init map_mem(void)  {

struct memblock_region *reg;

phys_addr_t limit;

limit = PHYS_OFFSET + SWAPPER_INIT_MAP_SIZE;---------------(1)

memblock_set_current_limit(limit);

for_each_memblock(memory, reg) {------------------------(2)

phys_addr_t start = reg->base;――确定该region的起始地址

phys_addr_t end = start + reg->size; ――确定该region的结束地址

if (start >= end)--参数检查

break;

if (ARM64_SWAPPER_USES_SECTION_MAPS) {----------------(3)

if (start < limit)

start = ALIGN(start, SECTION_SIZE);

if (end < limit) {

limit = end & SECTION_MASK;

memblock_set_current_limit(limit);

}

}

__map_memblock(start, end);-------------------------(4)

}

memblock_set_current_limit(MEMBLOCK_ALLOC_ANYWHERE);----------(5)

}

(1)首先限制了当前memblock的上限。之所以这么做是因为在进行mapping的时候,如果任何一级的Translation table不存在的话都需要进行页表内存的分配。而在这个时间点上,伙伴系统没有ready,无法动态分配。当然,这时候memblock已经ready了,但是如果分配的内存都还没有创建地址映射(整个物理内存布局已知并且保存在了memblock模块中的memblock模块中,但是并非所有系统内存的地址映射都已经建立好的,而我们map_mem函数的本意就是要创建所有系统内存的mapping),内核一旦访问memblock_alloc分配的物理内存,悲剧就会发生了。怎么破?这里采用了限定memblock上限的方法。一旦设定了上限,那么memblock_alloc分配的物理内存不会高于这个上限。

设定怎样的上限呢?基本思路就是在map_mem的调用过程中,不需要分配translation table,怎么做到呢?当然是尽量利用已经静态定义好的那些页表了。PHYS_OFFSET是物理内存的起始地址,SWAPPER_INIT_MAP_SIZE 是启动阶段kernel direct mapping的size。也就是说,从PHYS_OFFSET到PHYS_OFFSET + SWAPPER_INIT_MAP_SIZE的区域,所有的页表(各个level的translation table)都已经OK,不需要分配,只需要把描述符写入页表即可。因此,如果将当前memblock分配的上限设定在这里将不会产生内存分配的动作(因为页表都已经ready)。

(2)对系统中所有的memory type的region建立对应的地址映射。由于reserved type的memory region是memory type的region的真子集,因此reserved memory 的地址映射也就一并建立了。

(3)如果不使用section map,那么我们在kernel direct mapping区域静态分配了PGD~PTE的页表,通过起始地址对齐以及对memblock limit的设定就可以保证在create_mapping()的时候不分配页表内存。但是在下面的情况下:

(A)Memory block的start或者end地址并没有对齐在2M上

(B)使用section map

在这种情况下,调用create_mapping()的时候会分配pte页表内存(没有对齐2M,无法进行section mapping)。怎么破?还好第一个memory block(也就是kernel image所在的block)的start address是必定对齐在2M地址上的,所以只要考虑end地址,这时候需要适当的缩小limit到end & SECTION_MASK就可以保证分配的页表内存是已经建立地址映射的了。

(4)__map_memblock代码如下:

static void __init __map_memblock(phys_addr_t start, phys_addr_t end)

{

create_mapping(start, __phys_to_virt(start), end - start,

PAGE_KERNEL_EXEC);

}

需要说明的是,在map_mem之后,所有之前通过__create_page_tables创建的描述符都被覆盖了,取而代之的是新的映射,并且memory attribute如下:

#define PAGE_KERNEL_EXEC    __pgprot(_PAGE_DEFAULT | PTE_UXN | PTE_DIRTY | PTE_WRITE)

大部分memory attribute保持不变(例如MT

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值