跳到主要内容

内存管理总体架构

TGOSKits 的内存管理采用“启动期事实发现、运行期统一分配、页表机制复用、系统策略并列”的结构。公共层只维护地址、物理页、页表和虚拟区间等机制;ArceOS、StarryOS 与 Axvisor 分别实现内核地址空间、Linux 兼容虚拟内存和客户机第二阶段地址转换策略。

1. 架构边界

内存组件按资源所有权而不是按操作系统名称分层。这样可以让嵌入式配置裁剪不需要的策略,同时避免 StarryOS 和 Axvisor 复制底层页分配或页表实现。

下图是源码级总体架构。纵向箭头表示所有权或能力向下传递,横向并列的 ax-mm、StarryOS memory management 和 axaddrspace 分别维护 ArceOS、Linux 兼容环境和客户机的策略,不形成彼此包装关系。

TGOSKits 内存管理总体架构

源码目录和关键调用链见内存管理源码结构,客户机 GPA 策略和 AxVM adapter 见Axvisor 客户机地址空间设计与实现,各架构的地址转换、页表根、页表项和失效差异见多架构内存实现,锁类型和顺序见内存管理锁与并发

1.1 层级职责

总体架构只区分四类职责:启动层发布可信物理区间,公共机制层维护分配和地址翻译,策略层解释系统语义,设备能力层隔离驱动与操作系统实现。具体 crate、源码目录和公共类型由内存管理源码结构统一索引,不在本章重复列举。

层级维护的不变量代表性边界
启动事实RAM、保留区和启动占用不重叠MemoryDescriptor、early bump used-range 发布
公共机制页恰好属于一个 owner;虚拟区域不重叠GlobalPageFrameAllocatorMemorySet
系统策略ArceOS、Linux 进程和客户机分别解释映射与回收ax-mm、Starry memory management、axaddrspace
设备能力驱动只消费已验证的 DMA 或寄存器映射能力DeviceDmaMmio

层级数量由需要维护的不变量决定。只做转发、别名或重复统计的 facade 不构成独立层;拥有不同地址类型、释放协议或回滚语义的 adapter 则不能为减少调用层数而合并。

1.2 唯一入口

同一种资源只有一个公共入口:物理页和内核堆进入 ax-alloc,页表帧由 FrameAllocator 注入,虚拟区域由 MemorySet 持有,StarryOS 的 Linux 兼容虚拟内存状态由 os/StarryOS/kernel/src/mm 维护,DMA 和内存映射输入输出分别进入其设备能力接口。

buddy-slab-allocator 只是 ax-alloc 的算法实现,不能成为普通消费者的第二入口;StarryOS AddrSpace 也不是 ArceOS ax-mm 的包装层。具体依赖方向和禁止的反向依赖只在内存管理源码结构维护。

2. 端到端数据流

系统从固件提供的物理内存事实出发,依次经过启动期占用裁剪、运行期分配器接管,再由不同地址空间或设备能力消费。整个流程没有把多段内存强行拼成一个连续地址区。

2.1 启动到运行期

下图描述动态平台使用 someboot 时的主要交接。箭头表示事实或资源所有权的传递,不表示所有组件之间存在 Rust crate 直接依赖。

启动 bump 已使用的前缀由 memory_map_setup() 重新标记为 Reserved,因此不会再次进入 Buddy。运行期每个 Free 物理段作为独立 section 加入分配器,连续页分配不能跨越段边界。

2.2 运行期请求路径

运行期请求先按资源类型选择公共能力,再进入具体策略。普通 byte allocation、显式页分配、虚拟映射和 DMA 的入口不同,但最终物理 RAM 均由 ax-alloc 管理。

请求公共入口实现路径所有权结束条件
小对象Rust allocator / GlobalAllocax-alloc → per-CPU Slabbyte allocation 被释放
大对象Rust allocator / GlobalAllocax-alloc → Buddy pagesbyte allocation 被释放
显式物理页global_allocator().alloc_pages(num, align, UsageKind)GlobalPageax-alloc → Buddy sectionGlobalPage::drop 或对称 dealloc_pages
Stage-1 页表页FrameAllocator adapterax-mm/Starry adapter → ax-alloc页表层级销毁
Guest RAMNestedPageTableOps::alloc_frameaxaddrspace/AxVM → ax-alloc客户机解除映射或虚拟机销毁
DMA bufferDeviceDma 资源获取即初始化 APIdma-apiaxklib::dmaax-alloc高层 owner Drop 或显式 release

FrameAllocator 只隔离“页从哪里来”,不会在 page-table-genericaxcpu::pagingaxvmsomeboot 内注册回收策略。当前 StarryOS 在启动时把 ax_fs_ng::vfs::page_cache_reclaim 注册给 ax-allocalloc_pages()alloc_dma32_pages() 失败时会在释放 allocator 锁后最多尝试 4 轮回收/重试。

3. 一致性保证

内存安全和性能依赖少量可审计的一致性条件。组件拆分的目标是让这些条件只在一个位置维护,而不是增加调用层数。

3.1 所有权一致性

每个可释放物理页在任一时刻只能属于 Buddy free list、Slab backing 或一个 live owner。不同资源使用显式 token 或资源获取即初始化类型避免重复释放。

资源所有者或状态来源关键类型
普通页 / DMA32 页ax-alloc 内部 Buddy sectionalloc_pages()alloc_dma32_pages()GlobalPage
Slab backing 页owner CPU 的 SlabSlabPageHeader、remote-free stack
虚拟内存区域MemorySetBTreeMap 中互不重叠的 MemoryArea;backend 直接修改页表
Starry 写时复制页Starry backend 与引用状态FRAME_TABLEMemoryAccounting
DMA allocation/mapdma-api 资源获取即初始化 ownerDmaAllocationStreamingMapDmaAllocHandleDmaMapHandle

当前 DmaAllocHandleDmaMapHandle 在源码中派生了 Clone + Copy,高层 DmaAllocation / StreamingMap 才通过内部 Option 和 Drop 控制日常释放。GlobalPage 记录起始虚拟地址和页数,Drop 固定按 UsageKind::Global 归还;其他用途的长期 owner 必须保存原页数和 UsageKind 并调用对称释放。

3.2 上下文与并发约束

Buddy 采用单个非抢占自旋锁,per-CPU Slab 将小对象热路径留在本 CPU,跨 CPU free 使用 SlabPageHeader::remote_free 的无锁栈。当前设计不引入非统一内存访问、page migration、compaction 或完整 Linux 每处理器页缓存。

上下文允许的内存路径禁止或应预分配的路径
early bootchecked bump、boot 页表、固定容量 metadata调度等待、回收、文件 I/O
普通内核线程Slab、Buddy、地址空间操作;内存不足时可在 allocator 锁外触发已注册 reclaim持 allocator 锁调用虚拟文件系统/reclaim
中断请求 / 实时 critical固定池或已经预留的 ring/descriptor通用堆、Buddy、Slab 扩容、回收
Starry 用户缺页backend fault;底层页申请失败时可能触发 ax-alloc 注册的 page-cache reclaim中断请求上下文 fault、无限重试
Guest faultaxaddrspace 按需 Guest RAM在客户机地址空间层隐藏额外 reclaim 策略

当前不提供没有生产消费者的通用实时 guard;中断请求和实时路径由具体组件预分配固定对象,并保持不进入通用分配器的路径约束。

4. 端到端实例

一个物理地址从固件描述进入系统后,会先后经历“事实分类、启动占用、运行时所有权、虚拟映射或设备可见性”四类状态。下面使用两段不连续 RAM 展开这条路径;地址是用于说明算法的确定性输入,所有区间均采用半开形式 [start, end)

4.1 多段内存交接

假设固件报告两个 RAM bank,内核被加载到第一个 bank,第二个 bank 中存在设备固件保留区。someboot::fdt::memory::init_memory_map() 先把两个 bank 都登记为 Free,随后 VecOp::merge_add()KImageReserved 覆盖相交的 Free 子区间。

输入事实地址范围大小初始类型
RAM bank 00x4000_0000..0x4800_0000128 MiBFree
kernel image0x4000_0000..0x40c0_000012 MiBKImage
RAM bank 10x8000_0000..0x9000_0000256 MiBFree
device firmware0x8800_0000..0x8820_00002 MiBReserved

覆盖完成后,内存图不把物理 hole 0x4800_0000..0x8000_0000 表示成任何 RAM。最终直接映射尚未建立,early_init() 先把内存图按物理起点排序,再取第一个大小超过 8 MiB 的 Free 段整体作为 early arena,因此本例选择 bank 0 中紧随内核镜像的区间;该选择不会改变其他 Free 段的类型。该规则没有架构地址上限;x86_64 的应用处理器 trampoline 通过单独预留的低地址页保证 32 位启动阶段能装载 CR3,与 early arena 位置无关。

0x4000_0000 0x4800_0000
|--- KImage 12 MiB ---|---------------- Free 116 MiB ----------------|

0x4800_0000 0x8000_0000
|------------------------- physical hole -------------------------|

0x8000_0000 0x9000_0000
|----------- Free 128 MiB -----------| Rsv 2 MiB |-- Free 126 MiB --|
0x8800_0000 0x8820_0000

假设 boot 页表、设备树二进制对象副本和四个 CPU 区域共占用 0x40c0_0000..0x4100_0000memory_map_setup() 会把这一前缀发布为 Reserved,此后 early bump 不再使用。运行时最终看到三个 Free 描述符,而不是一个伪连续 heap。

运行时 Free section 候选大小处理方式
0x4100_0000..0x4800_0000112 MiBglobal_add_memory()
0x8000_0000..0x8800_0000128 MiB最大段,优先 global_init()
0x8820_0000..0x9000_0000126 MiBglobal_add_memory()

ax-runtime::init_allocator() 实际会重新扫描全部 MemRegionFlags::FREE 区域并选择最大段。因此本例中 0x8000_0000..0x8800_0000 的 128 MiB 是首个 section,另两个合法段随后加入。early arena 按启动阶段可访问性选择,运行时初始化段按容量选择,两者的约束不同。

4.2 页所有权变化

假设 Starry 缺页路径请求一个匿名 4 KiB 页。物理页从 Buddy free list 移出后,先由 GlobalPage 或 backend 临时所有,再写入页表项并转移给写时复制 frame owner;虚拟内存区域只描述虚拟范围,不直接拥有 Buddy free-list 节点。

单页失败路径由 backend 就地清理:文件读取或 map_page() 失败时,CowBackend::alloc_new_at() / alloc_file_run() 先调用 deinit_frame()(从 FRAME_TABLE 移除并归还物理页)再返回错误;映射尚未建立或刚刚失败,因此不存在需要先 unmap 的页表项。record_charge() 只在重复记账时失败,此时该页保持已映射并返回错误,属于调用方缺陷。解除映射方向的安全顺序仍由 backend 维护:只有页表项成功删除后才能递减引用并归还物理页,否则 CPU 仍可能通过旧地址转换访问已经重新分配的物理页。

连续预读一次填充多页时(CowBackend::alloc_file_run()),当前实现逐页“分配、复制、映射、记账”,中途失败只清理当前页;此前成功的页保持映射,剩余页由后续缺页、显式 unmap 或进程退出回收。整段预读没有逆序回滚机制,审查内存压力下的部分失败行为时应以此为前提。

5. 主流实现依据

TGOSKits 采用区间描述、分配器与页表策略分离的结构,不要求为每个固件物理页建立软件对象。这个选择同时适用于嵌入式实时配置、Linux 兼容的 StarryOS 和需要第二阶段地址转换的 Axvisor,但三类系统使用的上层策略不同。

5.1 通用操作系统

Linux 启动期使用 memblock 保存物理内存与保留区间,而不是为每个 4 KiB 页创建启动描述符;MEMBLOCK_NOMAP 还能把区间保留在物理内存图中,同时明确排除直接映射。建立 x86_64 直接映射时,内核按地址对齐、范围和属性选择允许的最大页尺寸。虚拟内存修改则依赖虚拟内存区域锁、页表锁、操作内的预留和失败清理,不为每次大范围新映射保存一份空页表项快照。参见 Linux 的 boot-time memory managementmemblock_mark_nomapprocess address locksx86_64 direct-map implementation

关键设计LinuxTGOSKits 当前实现TGOSKits 约束
启动物理内存表示memblock 区间固定容量 MemoryDescriptor 区间保持区间表示,不按基础页展开
分配排除reserved rangesReservedKImagePerCpuDataMmio必须在 Buddy 接管前完成
直接映射排除独立 MEMBLOCK_NOMAP没有独立标志,Reserved 当前通常仍进入映射清单平台必须准确分类;不可访问区间不能只依赖笼统 Reserved
大范围映射最大安全页尺寸页表核心支持大页,ArceOS linear 当前禁用启用前必须处理属性边界和局部修改语义
新 Map 失败恢复操作内清理已建部分单页失败由 backend 就地清理;多页预读失败保留已成功页Map prepare 不保存逐基础页空快照
Unmap/Protect 恢复锁与操作专用状态mremap 用逐页 MovedPage 记录并逆序回滚;unmap/protect 无整段旧映射快照超大范围仍需容量测试或紧凑撤销表示

StarryOS 兼容 Linux 用户态语义,不等于复制 Linux 的全部服务器级物理内存子系统。写时复制、文件映射、常驻内存集大小、提交记账和缺页结果属于 Starry 策略;非统一内存访问、内存压缩、页迁移和通用内存不足终止器不进入公共嵌入式分配器。

5.2 实时操作系统

Zephyr 在启用内存管理单元的配置中提供虚拟内存映射,同时使用 heap/multi-heap 表示不同内存来源;FreeRTOS、RT-Thread 和 ThreadX 的典型配置更直接地使用一个或多个 heap、固定块池或字节池。它们通常没有 Linux 式通用虚拟内存区域事务,也不会为了一个巨大固件保留范围构造数百万项恢复快照。参见 Zephyr 的 virtual memoryheap、FreeRTOS 的 memory management、RT-Thread 的 memory management 和 ThreadX 的 memory block pools

系统物理或堆内存组织确定性机制与 TGOSKits 的对应关系
Zephyrsystem heap、多个 heap;可选虚拟内存固定容量对象与按配置启用能力多段 Buddy、固定池和按 feature 裁剪页表
FreeRTOSheap_1heap_5 或应用自定义分配器静态分配、简单 heap;heap_2 属于旧方案,通常优先 heap_4hard-real-time 路径预分配,通用路径使用单一 ax-alloc 入口
RT-Threadheap、小内存算法、Slab 或内存池对象池和配置化 allocatorper-CPU Slab 加板级专用固定池
ThreadXbyte pool 与固定大小 block poolblock pool 提供固定大小、可预测分配中断和实时关键路径使用专用固定池

TGOSKits 没有增加通用 pool manager。只有驱动描述符、中断事件或实时请求已经证明存在固定上限和延迟要求时,所属组件才建立专用池;普通页、内核 heap、Starry 用户页和 Guest RAM 继续通过 ax-alloc 统一统计和回收所有权。