启动栈、内核任务栈与用户栈
TGOSKits 没有把物理 RAM 静态切成一个“栈区”和一个“堆区”。CPU0 最早期栈来自内核镜像 .bss,每 CPU 启动栈由 someboot 早期线性分配器预分配,普通内核任务栈从运行时分配器获取,Starry 用户栈则是用户地址空间中的 Virtual Memory Area(虚拟内存区域,VMA)。
1. 栈类型与生命周期
不同栈存在于不同启动阶段和地址空间。区分这些栈是分析内存占用、guard page 和释放行为的前提。
1.1 栈来源总览
当前主要栈类型如下。默认大小来自当前 linker/build 配置,平台配置可以覆盖任务栈大小。
| 栈类型 | 默认大小 | 来源 | 生命周期 |
|---|---|---|---|
| CPU0 最早期 linker 栈 | STACK_SIZE = 0x40000,256 KiB | kernel .bss / KImage | 启动早期,镜像范围始终保留 |
| 每 CPU boot/main 栈 | someboot::mem::stack_size(),默认 256 KiB | early bump 的 per-CPU 区 | 系统生命周期,TaskStack::Borrowed 不释放 |
| 普通内核任务栈 | 默认 0x40000,可由构建配置覆盖 | GlobalAlloc 或显式页 allocation | task owner Drop 时释放 |
| idle 特殊栈 | 取决于构建 feature,部分配置为 16 KiB | task allocator | idle task 生命周期 |
| Starry 用户栈 | loader/应用程序二进制接口选择的虚拟内存区域大小 | 用户地址空间 backend,按需填页 | exec/exit/unmap 时回收 |
栈大小不是物理连续 RAM 的全局配额。只有具体 stack allocation 会消耗页;用户栈预留的虚拟内存大小也不等于所有页面已经 resident。
1.2 栈与 heap 的关系
“栈和堆如何划分”在运行期表现为不同 owner 使用同一 allocator,而不是两个永久物理分区。下图展示来源关系。
普通任务栈默认 256 KiB,超过 2048 B Slab 上限,因此 plain 模式最终由 Buddy 提供大对象页。启用 guard page 后则直接使用显式连续页 API。
1.3 架构差异
栈的 owner 与分配来源跨架构一致,架构入口仅负责把栈顶写入本架构栈寄存器并跳转:x86_64 使用 rsp,AArch64 和 RISC-V 使用 sp,LoongArch64 使用 $sp。启动 entry 必须在进入 Rust 前满足相应调用约定的栈对齐,栈 owner 不保存架构私有寄存器状态。
Guard page 的区别来自地址转换缓存失效:AArch64 使用 inner-shareable 硬件广播,x86_64、RISC-V 和 LoongArch64 的默认实现只处理本地 CPU,需要上层远程失效。地址窗口和指令细节统一见多架构内存实现,本章后续只说明栈特有的 owner 和 guard 时序。
2. CPU0 启动栈
CPU0 在 allocator、完整页表和 per-CPU 映射可用之前就需要栈。这个阶段使用 linker 明确预留的静态范围,避免任何动态依赖。
2.1 链接布局
platforms/someboot/src/ld/bss.ld 在 .bss 末尾定义 __cpu0_stack 和 __cpu0_stack_top