__mt_alloc源码分析(9)

__pool<true>的内存分

虽然我在前面已经介绍过__mt_alloc::allocate函数,但是结合这里的多线程环境,我建议读者还是回顾一下它的代码。其实__mt_alloc::allocate函数并不关心内存池是多线程还是单线程的,它在2种环境下都采用统一的操作步骤,只是在单线程的时候其中有些步骤什么事情都不用做,于是我们前面就忽略了。而到了多线程下,每个步骤都变得有用和必需了。

 

__pool<true>::_M_get_thread_id

首先我们遇到的多线程下内存池重要的操作步骤,是获得线程的id

 

<mt_allocator.cc>

581    size_t

582    __pool<true>::_M_get_thread_id()

 

函数原型,返回值就是调用_M_get_thread_id函数的线程id

 

583    {

584      // If we have thread support and it's active we check the thread

585      // key value and return its id or if it's not set we take the

586      // first record from _M_thread_freelist and sets the key and

587      // returns it's id.

588      if (__gthread_active_p())

 

检查是否链接了多线程库,如果没有的话,会当成单线程环境处理。

 

589        {

590     void* v = __gthread_getspecific(__gnu_internal::freelist._M_key);

591     size_t _M_id = (size_t)v;

 

访问线程私有数据,并转化成size_t类型的线程id。前面已经研究过,符号__gthread_getspecificPOSIX多线程库函数pthread_getspecific的弱引用,而后者的作用也在介绍freelist的时候也研究过了。

 

592     if (_M_id == 0)

 

如果得到的线程id0,说明从未给这个线程分配过id,那么开始id分配工作。

 

593       {

594         {

 

这个“{”括号是用来限定下面__gnu_cxx::lock的锁定范围的,和前面介绍_M_initialize时的那个括号一样。

 

595           __gnu_cxx::lock sentry(__gnu_internal::freelist_mutex);

596           if (__gnu_internal::freelist._M_thread_freelist)

 

检查是否有空闲的线程id

 

597         {

598           _M_id = __gnu_internal::freelist._M_thread_freelist->_M_id;

599           __gnu_internal::freelist._M_thread_freelist

600             = __gnu_internal::freelist._M_thread_freelist->_M_next;

 

得到头一个节点的线程id,然后把这个节点移出freelist。虽然这里再没有任何指针指向这个移出的节点,但是程序并没有内存泄漏,因为freelist._M_thread_freelist_array指向了整个节点数组,而且我们还可以通过线程id找到对应的节点。

 

601         }

602         }

603 

604         __gthread_setspecific(__gnu_internal::freelist._M_key,

605                   (void*)_M_id);

 

既然这个线程分配到了一个id,那么把它设置到线程私有数据里,这样以后就可以直接获得它了。有一个问题就是,如果线程id链表为空,没有获到线程id会怎样?那么这时候_M_id的值应该是0,于是整个_M_get_thread_id函数就会返回0。读者可以思考一下事情会变成怎样。

 

606       }

607     return _M_id >= _M_options._M_max_threads ? 0 : _M_id;

 

 

返回线程id。注意到这里作了比较,如果线程id超过了规定的线程个数上限,就返回0。我觉得这属于程序的策略,不同的人会有不同的意见。比如我初始化__mt_alloc<int>只支持10个线程,而其他类型不限制(默认是4096个),那么当有第11个线程要分配int对象的时候,我想让__mt_alloc<int>怎么做?有的人会认为,既然线程id4096个,为什么不忽略用户的“错误设置”,让__mt_alloc<int>正确工作呢?而另一部分人则会持相反看法,认为用户既然设置了这个参数,就不能被忽略。

显然__mt_alloc的作者同意后一部分人的想法,忠实的遵守用户的设置,即使是程序崩溃了,也要让用户来选择是对是错。

 

608        }

609 

610      // Otherwise (no thread support or inactive) all requests are

611      // served from the global pool 0.

612      return 0;

 

如果程序不支持多线程,直接返回0

 

613    }

 

__pool<true>::_M_adjust_freelist

得到了线程id__mt_alloc::allocate会在对应bin下对应的_M_first链表里,找寻空闲块,找到之后会交给用户使用,不过交出去之前还需要调整一些设置工作,这些工作是交给_M_adjust_freelist来做的。

 

337        void

338        _M_adjust_freelist(const _Bin_record& __bin, _Block_record* __block,

339              size_t __thread_id)

340        {

341     if (__gthread_active_p())

 

检查是否支持多线程,因为在单线程下这个函数什么事情也不需要做。

 

342       {

343         __block->_M_thread_id = __thread_id;

 

设置分配出去的块的头信息,主要是记录下分配线程的id。这是因为,如果块被传递给了其他线程,并由另一个线程释放的话,__pool<true>仍需要将分配线程的used计数器减1

 

344         --__bin._M_free[__thread_id];

345         ++__bin._M_used[__thread_id];

 

调整分配线程的usedfree计数器。

 

346       }

347        }

<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`)。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值