__mt_alloc源码分析(3)

本文深入探讨了__common_pool和__per_type_pool两种内存池策略。介绍了__common_pool作为基类的作用及其实现细节,随后详细解析了__per_type_pool如何为不同类型提供定制化的内存管理。
struct __common_pool

__common_pool__common_pool_base的基类,它作用就是定义pool_type和实现_S_get_pool函数。

 

382    template<template <bool> class _PoolTp, bool _Thread>

383      struct __common_pool

 

__common_pool的原型,模板参数_PoolTp是实际的内存池类,_Thread表示是否支持多线程。

 

384      {

385        typedef _PoolTp<_Thread>      pool_type;

386       

387        static pool_type&

388        _S_get_pool()

389        {

390     static pool_type _S_pool;

391     return _S_pool;

 

前面说过,common pool是一种内存池策略,即所有的对象类型使用同一个内存池,那么这个内存池就是上面的_S_pool

 

392        }

393      };

 

写到这里,我突然很有兴趣看看per type pool的架构和实现,是否也是类似的。前面已经对比过__per_type_pool_policy__common_pool_policy,那么我们就从__per_type_pool_base开始看起吧。

 

struct __per_type_pool_basestruct __per_type_pool

__per_type_pool_base的代码位于490543之间,和__common_pool_base的代码几乎一模一样,除了名字不同和多了一个模板参数_Tp,所以我不想浪费篇幅陈列这些重复的代码。相比之下,__per_type_pool的代码就有趣得多,所以这个小节的重点是__per_type_pool

 

464    template<typename _Tp, template <bool> class _PoolTp, bool _Thread>

465      struct __per_type_pool

466      {

467        typedef _Tp           value_type;

468        typedef _PoolTp<_Thread>      pool_type;

 

首先,__per_type_pool__common_pool多了一个value_type,这是分配对象的类型。其次,__per_type_pool针对每种_Tp(也就是value_type)都会实例化成不同的类型,所以下面的静态函数_S_get_pool和返回的静态变量_S_pool对于每种_Tp都是不同的。这就实现了对_Tp类型的定制内存池。

 

470        static pool_type&

471        _S_get_pool()

472        {

473     // Sane defaults for the _PoolTp.

474     typedef typename pool_type::_Block_record _Block_record;

 

_Block_record本来应该在后面研究的,不过这里可以先介绍一下。它实际上就是一个内存块的“头信息”,典型的唯一成员就是指向下一个块的指针next

 

475     const static size_t __a = (__alignof__(_Tp) >= sizeof(_Block_record)

476                    ? __alignof__(_Tp) : sizeof(_Block_record));

 

关键字__alignof__用于查看某个类型在具体平台上的对齐字节数,它的语法和sizeof一样,详细信息可以参考http://www.delorie.com/gnu/docs/gcc/gcc_61.html。在流行的32位机器平台上,sizeof(_Block_record)值为4__alignof__(_Tp)的值可能为1248__a等于较大者(48)。

考虑一下,每个空闲内存块由2部分组成,头信息(_Block_record)和数据(_Tp),由于在mt allocator里,头信息不能被数据覆盖(因为多线程下还要记录线程id,并且是重用了_Block_record中的next指针),所以数据区究竟在整个块的什么位置,是编译器参考了_Tp类型的对齐规则之后决定的事情,所以这段代码就是为了准确的得到这个偏移量。

 

478     typedef typename __pool_base::_Tune _Tune;

479     static _Tune _S_tune(__a, sizeof(_Tp) * 64,

480                  sizeof(_Tp) * 2 >= __a ? sizeof(_Tp) * 2 : __a,

481                  sizeof(_Tp) * size_t(_Tune::_S_chunk_size),

482                  _Tune::_S_max_threads,

483                  _Tune::_S_freelist_headroom,

484                  getenv("GLIBCXX_FORCE_NEW") ? true : false);

 

__pool_base::_Tune是内存池控制参数的类型,里面包含了7个参数,通过构造函数一一赋值,每个参数的意义在前面已经做过介绍(参考mt allocator原理介绍)。静态变量_S_tune最后会根据_Tp的类型合适的初始化它的每个参数值。

 

485     static pool_type _S_pool(_S_tune);

 

用定制的控制参数_S_tune构造定制的内存池_S_pool

 

486     return _S_pool;

487        }

488      };

<think> 我们正在讨论的是ARM架构下的内存管理,特别是`arch_reserve_mmu`函数的作用。根据引用[1]中提到的`map_desc`结构体,它用于描述内存映射的区域(虚拟地址、物理页帧号、长度、类型)。在ARM Linux内核启动过程中,会建立初始的页表映射,而`arch_reserve_mmu`函数很可能与这个初始映射的预留有关。 ### `arch_reserve_mmu`函数的作用分析 1. **内存映射预留** 在ARM内核启动的早期阶段,需要为初始页表分配内存。`arch_reserve_mmu`函数通常负责在内存中预留一块区域,用于存放初始页表(即一级页表PGD或段映射表)。这样,在启用MMU之前,页表已经准备就绪。 2. **避免内存覆盖** 初始页表的位置必须位于不会被内核覆盖的区域。通常,这块区域位于内核镜像之后,例如在`.bss`段结束后的位置。通过`arch_reserve_mmu`,我们明确保留这块内存,防止后续的内存分配(如`memblock`分配器)使用它。 3. **与`map_desc`的关系** 引用[1]中的`map_desc`结构体描述了需要映射的物理区域。`arch_reserve_mmu`可能利用这些映射信息,计算页表所需的大小并预留空间。例如: - 每个`map_desc`条目对应一个连续映射区域。 - 页表大小取决于映射的条目数和页表层级(ARMv7使用段映射,每个条目映射1MB)。 4. **具体实现位置** 在ARM Linux内核源码中,`arch_reserve_mmu`可能位于`arch/arm/mm/mmu.c`。其典型实现如下: ```c void __init arch_reserve_mmu(void) { /* 计算初始页表所需大小 */ size_t pgd_size = PTRS_PER_PGD * sizeof(pgd_t); /* 预留内存 */ initial_pg_dir = memblock_alloc(pgd_size, pgd_size); } ``` 5. **启动流程中的调用时机** 如引用[2]所示,在U-Boot或类似bootloader中,启动流程会依次调用初始化函数。在内核启动时: - `start_kernel` → `setup_arch` → `paging_init` → `prepare_page_table` → `map_lowmem`等。 - `arch_reserve_mmu`可能在`setup_arch`的早期被调用,确保页表内存被预留。 ### 总结 | **功能** | **说明** | |-------------------|--------------------------------------------------------------| | 页表内存预留 | 为初始页表分配物理内存,避免被其他模块占用[^1] | | 位置选择 | 通常位于内核镜像之后(如`.bss`段末尾),保证启用MMU前可访问 | | 大小计算 | 根据映射条目数(如`map_desc`数组)计算页表所需空间 | | 与启动流程集成 | 在`setup_arch`中调用,确保后续映射建立时有可用页表内存 | > **关键点**: > `arch_reserve_mmu`的核心目的是**为初始页表安全预留物理内存**,确保MMU启用时页表已就位且不被覆盖。这是ARM架构启动过程中内存管理的关键一步。 §§相关问题§§ 1. ARM Linux内核在启用MMU前如何保证页表内存的物理地址可访问? 2. 如何动态调整`arch_reserve_mmu`预留的内存大小以适应不同的映射需求? 3. 在支持LPAE(大物理地址扩展)的ARMv8系统中,`arch_reserve_mmu`有何变化? [^1]: ARM内存映射描述结构体`map_desc`的作用 [^2]: 系统启动流程中初始化函数的执行顺序 [^3]: 显示相关函数示例(虽不直接相关,但体现系统初始化流程) </think> ### `arch_reserve_mmu`函数的作用分析 `arch_reserve_mmu`是ARM架构中与**内存映射预留**相关的关键函数,主要作用是为MMU(内存管理单元)的初始页表分配保留内存区域。其核心功能如下: #### 1. **页表内存预留** - 在系统启动早期(如U-Boot或内核初始化阶段),为MMU的**初始页表**分配物理内存空间[^1]。 - 通过`memblock`或类似机制保留一块未映射的物理内存,防止被其他模块占用。 - 典型实现逻辑: ```c /* arch/arm/mm/mmu.c */ void __init arch_reserve_mmu(void) { /* 计算页表所需大小(通常为16KB~4MB)*/ size_t pgd_size = PTRS_PER_PGD * sizeof(pgd_t); /* 通过memblock保留物理内存 */ initial_pg_dir = memblock_alloc(pgd_size, pgd_size); } ``` #### 2. **映射描述符准备** - 为后续`map_desc`结构体初始化提供基础(引用[1]): ```c struct map_desc { unsigned long virtual; // 虚拟地址起始 unsigned long pfn; // 物理页帧号 unsigned long length; // 映射长度 unsigned int type; // 内存类型索引 }; ``` - 函数内部会预留内存用于存储这些映射描述符,描述内核关键区域(如代码段、设备内存等)的映射关系[^1]。 #### 3. **启动流程中的调用时机** - 在ARM启动序列中(引用[2]的`init_sequence`),该函数通常在MMU初始化前被调用: ``` start_armboot() → init_sequence[] → arch_reserve_mmu() // 预留页表内存 → mmu_init() // 建立实际页表 ``` - **关键作用**:确保在启用MMU前,页表所需物理内存已被保留且未被污染。 #### 4. **内存类型与属性配置** - 通过参数`type`关联`mem_types[]`数组(引用[1]),决定内存区域的缓存策略和访问权限: ```c static struct mem_type { pteval_t prot_pte; // 页表属性 pteval_t prot_pte_s2; // Hyp模式属性 pmdval_t prot_l1; // 段映射属性 ... } mem_types[]; ``` - 例如:为DDR内存配置`MT_MEMORY`类型(可缓存、可执行),为设备内存配置`MT_DEVICE`类型(不可缓存)。 ### 总结对比表 | **功能** | **具体作用** | **影响范围** | |-------------------|-----------------------------------------------------------|--------------------------| | 页表内存保留 | 分配物理内存存放初始页表 | 确保MMU启用前有有效页表 | | 映射描述符初始化 | 准备`map_desc`结构体,定义虚拟-物理映射关系 | 内核关键区域映射的基础 | | 内存属性配置 | 通过`mem_types`设置缓存/访问权限 | 影响系统性能和稳定性 | | 启动流程保障 | 在MMU初始化前预留资源,防止内存被覆盖 | 系统启动可靠性的关键 | > **关键点**: > `arch_reserve_mmu`的核心价值在于**为MMU建立安全的运行环境**,通过预留物理内存和定义映射规则,确保后续`create_mapping()`等函数能正确建立虚拟地址映射(如将DDR物理地址`0x10000000`映射到`0x80000000`)。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值