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

Linux内存管理-malloc虚拟内存到物理映射详细分析

malloc虚拟内存到物理映射详细分析

概述

本文档详细分析了Linux内核中malloc分配虚拟内存后,进行实际读写操作时如何进行物理内存映射的完整过程,重点分析了buddy分配器在其中的作用,并澄清了slab分配器的角色。

1. 背景知识

1.1 按需分页机制

  • 虚拟内存分配:malloc只分配虚拟地址空间,不立即分配物理内存
  • 延迟分配:物理内存在首次访问时通过页面故障机制按需分配
  • 优势:节省物理内存,提高系统性能

1.2 内存分配器角色

  • Buddy分配器:负责以页为单位分配物理内存,支持不同order
  • Slab分配器:负责内核小对象分配,不参与用户匿名页分配

2. 页面故障处理流程

2.1 从虚拟地址访问到do_page_fault的完整路径

当用户程序访问malloc分配但未映射的虚拟地址时,会触发以下完整的调用链:

用户程序访问虚拟地址↓
CPU检测到页表项不存在或权限不足↓
CPU产生页面故障异常(Page Fault Exception)↓
硬件保存现场并跳转到异常向量表↓
内核异常处理入口(arch/*/kernel/entry_*.S)↓
page_fault异常处理程序↓
do_page_fault() [架构相关 - arch/*/mm/fault.c]↓
handle_mm_fault() [通用处理 - mm/memory.c]↓
__handle_mm_fault()
2.1.1 硬件层面的故障触发机制

MMU页表查找过程

  1. 地址转换请求:CPU执行访存指令时,MMU开始地址转换
  2. 页表遍历:MMU按照页表层次结构查找对应的页表项
    • PGD (Page Global Directory) → PUD (Page Upper Directory) → PMD (Page Middle Directory) → PTE (Page Table Entry)
  3. 故障条件检测
    • 页表项不存在(Present位为0)
    • 权限不足(如写只读页面)
    • 访问用户页面但处于内核模式等

异常产生与处理

// 硬件自动完成的操作
1. 保存故障地址到CR2寄存器(x86)或相应寄存器
2. 保存错误码(包含故障原因)
3. 保存当前执行上下文(寄存器状态)
4. 跳转到页面故障异常处理程序入口
2.1.2 内核异常处理入口

汇编层面的处理(以x86为例):

// arch/x86/kernel/entry_64.S
ENTRY(page_fault)// 保存寄存器状态// 获取故障地址和错误码// 调用C语言处理函数call do_page_fault// 恢复现场并返回
END(page_fault)

2.2 已映射虚拟内存的访问实现

对于已经建立物理映射的虚拟内存,访问过程完全不同:

2.2.1 正常的地址转换过程
用户程序访问已映射的虚拟地址↓
MMU进行地址转换↓
查找TLB(Translation Lookaside Buffer)↓
TLB命中 → 直接获得物理地址 → 访问物理内存↓
TLB未命中 → 硬件页表遍历 → 找到有效PTE → 更新TLB → 访问物理内存
2.2.2 TLB的作用机制

TLB缓存机制

  • 目的:缓存最近使用的虚拟地址到物理地址的映射
  • 结构:硬件维护的高速缓存,包含虚拟页号和对应的物理页框号
  • 命中率:通常达到95%以上,大大提高地址转换效率

页表遍历过程(TLB未命中时):

// 硬件自动完成的页表遍历(以4级页表为例)
1. 从CR3寄存器获取PGD基地址
2. 用虚拟地址的[47:39]位索引PGD,获取PUD地址
3. 用虚拟地址的[38:30]位索引PUD,获取PMD地址  
4. 用虚拟地址的[29:21]位索引PMD,获取PTE地址
5. 用虚拟地址的[20:12]位索引PTE,获取物理页框号
6. 物理页框号 + 页内偏移[11:0] = 最终物理地址

2.3 __handle_mm_fault函数分析

位置mm/memory.c:3342

核心逻辑

static int __handle_mm_fault(struct mm_struct *mm, struct vm_area_struct *vma,unsigned long address, unsigned int flags)
{pgd_t *pgd;pud_t *pud; pmd_t *pmd;pte_t *pte;// 1. 检查是否为大页面if (unlikely(is_vm_hugetlb_page(vma)))return hugetlb_fault(mm, vma, address, flags);// 2. 逐级分配页表pgd = pgd_offset(mm, address);pud = pud_alloc(mm, pgd, address);if (!pud) return VM_FAULT_OOM;pmd = pmd_alloc(mm, pud, address);if (!pmd) return VM_FAULT_OOM;// 3. 透明大页处理if (pmd_none(*pmd) && transparent_hugepage_enabled(vma)) {int ret = create_huge_pmd(mm, vma, address, pmd, flags);if (!(ret & VM_FAULT_FALLBACK))return ret;}// 4. PTE分配与处理if (unlikely(pmd_none(*pmd)) &&unlikely(__pte_alloc(mm, vma, pmd, address)))return VM_FAULT_OOM;pte = pte_offset_map(pmd, address);return handle_pte_fault(mm, vma, address, pte, pmd, flags);
}

2.4 handle_pte_fault函数分析

位置mm/memory.c:3275

故障类型判断

static int handle_pte_fault(struct mm_struct *mm,struct vm_area_struct *vma, unsigned long address,pte_t *pte, pmd_t *pmd, unsigned int flags)
{pte_t entry = *pte;if (!pte_present(entry)) {if (pte_none(entry)) {// 匿名页面故障if (vma_is_anonymous(vma))return do_anonymous_page(mm, vma, address, pte, pmd, flags);else// 文件页面故障return do_fault(mm, vma, address, pte, pmd, flags, entry);}// 交换页面故障return do_swap_page(mm, vma, address, pte, pmd, flags, entry);}// 写保护故障(COW)if (flags & FAULT_FLAG_WRITE) {if (!pte_write(entry))return do_wp_page(mm, vma, address, pte, pmd, ptl, entry);}return 0;
}

3. 匿名页面分配详细过程

3.1 do_anonymous_page函数分析

位置mm/memory.c:2670

完整流程

static int do_anonymous_page(struct mm_struct *mm, struct vm_area_struct *vma,unsigned long address, pte_t *page_table, pmd_t *pmd,unsigned int flags)
{struct mem_cgroup *memcg;struct page *page;spinlock_t *ptl;pte_t entry;// 1. 检查共享标志if (vma->vm_flags & VM_SHARED)return VM_FAULT_SIGBUS;// 2. 只读访问使用零页面if (!(flags & FAULT_FLAG_WRITE) && !mm_forbids_zeropage(mm)) {entry = pte_mkspecial(pfn_pte(my_zero_pfn(address),vma->vm_page_prot));// 设置零页面映射goto setpte;}// 3. 准备匿名VMAif (unlikely(anon_vma_prepare(vma)))goto oom;// 4. 分配物理页面page = alloc_zeroed_user_highpage_movable(vma, address);if (!page)goto oom;// 5. 内存控制组计费if (mem_cgroup_try_charge(page, mm, GFP_KERNEL, &memcg))goto oom_free_page;// 6. 设置页面状态__SetPageUptodate(page);// 7. 构造PTE项entry = mk_pte(page, vma->vm_page_prot);if (vma->vm_flags & VM_WRITE)entry = pte_mkwrite(pte_mkdirty(entry));// 8. 获取页表锁并检查page_table = pte_offset_map_lock(mm, pmd, address, &ptl);if (!pte_none(*page_table))goto release;// 9. 更新统计计数inc_mm_counter_fast(mm, MM_ANONPAGES);// 10. 建立反向映射page_add_new_anon_rmap(page, vma, address);// 11. 提交内存控制组计费mem_cgroup_commit_charge(page, memcg, false);// 12. 添加到LRU链表lru_cache_add_active_or_unevictable(page, vma);setpte:// 13. 设置页表项set_pte_at(mm, address, page_table, entry);// 14. 更新MMU缓存update_mmu_cache(vma, address, page_table);pte_unmap_unlock(page_table, ptl);return 0;release:mem_cgroup_cancel_charge(page, memcg);page_cache_release(page);goto unlock;
oom_free_page:page_cache_release(page);
oom:return VM_FAULT_OOM;
}

3.2 关键优化机制

  1. 零页面共享:只读访问使用全局零页面,节省内存
  2. COW机制:写入时才分配真实物理页面
  3. 延迟分配:只在实际访问时分配物理内存

4. Buddy分配器系统化分析

4.1 Buddy分配器基本原理

4.1.1 核心设计思想

伙伴系统算法

  • 基本单位:以页面(通常4KB)为基本分配单位
  • 分配粒度:支持2^order个连续页面的分配(order从0到MAX_ORDER-1)
  • 伙伴关系:相同大小的两个相邻内存块互为伙伴
  • 合并机制:释放时自动与伙伴块合并,减少外部碎片
4.1.2 数据结构组织

Zone结构

struct zone {// 空闲页面链表,按order组织struct free_area free_area[MAX_ORDER];// 水位标记unsigned long watermark[NR_WMARK];// 迁移类型管理unsigned long nr_migrate_reserve_block;// 统计信息atomic_long_t vm_stat[NR_VM_ZONE_STAT_ITEMS];
};

Free Area结构

struct free_area {struct list_head free_list[MIGRATE_TYPES];  // 按迁移类型分类的空闲链表unsigned long nr_free;                       // 空闲块数量
};
4.1.3 迁移类型分类

MIGRATE_UNMOVABLE:不可移动页面

  • 内核代码页、内核数据结构
  • 页表页、内核栈等

MIGRATE_MOVABLE:可移动页面

  • 用户匿名页面(malloc分配的页面属于此类)
  • 页面缓存页面
  • 支持内存压缩和热插拔

MIGRATE_RECLAIMABLE:可回收页面

  • 文件系统缓存
  • 可以通过回写到存储设备来回收

4.2 alloc_zeroed_user_highpage_movable详细解析

4.2.1 函数调用层次与职责
alloc_zeroed_user_highpage_movable(vma, address)
├── 职责:为用户VMA分配可移动的高端内存页并清零
├── 位置:include/linux/highmem.h:180
└── 实现:调用__alloc_zeroed_user_highpage(__GFP_MOVABLE, vma, vaddr)__alloc_zeroed_user_highpage(movableflags, vma, vaddr)  
├── 职责:分配页面并清零,支持调用者指定可移动标志
├── 位置:include/linux/highmem.h:157
├── GFP标志组合:GFP_HIGHUSER | __GFP_MOVABLE
└── 实现:alloc_page_vma() + clear_user_highpage()alloc_page_vma(gfp_mask, vma, addr)
├── 职责:VMA感知的单页分配(宏定义)
├── 位置:include/linux/gfp.h:467
└── 展开为:alloc_pages_vma(gfp_mask, 0, vma, addr, numa_node_id(), false)alloc_pages_vma(gfp, order, vma, addr, node, hugepage)
├── 职责:应用NUMA策略的VMA页面分配
├── 位置:mm/mempolicy.c:1950
├── NUMA策略处理:MPOL_INTERLEAVE/PREFERRED/BIND/DEFAULT
└── 核心调用:__alloc_pages_nodemask()__alloc_pages_nodemask(gfp_mask, order, zonelist, nodemask)
├── 职责:Buddy分配器的核心入口点
├── 位置:mm/page_alloc.c:3225
├── 快速路径:get_page_from_freelist()
└── 慢速路径:__alloc_pages_slowpath()
4.2.2 GFP标志详细解析

GFP_HIGHUSER标志组合

#define GFP_HIGHUSER    (GFP_USER | __GFP_HIGHMEM)
#define GFP_USER        (GFP_RECLAIM | __GFP_IO | __GFP_FS | __GFP_HARDWALL)
#define GFP_RECLAIM     (__GFP_DIRECT_RECLAIM | __GFP_KSWAPD_RECLAIM)

标志含义解析

  • __GFP_HIGHMEM:允许从高端内存分配
  • __GFP_MOVABLE:页面可移动,支持内存压缩
  • __GFP_DIRECT_RECLAIM:允许直接回收内存
  • __GFP_KSWAPD_RECLAIM:允许唤醒kswapd进行后台回收
  • __GFP_IO:允许启动I/O操作进行回收
  • __GFP_FS:允许调用文件系统进行回收

4.3 Buddy分配器核心算法

4.3.1 快速路径分配算法

get_page_from_freelist核心逻辑

static struct page *
get_page_from_freelist(gfp_t gfp_mask, unsigned int order, int alloc_flags,const struct alloc_context *ac)
{// 1. Zone遍历策略for_each_zone_zonelist_nodemask(zone, z, zonelist, ac->high_zoneidx, ac->nodemask) {// 2. 约束检查if (!cpuset_zone_allowed(zone, gfp_mask)) continue;if (!zone_dirty_ok(zone)) continue;// 3. 水位检查mark = zone->watermark[alloc_flags & ALLOC_WMARK_MASK];if (!zone_watermark_ok(zone, order, mark, ac->classzone_idx, alloc_flags)) {// 尝试zone回收if (zone_reclaim_mode != 0) {ret = zone_reclaim(zone, gfp_mask, order);if (zone_watermark_ok(zone, order, mark, ac->classzone_idx, alloc_flags))goto try_this_zone;}continue;}try_this_zone:// 4. 实际分配page = buffered_rmqueue(ac->preferred_zone, zone, order,gfp_mask, alloc_flags, ac->migratetype);if (page) {// 5. 页面预处理if (prep_new_page(page, order, gfp_mask, alloc_flags))goto try_this_zone;return page;}}return NULL;
}
4.3.2 buffered_rmqueue分配细节

Per-CPU页面缓存机制

static struct page *buffered_rmqueue(struct zone *preferred_zone,struct zone *zone, unsigned int order,gfp_t gfp_flags, int alloc_flags,int migratetype)
{struct page *page;// 单页分配优先使用per-CPU缓存if (likely(order == 0)) {struct per_cpu_pages *pcp;struct list_head *list;local_irq_save(flags);pcp = &this_cpu_ptr(zone->pageset)->pcp;list = &pcp->lists[migratetype];// 从per-CPU缓存获取if (!list_empty(list)) {page = list_first_entry(list, struct page, lru);list_del(&page->lru);pcp->count--;} else {// 缓存为空,从伙伴系统批量获取page = rmqueue_bulk(zone, 0, pcp->batch, list,migratetype, cold);}local_irq_restore(flags);} else {// 多页分配直接从伙伴系统获取page = __rmqueue(zone, order, migratetype);}return page;
}
4.3.3 伙伴系统核心分配算法

__rmqueue函数实现

static struct page *__rmqueue(struct zone *zone, unsigned int order,int migratetype)
{struct page *page;// 1. 尝试从指定迁移类型分配page = __rmqueue_smallest(zone, order, migratetype);// 2. 指定类型不足,尝试备用类型if (unlikely(!page)) {page = __rmqueue_fallback(zone, order, migratetype);}return page;
}static struct page *__rmqueue_smallest(struct zone *zone, unsigned int order,int migratetype)
{unsigned int current_order;struct free_area *area;struct page *page;// 从请求的order开始向上查找for (current_order = order; current_order < MAX_ORDER; ++current_order) {area = &(zone->free_area[current_order]);// 检查对应迁移类型的空闲链表if (list_empty(&area->free_list[migratetype]))continue;// 找到空闲块,从链表中移除page = list_entry(area->free_list[migratetype].next,struct page, lru);list_del(&page->lru);rmv_page_order(page);area->nr_free--;// 分割大块为所需大小expand(zone, page, order, current_order, area, migratetype);return page;}return NULL;
}

4.4 内存映射与Buddy分配器的关联

4.4.1 完整的关联链路
用户程序malloc() → 分配VMA → 首次访问触发缺页↓
do_anonymous_page() → 需要物理页面↓
alloc_zeroed_user_highpage_movable() → 请求可移动用户页↓
Buddy分配器 → 从MIGRATE_MOVABLE类型分配物理页面↓
clear_user_highpage() → 清零页面内容↓
mk_pte() → 构造页表项↓
set_pte_at() → 建立虚拟地址到物理地址的映射↓
用户程序可以正常访问该虚拟地址
4.4.2 关键的设计决策

为什么选择MIGRATE_MOVABLE

  1. 内存压缩支持:可移动页面可以被迁移到其他位置,支持内存碎片整理
  2. 热插拔支持:支持内存热插拔操作
  3. 大页面分配:有利于透明大页面分配

页面清零的必要性

  1. 安全性:防止信息泄露,新分配的页面不包含之前的数据
  2. 一致性:确保malloc返回的内存内容为零
  3. 性能考虑:在分配时清零比使用时清零更高效

4.5 内存压缩与碎片整理机制

4.5.1 内存碎片问题

外部碎片的产生

  • 随着系统运行,内存分配和释放导致可用内存被分割成小块
  • 虽然总的空闲内存足够,但无法满足大块连续内存的分配需求
  • 特别影响高阶页面分配(order > 0)和透明大页面分配

碎片化的影响

// 碎片化示例:总共有足够的空闲页面,但不连续
Zone状态:
[已用][空闲1][已用][空闲1][已用][空闲2][已用]
// 需要分配4页连续内存时失败,尽管总空闲页面 >= 4页
4.5.2 内存压缩核心算法

压缩原理

  • 通过页面迁移将可移动页面集中到zone的一端
  • 将空闲页面集中到zone的另一端
  • 形成更大的连续空闲内存块

双扫描器算法

// mm/compaction.c 核心算法
static int compact_zone(struct zone *zone, struct compact_control *cc)
{// 1. 迁移扫描器:从zone开始向前扫描,寻找可移动页面unsigned long migrate_pfn = zone->zone_start_pfn;// 2. 空闲扫描器:从zone末尾向后扫描,寻找空闲页面unsigned long free_pfn = zone_end_pfn(zone);while (migrate_pfn < free_pfn) {// 3. 隔离可移动页面isolate_migratepages_range(cc, migrate_pfn, end_pfn);// 4. 隔离空闲页面作为迁移目标isolate_freepages(cc, &free_pfn, migrate_pfn);// 5. 执行页面迁移migrate_pages(&cc->migratepages, compaction_alloc,compaction_free, (unsigned long)cc, cc->mode, MR_COMPACTION);// 6. 更新扫描位置migrate_pfn = cc->migrate_pfn;}
}
4.5.3 压缩触发条件

自动触发场景

// 1. 高阶页面分配失败时
static struct page *__alloc_pages_slowpath(gfp_t gfp_mask, unsigned int order,struct alloc_context *ac)
{// 尝试直接压缩page = __alloc_pages_direct_compact(gfp_mask, order, alloc_flags, ac,mode, &contended_compaction,&deferred_compaction);if (page)return page;
}// 2. kswapd后台回收时
static void kswapd_try_to_sleep(pg_data_t *pgdat, int order,int classzone_idx)
{if (pgdat_needs_compaction && sc.nr_reclaimed > nr_attempted)wakeup_kcompactd(pgdat, order, classzone_idx);
}

压缩适用性判断

static unsigned long __compaction_suitable(struct zone *zone, int order,int alloc_flags, int classzone_idx)
{// 1. 检查水位标记if (zone_watermark_ok(zone, order, watermark, classzone_idx, alloc_flags))return COMPACT_PARTIAL;  // 无需压缩// 2. 检查碎片化程度fragindex = fragmentation_index(zone, order);if (fragindex >= 0 && fragindex <= sysctl_extfrag_threshold)return COMPACT_NOT_SUITABLE_ZONE;  // 碎片化不严重return COMPACT_CONTINUE;  // 适合压缩
}
4.5.4 压缩模式与策略

异步压缩(MIGRATE_ASYNC)

  • 不会阻塞,遇到锁竞争立即放弃
  • 只迁移容易移动的页面
  • 适用于用户态分配请求

同步压缩(MIGRATE_SYNC)

  • 可以等待锁和I/O操作
  • 能够迁移更多类型的页面
  • 适用于内核态和关键分配

轻量同步压缩(MIGRATE_SYNC_LIGHT)

  • 介于异步和同步之间
  • 平衡性能和成功率
4.5.5 压缩效果与优化

压缩成功率影响因素

// 1. 页面类型分布
MIGRATE_UNMOVABLE    // 不可移动,影响压缩效果
MIGRATE_MOVABLE      // 可移动,压缩效果好
MIGRATE_RECLAIMABLE  // 可回收,中等效果// 2. 内存使用模式
- 大量长期存在的不可移动页面会降低压缩效果
- 频繁的小内存分配释放有助于压缩
- 内存热点区域可能影响压缩性能

压缩优化机制

// 1. 延迟压缩机制
void defer_compaction(struct zone *zone, int order)
{zone->compact_considered = 0;zone->compact_defer_shift++;if (order < zone->compact_order_failed)zone->compact_order_failed = order;
}// 2. 跳过不适合的页面块
static bool suitable_migration_source(struct compact_control *cc,struct page *page)
{if (pageblock_skip_persistent(page))return false;return true;
}// 3. Per-CPU页面缓存避免碎片
static struct page *buffered_rmqueue(struct zone *preferred_zone,struct zone *zone, unsigned int order,gfp_t gfp_flags, int alloc_flags,int migratetype)
{// 单页分配优先使用per-CPU缓存,减少碎片if (likely(order == 0)) {// 从per-CPU缓存分配}
}
4.5.6 压缩与MIGRATE_MOVABLE的协同

设计协同

// 1. 分配时指定可移动类型
page = alloc_zeroed_user_highpage_movable(vma, address);
// 等价于:
page = __alloc_zeroed_user_highpage(__GFP_MOVABLE, vma, vaddr);// 2. 压缩时优先处理可移动页面
static bool suitable_migration_source(struct compact_control *cc,struct page *page)
{int migratetype = get_pageblock_migratetype(page);// 异步压缩只处理可移动页面if (cc->mode == MIGRATE_ASYNC && !migrate_async_suitable(migratetype))return false;return true;
}// 3. 迁移类型保持一致
static struct page *compaction_alloc(struct page *migratepage,unsigned long data)
{struct compact_control *cc = (struct compact_control *)data;struct page *freepage;// 从空闲页面列表获取目标页面,保持迁移类型freepage = list_entry(cc->freepages.next, struct page, lru);list_del(&freepage->lru);cc->nr_freepages--;return freepage;
}

协同效果

  1. 提高压缩成功率:可移动页面更容易被迁移
  2. 减少压缩开销:避免尝试迁移不可移动页面
  3. 保持系统稳定性:关键内核页面不受压缩影响
  4. 支持大页分配:为透明大页面创造连续内存空间

这种精心设计的内存压缩机制与MIGRATE_MOVABLE标志的协同,使得Linux能够在长期运行中有效对抗内存碎片化,保证大块内存分配的成功率。

5. 内存分配的可移动性分析

5.1 迁移类型概述

Linux内核根据页面的使用特性将内存分为不同的迁移类型,这是内存管理的重要分类机制:

// include/linux/mmzone.h
enum {MIGRATE_UNMOVABLE,      // 不可移动页面MIGRATE_MOVABLE,        // 可移动页面  MIGRATE_RECLAIMABLE,    // 可回收页面MIGRATE_PCPTYPES,       // Per-CPU页面类型数量MIGRATE_HIGHATOMIC,     // 高优先级原子分配
#ifdef CONFIG_CMAMIGRATE_CMA,            // 连续内存分配器页面
#endif
#ifdef CONFIG_MEMORY_ISOLATIONMIGRATE_ISOLATE,        // 隔离页面
#endifMIGRATE_TYPES
};

5.2 可移动页面(MIGRATE_MOVABLE)场景

5.2.1 用户空间匿名页面

典型场景

// 1. malloc分配的堆内存
void *ptr = malloc(4096);  // 触发匿名页分配// 2. 用户栈页面
int local_array[1024];     // 栈扩展时分配的页面// 3. mmap匿名映射
void *addr = mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);// 4. brk系统调用扩展堆
sbrk(4096);               // 扩展数据段

分配路径

// do_anonymous_page() -> alloc_zeroed_user_highpage_movable()
page = alloc_zeroed_user_highpage_movable(vma, address);
// 等价于使用 GFP_HIGHUSER | __GFP_MOVABLE 标志
5.2.2 页面缓存(Page Cache)

文件映射页面

// 1. 文件读取缓存
int fd = open("file.txt", O_RDONLY);
read(fd, buffer, 4096);    // 可能触发页面缓存分配// 2. mmap文件映射
void *mapped = mmap(NULL, file_size, PROT_READ, MAP_PRIVATE, fd, 0);// 3. 写回缓存
write(fd, data, 4096);     // 脏页缓存,可移动但需要写回

分配特点

  • 可以通过写回到存储设备来释放
  • 支持页面迁移和内存压缩
  • 在内存压力下可以被回收
5.2.3 用户空间共享内存

共享内存场景

// 1. System V共享内存
int shmid = shmget(IPC_PRIVATE, 4096, IPC_CREAT | 0666);
void *shm_addr = shmat(shmid, NULL, 0);// 2. POSIX共享内存
int fd = shm_open("/myshm", O_CREAT | O_RDWR, 0666);
ftruncate(fd, 4096);
void *addr = mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);// 3. tmpfs文件系统
// /dev/shm下的文件实际存储在内存中,页面可移动

5.3 不可移动页面(MIGRATE_UNMOVABLE)场景

5.3.1 内核核心数据结构

关键内核对象

// 1. 内核代码段和数据段
// 内核镜像本身,包含代码和静态数据// 2. 页表页面
pte_t *pte = pte_alloc_kernel(pmd, address);  // 页表分配// 3. 内核栈
// 每个进程/线程的内核栈,通常8KB或16KB// 4. 中断处理相关结构
// 中断向量表、中断处理程序栈等// 5. 内核关键数据结构
struct task_struct *task;     // 进程控制块
struct mm_struct *mm;         // 内存描述符
struct vm_area_struct *vma;   // VMA结构
5.3.2 DMA缓冲区

DMA内存分配

// 1. 一致性DMA内存
void *coherent_mem = dma_alloc_coherent(dev, size, &dma_handle, GFP_KERNEL);// 2. 流式DMA映射
dma_addr_t dma_addr = dma_map_single(dev, cpu_addr, size, direction);// 3. 低端内存DMA(某些老设备)
void *dma_mem = kmalloc(size, GFP_KERNEL | GFP_DMA);

不可移动原因

  • 硬件设备直接访问物理地址
  • 移动会破坏DMA传输的连续性
  • 某些设备只能访问特定地址范围
5.3.3 原子分配和紧急分配

高优先级分配

// 1. 中断上下文分配
void *ptr = kmalloc(size, GFP_ATOMIC);// 2. 自旋锁持有期间分配
spin_lock_irqsave(&lock, flags);
void *mem = kmalloc(size, GFP_ATOMIC);
spin_unlock_irqrestore(&lock, flags);// 3. 网络数据包分配
struct sk_buff *skb = alloc_skb(len, GFP_ATOMIC);
5.3.4 内核模块和驱动程序

模块内存分配

// 1. 模块加载时的代码段
// 内核模块的代码和数据段// 2. 驱动程序私有数据
struct my_device *dev = kmalloc(sizeof(*dev), GFP_KERNEL);// 3. 硬件寄存器映射
void __iomem *regs = ioremap(phys_addr, size);

5.4 可回收页面(MIGRATE_RECLAIMABLE)场景

5.4.1 Slab/SLUB分配器缓存

内核对象缓存

// 1. 文件系统相关缓存
struct inode *inode = kmem_cache_alloc(inode_cachep, GFP_KERNEL);
struct dentry *dentry = kmem_cache_alloc(dentry_cache, GFP_KERNEL);// 2. 网络协议栈缓存
struct sock *sk = sk_alloc(net, family, priority, prot, kern);// 3. 通用内核对象
void *obj = kmalloc(size, GFP_KERNEL);  // 小对象通过slab分配
5.4.2 文件系统元数据

元数据缓存

// 1. 目录项缓存(dcache)
// 文件路径解析缓存,可以重建// 2. 索引节点缓存(icache)  
// 文件元数据缓存,可以从磁盘重新读取// 3. 缓冲区头(buffer_head)
// 块设备I/O缓存,可以释放和重建

5.5 迁移类型的动态转换

5.5.1 GFP标志到迁移类型的转换
static inline int gfpflags_to_migratetype(const gfp_t gfp_flags)
{// 如果禁用了按移动性分组,所有页面都是不可移动的if (unlikely(page_group_by_mobility_disabled))return MIGRATE_UNMOVABLE;// 根据GFP标志的移动性位确定迁移类型return (gfp_flags & GFP_MOVABLE_MASK) >> GFP_MOVABLE_SHIFT;
}

标志映射关系

// GFP_KERNEL -> MIGRATE_UNMOVABLE
void *ptr1 = kmalloc(size, GFP_KERNEL);// GFP_KERNEL | __GFP_MOVABLE -> MIGRATE_MOVABLE  
void *ptr2 = kmalloc(size, GFP_KERNEL | __GFP_MOVABLE);// GFP_KERNEL | __GFP_RECLAIMABLE -> MIGRATE_RECLAIMABLE
struct kmem_cache *cache = kmem_cache_create(..., SLAB_RECLAIM_ACCOUNT, ...);
5.5.2 运行时迁移类型调整

页面块迁移类型转换

// 1. 内存碎片严重时,可能会"偷取"其他类型的页面块
static struct page *__rmqueue_fallback(struct zone *zone, unsigned int order,int start_migratetype)
{// 从其他迁移类型"借用"页面块// 并可能改变整个页面块的迁移类型
}// 2. CMA区域的动态转换
// MIGRATE_CMA <-> MIGRATE_MOVABLE 之间的转换

5.6 实际应用中的选择策略

5.6.1 应用程序内存分配

用户态分配

// 所有用户态malloc/new分配都是MIGRATE_MOVABLE
char *buffer = malloc(1024);        // 可移动
int *array = new int[1000];         // 可移动
void *mmap_mem = mmap(...);         // 可移动(匿名映射)
5.6.2 内核驱动开发

驱动程序分配策略

// 1. 一般内核数据结构 - 不可移动
struct my_device *dev = kzalloc(sizeof(*dev), GFP_KERNEL);// 2. 大块临时缓冲区 - 可考虑可移动
void *temp_buf = kmalloc(large_size, GFP_KERNEL | __GFP_MOVABLE);// 3. DMA缓冲区 - 必须不可移动
void *dma_buf = dma_alloc_coherent(dev, size, &dma_handle, GFP_KERNEL);// 4. 可回收的缓存对象
struct my_cache_obj *obj = kmem_cache_alloc(my_cache, GFP_KERNEL);

5.7 性能和稳定性考虑

5.7.1 可移动页面的优势
  1. 内存碎片整理:支持内存压缩,减少外部碎片
  2. 内存热插拔:支持内存模块的热插拔操作
  3. 大页分配:有利于透明大页面的分配
  4. 系统长期稳定性:防止内存碎片化导致的分配失败
5.7.2 不可移动页面的必要性
  1. 系统稳定性:关键内核结构不能被随意移动
  2. 硬件兼容性:DMA等硬件操作需要固定物理地址
  3. 性能考虑:避免移动开销影响关键路径
  4. 原子操作:中断上下文等不能进行复杂的页面迁移

通过这种精细的分类管理,Linux内核在保证系统稳定性的同时,最大化了内存使用效率和抗碎片化能力。

6. 页表映射建立过程

6.1 do_set_pte函数分析

位置mm/memory.c:2813

void do_set_pte(struct vm_area_struct *vma, unsigned long address,struct page *page, pte_t *pte, bool write, bool anon)
{pte_t entry;// 1. 刷新指令缓存flush_icache_page(vma, page);// 2. 构造PTE项entry = mk_pte(page, vma->vm_page_prot);if (write)entry = maybe_mkwrite(pte_mkdirty(entry), vma);// 3. 处理匿名页面if (anon) {inc_mm_counter_fast(vma->vm_mm, MM_ANONPAGES);page_add_new_anon_rmap(page, vma, address);} else {inc_mm_counter_fast(vma->vm_mm, MM_FILEPAGES);page_add_file_rmap(page);}// 4. 设置页表项set_pte_at(vma->vm_mm, address, pte, entry);// 5. 更新MMU缓存update_mmu_cache(vma, address, pte);
}

6.2 关键步骤说明

  1. PTE构造:根据页面和VMA权限构造页表项
  2. 反向映射:建立物理页面到虚拟地址的反向映射
  3. 统计更新:更新进程内存统计信息
  4. MMU同步:确保MMU缓存与页表一致

7. Slab分配器角色澄清

7.1 职责边界

  • Buddy分配器

    • 分配单位:页(4KB)
    • 用途:用户匿名页、内核页面分配
    • 特点:支持不同order,NUMA感知
  • Slab分配器

    • 分配单位:对象(小于页面)
    • 用途:内核对象缓存、kmalloc
    • 特点:对象重用,减少碎片

7.2 匿名页分配中的角色

  • 用户匿名页:完全由Buddy分配器处理
  • 页表页:通常也由Buddy分配器分配
  • Slab不参与:用户匿名页的物理内存分配

8. 性能优化要点

8.1 内存分配优化

  1. NUMA策略:合理配置NUMA内存策略
  2. 迁移类型:使用__GFP_MOVABLE支持内存压缩
  3. 透明大页:评估THP对性能的影响
  4. 预分配:对于频繁分配的场景考虑预分配

8.2 页面故障优化

  1. 预取机制:利用fault-around机制
  2. 批量处理:减少页表锁竞争
  3. 零页面共享:充分利用零页面优化

9. 流程图解与架构图

9.1 malloc到物理映射完整流程图

┌─────────────────┐
│  用户程序调用   │
│   malloc()      │
└─────────┬───────┘│▼
┌─────────────────┐
│  分配VMA结构    │
│ (虚拟地址空间)  │
└─────────┬───────┘│▼
┌─────────────────┐
│ 用户程序首次    │
│ 访问虚拟地址    │
└─────────┬───────┘│▼
┌─────────────────┐
│  CPU/MMU检测    │
│  页表项不存在   │
└─────────┬───────┘│▼
┌─────────────────┐
│  产生页面故障   │
│     异常        │
└─────────┬───────┘│▼
┌─────────────────┐
│ 硬件保存现场并  │
│ 跳转异常处理程序│
└─────────┬───────┘│▼
┌─────────────────┐
│  do_page_fault  │
│  (架构相关)     │
└─────────┬───────┘│▼
┌─────────────────┐
│ handle_mm_fault │
│   (通用处理)    │
└─────────┬───────┘│▼
┌─────────────────┐
│__handle_mm_fault│
│  (页表层次处理) │
└─────────┬───────┘│▼
┌─────────────────┐
│handle_pte_fault │
│ (PTE层故障分类) │
└─────────┬───────┘│▼
┌─────────────────┐
│do_anonymous_page│
│  (匿名页处理)   │
└─────────┬───────┘│▼
┌─────────────────┐
│alloc_zeroed_    │
│user_highpage_   │
│movable()        │
└─────────┬───────┘│▼
┌─────────────────┐
│  Buddy分配器    │
│  分配物理页面   │
└─────────┬───────┘│▼
┌─────────────────┐
│  clear_user_    │
│  highpage()     │
│   (页面清零)    │
└─────────┬───────┘│▼
┌─────────────────┐
│   mk_pte()      │
│  (构造页表项)   │
└─────────┬───────┘│▼
┌─────────────────┐
│  set_pte_at()   │
│ (建立页表映射)  │
└─────────┬───────┘│▼
┌─────────────────┐
│page_add_new_    │
│anon_rmap()      │
│ (建立反向映射)  │
└─────────┬───────┘│▼
┌─────────────────┐
│lru_cache_add_   │
│active_or_       │
│unevictable()    │
└─────────┬───────┘│▼
┌─────────────────┐
│ 用户程序可以    │
│ 正常访问该地址  │
└─────────────────┘

9.2 Buddy分配器架构图

                    Buddy分配器架构┌─────────────────────────────────────────────────┐│                   Zone管理                      ││  ┌─────────────┐ ┌─────────────┐ ┌─────────────┐││  │ZONE_DMA     │ │ZONE_NORMAL  │ │ZONE_HIGHMEM │││  └─────────────┘ └─────────────┘ └─────────────┘│└─────────────────────────────────────────────────┘│▼┌─────────────────────────────────────────────────┐│              Free Area数组                      ││  Order 0: [UNMOVABLE][MOVABLE][RECLAIMABLE]     ││  Order 1: [UNMOVABLE][MOVABLE][RECLAIMABLE]     ││  Order 2: [UNMOVABLE][MOVABLE][RECLAIMABLE]     ││  ...                                            ││  Order 10:[UNMOVABLE][MOVABLE][RECLAIMABLE]     │└─────────────────────────────────────────────────┘│▼┌─────────────────────────────────────────────────┐│              分配路径选择                       ││                                                 ││  单页分配(order=0)     多页分配(order>0)        ││         │                      │                ││         ▼                      ▼                ││  Per-CPU缓存            直接从伙伴系统           ││  ┌─────────────┐        ┌─────────────┐         ││  │CPU0 PCP     │        │__rmqueue()  │         ││  │CPU1 PCP     │        │分割大块     │         ││  │...          │        │合并小块     │         ││  └─────────────┘        └─────────────┘         │└─────────────────────────────────────────────────┘

9.3 页表映射建立过程图

虚拟地址到物理地址映射建立过程┌─────────────────┐    ┌─────────────────┐    ┌─────────────────┐
│   虚拟地址      │    │   页表层次      │    │   物理页面      │
│  0x12345000     │    │                 │    │                 │
└─────────┬───────┘    │                 │    │                 ││            │                 │    │                 │▼            │                 │    │                 │
┌─────────────────┐    │  ┌───────────┐  │    │ ┌─────────────┐ │
│  页表遍历       │───▶│  │    PGD    │  │    │ │ 物理页框    │ │
│  [47:39]→PGD    │    │  └─────┬─────┘  │    │ │   4KB       │ │
│  [38:30]→PUD    │    │        │        │    │ └─────────────┘ │
│  [29:21]→PMD    │    │        ▼        │    │                 │
│  [20:12]→PTE    │    │  ┌───────────┐  │    │ ┌─────────────┐ │
│  [11:0] →偏移   │    │  │    PUD    │  │    │ │页面内容清零 │ │
└─────────────────┘    │  └─────┬─────┘  │    │ │全部为0x00   │ ││        │        │    │ └─────────────┘ ││        ▼        │    │                 ││  ┌───────────┐  │    └─────────────────┘│  │    PMD    │  │              ▲│  └─────┬─────┘  │              ││        │        │              ││        ▼        │    ┌─────────────────┐│  ┌───────────┐  │    │  PTE项构造      ││  │    PTE    │◀─┼────│ mk_pte()        ││  │  Present=1│  │    │ 物理页框号+权限 ││  │  Write=1  │  │    │ Dirty=1         ││  │  User=1   │  │    │ Young=1         ││  └───────────┘  │    └─────────────────┘└─────────────────┘

9.4 内存分配器层次关系图

                    Linux内存分配器层次结构┌─────────────────────────────────────────────────────────┐│                    用户空间                             ││  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐     ││  │    mmap     │  │    brk      │  │   munmap    │     ││  └─────────────┘  └─────────────┘  └─────────────┘     │└─────────────────────────────────────────────────────────┘│▼┌─────────────────────────────────────────────────────────┐│                   VMA管理层                             ││  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐     ││  │ VMA分配     │  │ VMA查找     │  │ VMA合并     │     ││  └─────────────┘  └─────────────┘  └─────────────┘     │└─────────────────────────────────────────────────────────┘│▼┌─────────────────────────────────────────────────────────┐│                  页面故障处理                           ││  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐     ││  │do_page_fault│  │handle_mm_   │  │do_anonymous_│     ││  │             │  │fault        │  │page         │     ││  └─────────────┘  └─────────────┘  └─────────────┘     │└─────────────────────────────────────────────────────────┘│▼┌─────────────────────────────────────────────────────────┐│                  物理页面分配                           ││                                                         ││  ┌─────────────────────┐    ┌─────────────────────┐     ││  │   Buddy分配器       │    │   Slab分配器        │     ││  │  (页面级分配)       │    │  (对象级分配)       │     ││  │                     │    │                     │     ││  │ • 用户匿名页        │    │ • 内核对象          │     ││  │ • 页表页            │    │ • kmalloc缓存       │     ││  │ • 内核页面          │    │ • 文件系统缓存      │     ││  │                     │    │                     │     ││  └─────────────────────┘    └─────────────────────┘     │└─────────────────────────────────────────────────────────┘│▼┌─────────────────────────────────────────────────────────┐│                    物理内存                             ││  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐     ││  │  ZONE_DMA   │  │ZONE_NORMAL  │  │ZONE_HIGHMEM│     ││  └─────────────┘  └─────────────┘  └─────────────┘     │└─────────────────────────────────────────────────────────┘

9.5 TLB与页表交互示意图

                    TLB与页表交互机制┌─────────────────┐│   CPU访问       ││  虚拟地址       ││  0x12345678     │└─────────┬───────┘│▼┌─────────────────┐│   查找TLB       ││ (Translation    ││  Lookaside      ││   Buffer)       │└─────────┬───────┘│┌────┴────┐│         │TLB命中      TLB未命中│         │▼         ▼┌─────────┐ ┌─────────────────┐│直接获得 │ │   硬件页表遍历   ││物理地址 │ │                 │└─────────┘ │ 1. 读取CR3获取   ││    PGD基地址     ││ 2. 索引PGD→PUD   ││ 3. 索引PUD→PMD   ││ 4. 索引PMD→PTE   ││ 5. 获取物理页框号 │└─────────┬───────┘│▼┌─────────────────┐│   更新TLB       ││ 缓存新的映射关系│└─────────┬───────┘│▼┌─────────────────┐│   访问物理内存  ││   完成读写操作  │└─────────────────┘

10. 总结

malloc虚拟内存到物理映射的完整过程体现了Linux内存管理的精妙设计:

10.1 核心机制总结

  1. 按需分配机制

    • malloc只分配虚拟地址空间,不立即分配物理内存
    • 物理内存在首次访问时通过页面故障机制按需分配
    • 这种延迟分配策略大大节省了系统内存资源
  2. 分层处理架构

    • 硬件层:CPU/MMU检测页表项不存在,产生页面故障异常
    • 架构层:do_page_fault处理架构相关的故障细节
    • 通用层:handle_mm_fault和__handle_mm_fault处理通用逻辑
    • 具体层:do_anonymous_page等函数处理特定类型的页面故障
  3. Buddy分配器核心作用

    • 作为物理页面分配的唯一来源,负责所有用户匿名页的分配
    • 通过Zone管理、迁移类型分类和Per-CPU缓存提供高效分配
    • 支持NUMA感知分配,优化多处理器系统性能
  4. 优化机制集成

    • 零页面共享:只读访问使用全局零页面
    • COW机制:写入时才分配真实物理页面
    • TLB缓存:已映射页面通过TLB实现高速地址转换
    • 反向映射:支持内存回收和页面迁移

10.2 关键设计决策

  1. 为什么选择按需分配

    • 内存节省:避免分配未使用的物理内存
    • 启动加速:程序启动时无需等待物理内存分配
    • 缓存友好:按需分配提高内存局部性
  2. 为什么使用MIGRATE_MOVABLE

    • 支持内存压缩和碎片整理
    • 支持内存热插拔操作
    • 有利于透明大页面分配
  3. 为什么Slab不参与用户页分配

    • Slab专注于内核小对象的高效管理
    • 用户页面需要整页分配,不适合对象级管理
    • 职责分离使系统架构更清晰

10.3 性能特点分析

优势

  • 内存利用率高:只分配实际使用的物理内存
  • 启动速度快:程序启动时无需分配所有物理内存
  • 缓存友好:按需分配提高缓存局部性
  • 扩展性好:支持NUMA和大内存系统

开销

  • 首次访问延迟:页面故障处理需要时间
  • TLB刷新成本:页表更新需要刷新TLB
  • 内存碎片:可能产生外部碎片

10.4 实践意义

这种设计为现代操作系统内存管理提供了重要参考:

  1. 系统设计:分层架构和职责分离的重要性
  2. 性能优化:缓存机制和延迟分配的有效性
  3. 资源管理:按需分配和自动回收的平衡
  4. 扩展性:NUMA感知和多核优化的必要性

通过深入理解这一机制,我们可以更好地:

  • 优化应用程序的内存使用模式
  • 理解系统性能瓶颈的根源
  • 设计更高效的内存管理策略
  • 为特定场景选择合适的内存分配方案

这种“按需映射 + 伙伴分配 + 迁移压缩”的三级协同机制,使 Linux 在用户态 malloc 的每一次缺页都能以最小代价拿到物理页,同时保持全局内存的紧凑与可伸缩性,是当代操作系统在性能、功耗与资源利用率之间取得最佳权衡的典范实现。

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

相关文章:

  • 桂林网站建设内容大专自考报名入口官网
  • AMS支持的融资业务如何优化风控流程?
  • 小杰深度学习(thirteen)——视觉-经典神经网络——GoogLeNet
  • jtag转swd
  • 多语言支持应用场景实战解析
  • 手机微网站怎么设计方案陕西建省级执法人才库
  • c# 中文数字转阿拉伯数字
  • 如何自定义 Qt 日志处理并记录日志到文件
  • Spring Boot 3零基础教程,类属性绑定配置文件中的值,笔记10
  • TypeScript 基础类型
  • 鸿蒙NEXT Function Flow Runtime Kit:解锁高效并发编程的利器
  • 一个小项目的记录:PHP 分账组件
  • excel-mcp-server rocky linux简单部署
  • 网站前台模块包括什么软件wordpress js放到oss
  • ENET_INIT卡死在DMA_MODE判断
  • 蓝光3D扫描:汽车模具高精度尺寸检测与数字化质量控制实践
  • 一文对最新版本 Flink 反压机制全景深度解析(附源码)
  • 从硅谷到全球:新思科技(Synopsys)的发展史与产业深耕之路
  • 网站建设wang1314公司图案设计
  • 【AES加密专题】7.AES全局函数的编写
  • EPSON TG2016SMN:低功耗温补晶振延长电池设备续航
  • Qt C++ 教程:无边框窗体 + 自定义标题栏 + 圆角 + 拖拽拉升 + 阴影
  • 用 Gradle 实现自动化测试:集成 JUnit、TestNG,生成测试报告
  • 邵阳市住房和建设局网站西安做网站收费价格
  • 【QT界面设计学习篇】qt快速开发技巧
  • Hadoop面试题及详细答案 110题 (86-95)-- Hadoop生态系统工具
  • 基于单片机电器断路器保护器系统Proteus仿真(含全部资料)
  • 如何做天猫网站怎么做win10原版系统下载网站
  • FocusAny开源 #2:速算本Calculator
  • Typecho独立页面能否支持多个自定义永久链接路径(如 /special/ 和 /other/)