内存申请底层逻辑
内存申请的本质是向操作系统请求虚拟地址空间并映射到物理内存。主要分为栈内存(自动管理)和堆内存(手动/自动管理)。堆内存通过系统调用(如Linux的brk()和mmap())获取,并由分配器(如ptmalloc)管理空闲块。虚拟地址通过MMU映射到物理页,采用延迟分配优化性能。内存碎片分为内部和外部碎片,现代分配器通过算法缓解。嵌入式平台因无MMU而不同,通常采用静态分配或内存池。理解应用层、C库
内存申请(Memory Allocation)的本质是向操作系统或运行时请求一块连续的虚拟地址空间,并将其映射到可用的物理内存(或交换空间)。下面从底层到上层,分层说明其原理:
一、两种基本申请路径
| 类型 | 管理方式 | 生命周期 | 典型场景 |
|---|---|---|---|
| 栈内存(Stack) | 编译器自动管理,CPU通过SP/BP寄存器维护 | 随函数调用分配,返回即释放 | 局部变量、函数参数 |
| 堆内存(Heap) | 程序员/垃圾回收器手动/自动管理 | 从申请到显式释放或进程结束 | malloc、new、动态数据结构 |
二、堆内存申请的系统调用层
程序调用 malloc 时,并不会每次都直接找操作系统要内存,而是分两级:
1. 向操作系统"批发"内存(系统调用)
Linux 下主要有两个系统调用:
brk():调整程序数据段的结束地址(Break),向上扩展堆顶。适合申请小块连续内存(通常 < 128KB,阈值可配置)。mmap():在虚拟地址空间中单独映射一块匿名页。适合申请大块内存(>= 128KB),或需要独立释放的大对象。
Windows 对应的是
VirtualAlloc,机制类似。
2. 用户态内存分配器"零售"(glibc ptmalloc / jemalloc / tcmalloc)
操作系统以页(通常 4KB)为单位分配,但程序可能只需要 24 字节。因此 C 库的 malloc 实现(如 ptmalloc)会维护复杂的空闲块管理结构:
- 空闲链表(Bins):将释放的内存块按大小分类缓存,下次申请时优先复用。
- 切割与合并:大块切小分配;相邻空闲块合并,减少碎片。
- 线程缓存(Thread Cache):每个线程先用自己的缓存池拿内存,避免全局锁竞争。
三、虚拟内存映射机制
操作系统返回给程序的地址都是虚拟地址,需要通过 MMU(内存管理单元) 映射到物理内存:
虚拟地址空间
├─ 已分配页 ──→ 页表 ──→ 物理页框(RAM)
├─ 已分配页 ──→ 页表 ──→ 未映射(首次访问触发缺页中断,才分配物理页)
└─ 未分配页 ──→ 访问时触发 Segmentation Fault
关键优化:延迟分配(Demand Paging)
malloc/mmap申请时,内核往往只记录"这块虚拟地址归你了",并不立即分配物理页。- 直到真正读写该内存时,触发缺页中断(Page Fault),内核才分配物理页框并建立映射。
- 这使得申请大内存的成本很低,物理内存只在实际使用时消耗。
四、内存碎片问题
| 碎片类型 | 原因 | 后果 |
|---|---|---|
| 内部碎片 | 分配粒度对齐(如只要 10 字节,给了 16 字节) | 已分配块内部浪费 |
| 外部碎片 | 频繁申请/释放不同大小的块,导致空闲块不连续 | 总空闲内存够,但无法满足大块连续申请 |
现代分配器通过伙伴系统(Buddy System)、slab 分配器、地址排序合并等算法来缓解碎片。
五、一个完整的 malloc 流程示例
void *p = malloc(100);
- 判断大小:100 字节 < 阈值(如 128KB)→ 走堆(brk 区域)。
- 查找空闲块:在 ptmalloc 的 fast bin / small bin 中查找是否有合适大小的空闲块。
- 命中缓存:找到一个 128 字节的空闲块,切出 100 字节给用户,剩余 28 字节放回空闲链表。
- 未命中缓存:分配器发现堆顶(brk)不够,执行
brk()向内核申请扩展几页内存。 - 内核处理:
brk系统调用修改进程 VMA(虚拟内存区域),标记新的虚拟地址范围有效,但不分配物理页。 - 用户访问:程序写入
p[0]→ 缺页中断 → 内核分配物理页 → 建立页表映射 → 写入成功。
六、嵌入式场景的特别之处
从你之前的对话看,你可能也关注 ESP32 等嵌入式平台。这类平台的内存申请原理与 Linux 有显著不同:
- 无 MMU 或仅有简单 MPU:通常没有虚拟内存和缺页中断,物理地址与虚拟地址一一对应(或线性偏移)。
- 静态分区或简单堆:FreeRTOS 的
pvPortMalloc()通常基于**首次适应(First Fit)**算法,碎片问题更突出。 - 内存总量极小:没有"延迟分配"的奢侈,
malloc失败是常态,嵌入式开发更推荐静态分配或内存池(Memory Pool)。
总结
内存申请的核心原理是:虚拟地址空间的记账 + 物理页面的延迟映射 + 用户态分配器的缓存与复用。理解这三层(应用层→C库→内核→硬件MMU),就能解释从 malloc(1) 到申请 1GB 内存时系统发生的全部行为。
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐


所有评论(0)