4. 内存管理迁移:虚拟内存管理、页面分配与回收、内存映射机制

内存管理,说白了就是操作系统怎么管好这块“地皮”。Linux 和鸿蒙在这块儿上,思路有相似之处,但实现细节差异不小。我当年从 Linux 内核转向鸿蒙时,第一个感觉就是:嗯,鸿蒙对内存的“精细化”要求更高,尤其是在多设备协同的场景下。

4.1 虚拟内存管理:从“页表”到“空间”的博弈

虚拟内存这东西,大家都不陌生。Linux 用的是经典的 4 级页表(PGD、PUD、PMD、PTE),在 ARM64 上还能支持 5 级。鸿蒙呢?它也是基于 ARM64 架构,但做了不少“瘦身”。

我个人习惯,看一个系统的内存管理,先看它的虚拟地址空间布局。Linux 的布局很成熟,用户空间和内核空间比例固定(比如 3:1 或 4:1)。鸿蒙则更灵活,它允许根据设备类型动态调整。比如在轻量级 IoT 设备上,内核空间可以压缩得很小,把更多地址留给用户态应用。

核心差异点:

  • 页表层级:Linux 默认 4 级,鸿蒙在轻量设备上只用 2 级(减少 TLB miss)。
  • 地址空间划分:Linux 固定比例,鸿蒙支持动态分区。
  • TLB 管理:鸿蒙引入了“局部刷新的概念”,不像 Linux 动不动就全刷。

我在项目中遇到过一个问题:把 Linux 上的一个内存密集型应用迁移到鸿蒙,结果跑起来性能反而下降了。查了半天,发现是鸿蒙的页表层级太少,导致大页(HugePage)支持不完善。后来我们手动调整了页表分配策略,才把性能拉回来。

4.2 页面分配与回收:伙伴系统与“零拷贝”的诱惑

页面分配,Linux 用的是伙伴系统(Buddy System),这个大家都知道。鸿蒙呢?它也是伙伴系统,但加了一个“快速分配路径”。

你想想看, 在 Linux 里,分配一个页面要走 buddy allocator,如果内存碎片严重,还得触发 compaction。鸿蒙的做法是:先从一个“预分配池”里拿,拿不到再走 buddy。这个预分配池的大小可以动态调整,有点像“缓存”的概念。

避坑指南:我曾经在鸿蒙上写驱动,频繁分配和释放小页面(比如 4KB),结果发现预分配池很快就耗尽了。后来我改用 slab 分配器,问题才解决。记住:鸿蒙的 buddy 系统对大块内存友好,小块内存还是走 slab 更靠谱。

页面回收方面,Linux 有 kswapd 和 direct reclaim,鸿蒙也有类似的机制。但鸿蒙多了一个“内存压力通知”机制。当系统内存低于某个阈值时,它会主动通知用户态进程,让它们自己释放内存。这比 Linux 的 OOM killer 要温和得多。

特性 Linux 鸿蒙
分配算法 Buddy System Buddy + 预分配池
回收触发 kswapd / direct reclaim kswapd + 用户态通知
碎片处理 compaction compaction + 快速合并
大页支持 HugePage (2MB/1GB) HugePage (2MB 为主)

4.3 内存映射机制:mmap 的“鸿蒙版”

内存映射,Linux 里最常用的就是 mmap 系统调用。鸿蒙也有 mmap,但参数和行为有细微差别。

说白了, Linux 的 mmap 是“文件映射”和“匿名映射”两套逻辑。鸿蒙则把这两者统一了——它用了一个叫做“共享内存区域(Shared Memory Region)”的概念。不管是文件还是匿名页,最终都映射到同一个 SMR 上。

这样做的好处是什么?我举个例子:在 Linux 里,如果你要跨进程共享内存,得用 shmget 或者 mmap 加 MAP_SHARED。鸿蒙里,你只需要创建一个 SMR,然后所有进程都可以 attach 上去。这大大简化了 IPC 的复杂度。

注意:鸿蒙的 mmap 不支持 MAP_GROWSDOWN 标志。如果你在 Linux 代码里用了这个标志(比如实现栈自动增长),迁移到鸿蒙时必须手动处理。我曾经踩过这个坑,一个多线程程序在鸿蒙上频繁段错误,查了两天才发现是栈映射的问题。

另外,鸿蒙的 mmap 对“设备内存”映射做了优化。比如你要映射一个 GPU 的显存,Linux 里得用 ioctl 或者 DRM 接口。鸿蒙直接支持通过 mmap 映射设备内存,参数里指定设备 ID 就行。这让我在写图形驱动时省了不少事。

4.4 核心逻辑:一张图看懂内存管理迁移

下面这张 SVG 图,是我自己梳理的。它展示了从 Linux 到鸿蒙,内存管理各模块的对应关系和迁移要点。

Linux → 鸿蒙 内存管理迁移核心逻辑 Linux 内存管理 虚拟内存 (4级页表) 固定地址空间划分 页面分配 (Buddy) kswapd + direct reclaim 内存映射 (mmap) 文件/匿名映射分离 碎片处理 (compaction) 大页支持 (HugePage) 迁移要点 • 页表层级减少 • 动态地址空间 • TLB 局部刷新 • 预分配池机制 • 用户态内存通知 • 快速合并碎片 • SMR 统一映射 • 设备内存直映射 • 不支持 GROWSDOWN • 大页以 2MB 为主 • compaction 策略优化 鸿蒙内存管理 虚拟内存 (2/4级页表) 动态地址空间划分 页面分配 (Buddy+池) kswapd + 用户态通知 内存映射 (SMR) 文件/匿名/设备统一 碎片处理 (快速合并) 大页支持 (2MB为主)

4.5 迁移实战:一个 mmap 的例子

最后,我分享一个实际迁移的例子。假设你在 Linux 上有这样一段代码:

// Linux 代码:映射一个文件到内存
int fd = open("/data/test.bin", O_RDWR);
void *addr = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
// 使用 addr...
munmap(addr, 4096);
close(fd);

迁移到鸿蒙,你可能会写成这样:

// 鸿蒙代码:使用 SMR 实现类似功能
SMR_Handle handle;
SMR_Create(&handle, 4096, SMR_FLAG_SHARED);
void *addr = SMR_Attach(handle, NULL, 0);
// 使用 addr...
SMR_Detach(addr);
SMR_Destroy(handle);

嗯,这里要注意: 鸿蒙的 SMR 接口比 Linux 的 mmap 多了一个“创建”步骤。但好处是,你不需要打开文件描述符,SMR 本身就是一个独立的内存区域。如果你需要持久化,可以再关联一个文件。

我个人觉得,鸿蒙的内存管理设计更贴近“分布式”场景。它把内存抽象成资源池,设备之间可以共享。而 Linux 更偏向单机优化。所以迁移时,别只盯着 API 差异,更要理解设计思路的变化。

总结一下:

  • 虚拟内存:鸿蒙页表更精简,地址空间更灵活。
  • 页面分配:鸿蒙多了预分配池,回收更温和。
  • 内存映射:鸿蒙用 SMR 统一了映射逻辑,设备内存支持更好。

好了,这一章就到这里。记住,内存管理是操作系统的核心,迁移时一定要多测试边界情况。尤其是那些在 Linux 上跑得好好的“老代码”,到了鸿蒙上可能就会因为页表层级、映射标志等问题翻车。