跳到主要内容

StarryOS Linux 兼容内存管理

StarryOS 在公共内存机制之上实现 Linux 兼容虚拟内存。当前源码没有独立的 memory/starry-mm crate;相关策略、记账、后端、syscall 接线和 procfs 展示集中在 os/StarryOS/kernel/src/mm/os/StarryOS/kernel/src/syscall/mm/os/StarryOS/kernel/src/pseudofs/proc.rs

StarryOS 与 ArceOS ax-mm 并列使用 ax-memory-set 和主机 Stage-1 页表机制,但不把 Linux 虚拟内存区域包装在 ArceOS AddrSpace 外面。StarryOS 自己维护 Linux VMA backend、写时复制、文件映射、共享页、RSS/VSS 统计和 fault/syscall 结果转换。

1. 当前边界

StarryOS 的进程地址空间把区域集合、Stage-1 页表和 Linux 统计放在同一个 AddrSpace 所有者中,并由外层 Arc<Mutex<_>> 串行化修改。公共内存 crate 只提供机制,Linux syscall、procfs 和文件映射语义都保留在 Starry kernel 内。

1.1 主要对象

AddrSpace 是 StarryOS 进程地址空间的核心 owner,它同时保存用户虚拟范围、VMA 集合、Stage-1 页表和两类统计状态。下面的字段布局解释了为什么 map、fault、clone 与 procfs 采集必须围绕同一个外层锁组织。

pub struct AddrSpace {
va_range: VirtAddrRange,
areas: MemorySet<Backend>,
pt: PageTable,
process_slots: AtomicUsize,
pub vm_stat: ProcessVmStat,
rss: MemoryAccounting,
}

这些字段共同构成单个进程地址空间的状态边界,任何成功的 map、unmap、fault 或 clone 都必须保持区域、页表和统计之间一致;外层锁负责使这组变化不会与另一个修改者交错。

字段当前职责
va_range用户虚拟地址上限
MemorySet<Backend>VMA 查找、拆分、收缩、metadata-only 操作
PageTableStarry 用户 Stage-1 页表
process_slots引用该地址空间的 live process slot 数
ProcessVmStat当前 VSS、VmPeak、当前近似 VmHWM
MemoryAccountingAnon/File/Shmem RSS bucket、hiwater 和 COW charge map

外层 Arc<Mutex<AddrSpace>> 负责进程共享和同步。ax-memory-set 自身不含锁,也不提供跨 VMA 的通用事务日志;StarryOS 需要回滚时由具体 backend 或 AddrSpace 专用流程维护局部记录。

1.2 与公共机制的关系

StarryOS 复用区域容器、页表、物理页与 DMA owner,但会在各机制外增加 Linux 可见的 admission、记账和错误转换。下表区分可共享的实现边界与不能下沉到公共 crate 的系统策略。

公共机制StarryOS 使用方式StarryOS 保留策略
ax-memory-set保存 VMA、调用 backend map/unmap/protectLinux mmap/mprotect/mremap 规则、reported flags
page-table-generic/axcpu::pagingStage-1 页表和 frame allocatorfault access、signal/errno 转换
ax-allocUsageKind::VirtMemPageTablePageCacheDmaRSS 分类、COW ref、page-cache owner
dma-apidma-buf、device mmap/import ownerfd、mmap retainer、设备操作期 owner

当前 StarryOS 在 kernel/src/entry.rs 注册 ax_fs_ng::vfs::page_cache_reclaim。因此页分配不足时,ax-alloc 的 page allocation 路径会在 allocator 锁外调用已注册回收函数并最多重试 4 轮,而不是由 AddrSpace::handle_page_fault() 外层实现“一次 reclaim/retry”包装。

2. 映射后端

os/StarryOS/kernel/src/mm/aspace/backend/ 使用 enum dispatch 统一四种 backend。每个变体都实现相同的区域操作入口,但物理页来源、共享方式和 RSS 更新规则不同,因而不能只根据 VMA flags 推断释放动作。

Backend物理来源典型 mapping释放/记账
Linear已知连续物理地址设备、ION 或 kernel-provided range不释放外部物理区
Cowanonymous page 或私有 file pageMAP_PRIVATE、匿名 heap/stackframe table 引用计数,RSS Anon/File
SharedArc<SharedPages>shared anonymous、SysV SHM、borrowed/imported pages最后一个 Arc 决定 allocated 页释放
FileVFS/page cacheMAP_SHARED file mappingpage cache owner 与 dirty/writeback 语义

每个 MemoryArea 同时保存 actual flags 和 reported flags。写时复制页表项可以保持只读以捕获第一次写 fault,而 VMA 仍向用户报告原始可写属性。

2.1 map/unmap/protect

AddrSpace::map() 先验证虚拟地址范围和 4 KiB 对齐,再进入 MemorySet::map()。backend 直接修改页表,成功后 vm_stat.on_map() 增加 VSS。unmap() 会先计算实际移除的 VMA 页数,调用 memfd listener,再由 MemorySet::unmap() 调 backend,最后 vm_stat.on_unmap()

protect_with_reported_flags() 通过 MemorySet::protect_with_reported_flags() 拆分相交 VMA,并在同一次 backend 调用中更新 actual flags 与 reported flags。File-backed private COW backend 可把实际 PTE 写权限清除,同时保留用户可见写权限。

2.2 metadata-only 操作

unmap_metadata()replace_area_metadata() 用于“页表项已经由专用流程移动或分离,只更新 VMA 元数据”的路径。例如 COW fork 由 backend clone_map() 安装 child 页表项和 RSS charge,再用常规 map(不允许覆盖)发布 child VMA metadata;mremap 失败恢复用 replace_area_metadata() 还原源区域描述。公共 MemorySet 不重复保存这些页表项的 undo 日志。

3. 写时复制

当前 COW frame table 位于 os/StarryOS/kernel/src/mm/aspace/backend/cow.rs,它为每个已登记物理页保存共享引用对象。父子地址空间的只读页表项、frame 引用数和 RSS charge 必须在 clone、写故障和 unmap 三条路径中保持对称。

struct FrameRefCnt {
count: u8,
}

struct FrameTableRefCount {
table: BTreeMap<PhysAddr, Arc<IrqMutex<FrameRefCnt>>>,
}

这表示当前实现仍是 u8 引用计数和每 frame Arc<IrqMutex<_>>,不是独立 crate 中的 checked u32 引用规则。新增 fork/COW 逻辑必须显式考虑引用计数上限和失败清理,不能依赖文档假设的 u32 设计。

3.1 fault-in 与写故障

CowBackend::populate() 根据匿名或文件来源以及本次访问类型决定物理页内容、页表权限和 RSS 分类。私有文件页第一次写入还会把既有 File charge 迁移为 Anon,而不是简单增加第二份统计。

fault物理页内容PTE flagsRSS
anonymous read/write新清零页VMA flagsAnon
private file read从 file offset 读取并尾部清零通常保持只读File
private file first writeCOW 新页或重分类可写Anon

文件私有映射第一次写入时,cow_file_write_to_anon() 会在地址空间锁内把 charge 从 File 迁移为 Anon;找不到旧 charge 时会尝试 adopt 为 Anon 并减少一个 File bucket。

3.2 clone

AddrSpace::try_clone() 创建未发布 child 地址空间,逐个 parent VMA 调用 backend clone_map()。COW backend 会修改父子页表项、增加 frame 引用并给 child 建立 RSS charge。随后 child 用 MemoryArea::new_with_reported_flags() 发布 metadata。

失败时,fresh child 尚未发布,已建立的 child 映射和 frame 引用依靠 backend 清理与 child clear/drop 路径释放。父进程的页表 flags 恢复和 frame 引用对称释放是 COW 代码的关键审计点。

4. RSS 与 VSS

StarryOS 把已驻留物理页和已声明虚拟范围分开统计:MemoryAccounting 维护 RSS 分类与 COW charge,ProcessVmStat 维护 VSS 与峰值近似值。procfs 再将两组状态和 VMA 分类组合为用户可见字段。

4.1 常驻页记账

MemoryAccounting 使用三个 relaxed atomic bucket 保存 rss_anonrss_filerss_shmem,并维护 hiwater_rss。此外,它用 UnsafeCell<BTreeMap<VirtAddr, RssKind>> 保存 COW resident charge;所有 charge map 访问要求调用方持有 AddrSpace 锁。

减少操作当前使用 fetch_sub,debug build 会断言下溢,发布构建不会返回 checked error。因此文档、测试和评审不能声称 RSS 减少在发布构建中会转换为 AxError::BadState

4.2 虚拟规模统计

ProcessVmStat 位于 os/StarryOS/kernel/src/mm/vm_stat.rs。它用 AtomicI64 保存当前 VSS 页数,用 AtomicU64 保存 peak_vss_pagespeak_rss_pages。当前 peak_rss_pages 仍在 map 时按 VSS 更新,是近似高水位;真实 RSS 当前值和 hiwater 由 MemoryAccounting 提供。

4.3 proc 展示

ProcessMemStats::collect() 遍历 AddrSpace::areas(),按 path、flags、shared 标志和 stack range 计算 VmSize/Text/Data/Stack/Exe 等 VMA 分类,再合并 MemoryAccounting 的 RSS bucket。

展示数据来源
/proc/<pid>/statmProcessMemStats::format_statm()
/proc/<pid>/status Vm/RSS 行format_status_vm_lines()
VmRSSAnon + File + Shmem resident
VmPeakProcessVmStat::peak_vss_pages() 与当前 VSS 的最大值
/proc/meminfo Committed_AS当前固定展示 0 kB
/proc/sys/vm/overcommit_memory当前固定展示 0

这意味着当前 procfs 还没有完整 Linux committed-memory ledger。不要把 Committed_AS、overcommit mode 或 CommitLimit 描述成已经由 Starry mm 策略精确维护。

5. 缺页路径

AddrSpace::handle_page_fault(vaddr, access_flags) 返回 booltrue 表示 fault 被处理,false 交给 trap 层转换为用户可见 fault。它的主流程是:

check vaddr in user range
-> find containing MemoryArea
-> compare VMA actual flags with requested access
-> align fault address to backend page size
-> backend.populate(range, flags, access, Some(&rss), &mut pt)
-> run optional populate callback
-> success only if populated page count > 0

File backend 可能返回 deferred callback,用于处理 page-cache eviction 期间需要在 AddrSpace 上执行的 unmap/TLB flush 等清理。调用方不能丢弃 callback,否则可能让用户 PTE 继续指向已经回收的 page-cache frame。

内存不足时,backend 最终来自 ax_alloc::global_allocator().alloc_pages(..., UsageKind::VirtMem)。是否尝试回收由 ax-alloc 的注册 callback 决定,不由 handle_page_fault() 直接区分 FaultOutcome::NoMemory 这类结果枚举。

6. 文件、共享页和 dma-buf

文件页、共享匿名页和 dma-buf 的最终 owner 不相同,StarryOS 通过不同 backend 与 retainer 保持释放协议可判定。三类对象都可以出现在进程 VMA 中,但 VMA 的存在本身并不等价于拥有底层物理页。

6.1 文件映射

FileBackend 仍留在 Starry kernel。私有 file mapping 走 COW backend,shared file mapping 走 File backend/page cache。syscall 层在 destructive MAP_FIXED unmap 前先验证 file mmap 权限和 memfd seals,避免 EACCES / EPERM 时提前撕掉旧映射。

6.2 共享页所有权

SharedPages 当前区分自行分配和借用外部物理页两种 owner。该区分决定最后一个 Arc 析构时是逐页归还 allocator,还是只释放维持外部资源存活的 retainer。

OwnerDrop 行为使用场景
Allocated逐页 dealloc_frame()shared anonymous / SysV SHM
Borrowed(retainer)只释放 retainer,不释放物理页dma-buf、设备或外部 owner mapping

SharedPages::new(size, page_size) 中途分配失败时,当前构造路径没有显式回滚已经 push 到 phys_pages 的页;因为 result 的 Drop 会释放已保存的 allocated pages,最终仍依赖 RAII 清理。

6.3 dma-buf

Starry /dev/dma_heap 和设备 import 使用 dma-api 高层 owner。fd、mmap 和设备操作通过 Arc retainer 保持同一 allocation live;设备 backend 不能释放 imported buffer,也不能把 fd 生命周期当作唯一 owner。

7. 当前限制

当前实现已经提供基本 Linux 虚拟内存行为,但仍有若干统计精度、引用计数宽度和缺页结果表达方面的限制。下表描述的是源码现状,不能据此推断未列出的 Linux 内存策略已经实现。

限制当前状态
独立 starry-mm crate未存在,策略在 Starry kernel 内
committed-memory ledger未实现为独立对象,Committed_AS 固定 0
overcommit modeprocfs 固定展示 0,不是完整 Linux mode 实现
swap未实现,proc 多项固定 0
COW ref countu8,不是 checked u32
RSS decrementdebug assert,下溢在发布构建不返回 typed error
fault result enumhandle_page_fault() 返回 bool,没有公共 FaultOutcome
dirty-page writeback reclaimpage-cache reclaim 由注册 callback 提供,需保持锁外和有界

这些限制若影响 syscall 或 procfs 的用户可见语义,修改时必须重新核对 Linux 行为和 docs/guideline/starry_syscall.md,并补充对应的系统级回归证据。

增加用户可见内存语义时必须按 docs/guideline/starry_syscall.md 对齐 Linux 行为;unsupported 路径应返回明确错误,不能静默成功或仅靠 proc 占位值误报完整支持。

8. 源码检查点

StarryOS 内存修改通常同时影响地址空间编排、某个 backend 和用户可见统计。下表按 owner 与边界列出主要入口,便于从行为变化追踪到实际状态维护位置。

源码审计重点
os/StarryOS/kernel/src/mm/aspace/mod.rsAddrSpace、VMA/page-table/RSS/VSS 编排、fault、clone
os/StarryOS/kernel/src/mm/aspace/accounting.rsRSS bucket、COW charge map、fork charge reconciliation
os/StarryOS/kernel/src/mm/aspace/backend/cow.rsCOW frame table、file private fault、clone/unmap cleanup
os/StarryOS/kernel/src/mm/aspace/backend/shared.rsallocated/borrowed SharedPages owner
os/StarryOS/kernel/src/mm/aspace/backend/file.rsshared file mmap、page-cache dirty/writeback 回调
os/StarryOS/kernel/src/mm/vm_stat.rsVSS 和 peak 统计
os/StarryOS/kernel/src/mm/stats.rsproc 进程内存聚合
os/StarryOS/kernel/src/syscall/mm/Linux mmap/brk/mprotect/mremap/mincore syscall 语义
os/StarryOS/kernel/src/pseudofs/proc.rsmeminfo、statm、status、overcommit sysctl 展示
os/StarryOS/kernel/src/entry.rspage-cache reclaim callback 注册

只修改 procfs 展示不能修复底层记账,只修改 backend 也不能自动更新 syscall 错误与统计输出;涉及跨层语义时应把这些入口作为同一审计集合。

StarryOS 的系统调用、跨 VMA 失败注入、写时复制回滚和 RSS 统计用例见内存管理测试