2. 内存管理基础:内存架构与分配回收机制

大家好,我是你们的老朋友。今天咱们聊聊内存管理的基础。说实话,很多开发者在做应用时,对内存的认知就停留在「申请-使用-释放」这个层面。但如果你真想在鸿蒙上做出流畅体验,必须得往下挖一层。

我个人习惯把内存管理比作「仓库管理」。L1/L2 Cache 是手边的工具台,DDR 是主货架,虚拟内存就是你的库存清单。嗯,咱们一个一个来看。

2.1 内存架构:从 L1 Cache 到 DDR

先说说硬件层面的内存架构。鸿蒙系统跑在 ARM 或 RISC-V 架构上,内存层次基本是统一的:

  • L1 Cache:离 CPU 最近,速度最快(约 1-2 个时钟周期),但容量最小(通常 32KB-64KB)。分指令缓存和数据缓存。
  • L2 Cache:速度稍慢(约 10-20 个时钟周期),容量大一些(256KB-2MB)。
  • DDR(主存):速度最慢(几十到上百纳秒),但容量最大(几 GB)。

我在项目中遇到过一个问题:某个图像处理算法在测试机上跑得飞快,但到了低端设备上就卡成 PPT。查了半天,发现是 L1 Cache 命中率太低。数据在 DDR 和 Cache 之间来回搬运,CPU 大部分时间都在等数据。

关键指标:Cache 命中率。命中率每下降 1%,性能可能下降 5%-10%。

为什么会这样?说白了,CPU 的速度比 DDR 快了两个数量级。如果每次取数据都要去 DDR 拿,CPU 就得干等着。Cache 就是用来弥合这个速度鸿沟的。

2.2 内存分配与回收机制

鸿蒙的内存分配器,我研究过一段时间。它不像 Linux 那样直接使用 glibc 的 malloc,而是自己实现了一套轻量级的分配器。核心思路就两个:

  1. 减少碎片:通过 slab 分配器管理小对象,伙伴系统管理大块内存。
  2. 快速分配:预分配常用大小的内存块,避免频繁调用内核。

举个例子,鸿蒙里有个叫 LOS_MemAlloc 的接口,它内部会先检查当前线程的本地缓存池。如果有空闲块,直接返回,不需要加锁。这比每次去全局堆里分配快得多。

// 鸿蒙内存分配示例
void *ptr = LOS_MemAlloc(m_pool, size);
if (ptr == NULL) {
    // 分配失败,尝试回收或扩展堆
    LOS_MemExpand(m_pool, size);
    ptr = LOS_MemAlloc(m_pool, size);
}
// 使用完后释放
LOS_MemFree(m_pool, ptr);

这里要注意:LOS_MemAlloc 返回的内存是物理连续的。这对 DMA 操作很重要,但也会增加分配失败的概率。我曾经在视频编解码模块里踩过这个坑——申请 4MB 连续内存失败,导致编码中断。

避坑指南:我曾经在申请大块连续内存时,没有做 fallback 处理。结果在内存碎片化严重的设备上,应用直接崩溃。后来我加了个策略:先尝试连续分配,失败后改用分散-聚集 DMA。

2.3 虚拟内存与物理内存

虚拟内存这个概念,很多开发者觉得抽象。你想想看,每个进程都以为自己拥有 4GB 的地址空间,但实际上物理内存可能只有 2GB。这怎么做到的?

答案是:页表 + MMU(内存管理单元)。鸿蒙的虚拟内存管理,核心就是维护一张页表,把虚拟地址映射到物理地址。当进程访问一个尚未映射的虚拟地址时,会触发缺页异常,内核再去分配物理页。

鸿蒙的页大小默认是 4KB,但也支持 64KB 的大页。大页的好处是减少 TLB(页表缓存) miss,适合大块连续内存的场景。我建议在多媒体、图形渲染这类场景下,主动使用大页。

特性 4KB 小页 64KB 大页
TLB 覆盖范围 大(减少 miss)
内存浪费 少(细粒度) 多(内部碎片)
适用场景 通用应用 多媒体、图形

再说说物理内存的管理。鸿蒙把物理内存分成多个区域:

  • Normal 区:给普通进程用,可回收。
  • DMA 区:给外设用,要求物理连续。
  • HighMem 区:高端内存,用于大块映射。

每个区域都有自己的回收策略。比如 Normal 区内存不足时,会触发 kswapd 内核线程,把不活跃的页面换出到 zram(压缩内存)或 swap 分区。

个人经验:我建议你在开发阶段,通过 /proc/meminfo 观察各个区域的使用情况。如果 DMA 区频繁告警,说明你的驱动申请了太多连续内存,可以考虑改用 scatter-gather 方式。

嗯,这里要特别提一下鸿蒙的「内存压缩」机制。它不像 Linux 那样直接 swap 到磁盘,而是把不活跃的页面压缩后存到 zram 里。压缩率大概 2:1 到 3:1,读取时再解压。这比磁盘 I/O 快得多,但会消耗 CPU。

我在优化一个后台服务时发现,它的 RSS(常驻内存)高达 200MB,但实际活跃页面只有 50MB。通过调整 /proc/sys/vm/swappiness 参数,让系统更积极地压缩不活跃页面,最终把 RSS 降到了 80MB,而且 CPU 开销只增加了 2%。

最后总结一下:内存管理不是简单的 malloc/free。你得理解硬件层次(Cache vs DDR),知道分配器的内部机制(slab vs 伙伴系统),还得掌握虚拟内存的映射策略(小页 vs 大页)。只有这样,你才能在性能优化时做出正确的决策。

下一章,咱们聊聊内存泄漏的检测与定位。这可是个硬骨头,但掌握了方法,你会发现它没那么可怕。