Zeal 8-bit OS内存映射深度解析:MMU与no-MMU模式到底怎么选?
【免费下载链接】Zeal-8-bit-OSAn Operating System for Z80 computers, written in assembly项目地址: https://gitcode.com/gh_mirrors/ze/Zeal-8-bit-OS
Zeal 8-bit OS是一款专为Z80处理器编写的开源操作系统,全部核心代码使用汇编语言实现。对于想要在这款系统上开发程序或移植到自制Z80硬件的开发者来说,Zeal 8-bit OS内存映射机制是最先需要理解的概念。系统提供了MMU与no-MMU两种运行模式,它们决定了内核、用户程序与RAM之间如何划分地盘。本文将从零开始,为你逐层拆解两种模式的内存布局、关键配置项,并给出不同硬件场景下的选型建议。
为什么Z80也需要"内存映射"?
Z80是经典的8位处理器,只有16根地址线,理论寻址空间为64KB。要在如此有限的空间里同时容纳操作系统、用户程序和驱动数据,就必须对内存进行精细的"分区管理"。
Zeal 8-bit OS 的做法是提供两种策略:
- MMU模式:通过硬件内存映射器(Memory Mapper),把64KB虚拟地址空间划分为4个16KB的虚拟页,实现内核与用户程序的隔离;
- no-MMU模式:不使用映射器,采用"低16KB内核 + 其余RAM"的平坦布局,适合无MMU芯片的Z80兼容机。
两种模式的核心差异,就在于"内核数据"与"用户程序"如何共享那宝贵的RAM空间。
MMU模式:4个16KB虚拟页的精细分工
在MMU模式下,内核把64KB地址空间划分为4个16KB的虚拟页,每页的职责截然不同。页大小与虚拟地址定义在 include/kern_mmu_h.asm 中(KERN_MMU_VIRT_PAGES_SIZE = 0x4000)。
Page 0:内核代码保护区(0x0000 - 0x3FFF)
第0页存放操作系统全部核心代码,绝对不允许被切换。这意味着:
- 操作系统二进制体积被严格限制在16KB以内;
- 当用户程序执行
syscall时,CPU会跳转到这一页执行内核代码; - 一旦此页被切走,任何系统调用、中断都将产生未定义行为(Undefined Behavior)。
这一限制也解释了为什么Zeal 8-bit OS的内核要精打细算——多一行冗余代码都可能挤爆16KB的"内核小屋"。
Page 1:用户程序加载区(0x4000 - 0x7FFF)
第1页是用户程序被复制并执行的地方,因此所有程序都默认从地址0x4000开始链接(可通过KERNEL_INIT_EXECUTABLE_ADDR配置项修改,但改动后现有程序必须重新编译)。
Page 2 与 Page 3:用户RAM与内核RAM的"身份切换"
- Page 2(0x8000 - 0xBFFF):与Page 1一起映射为可用RAM,供用户程序使用;
- Page 3(0xC000 - 0xFFFF):默认映射为内核RAM,存放内核与驱动的全部数据。
用户程序加载时,Page 3会被切换到普通RAM交给程序使用;一旦发生系统调用,它又会被立即切回内核RAM。因此,用户程序最大可用空间为48KB(Page 1 + 2 + 3),栈指针(SP)在加载程序时被设置为0xFFFF。
这里有一个值得注意的细节:如果用户程序传入的系统调用参数恰好指向Page 3区域,内核会在调用期间把用户Page 3临时重映射到Page 2,并对指针自动减去16KB,从而避免与内核RAM冲突。这种"动态换页"正是MMU模式优雅之处。
no-MMU模式:简单直接的平坦内存
并非所有Z80电脑都配有MMU(如Agon Light、TRS-80等老式兼容机),为此内核提供了no-MMU模式,可通过menuconfig中的KERNEL_TARGET_HAS_MMU选项切换。
在该模式下:
- 操作系统代码依旧位于前16KB(0x0000 - 0x3FFF);
- 剩余空间全部视为RAM;
- 理想情况是
0x4000起映射48KB RAM直到0xFFFF,但实际容量可灵活配置。
两个关键配置项:KERNEL_RAM_START 与 KERNEL_STACK_ADDR
no-MMU模式的内存布局由两个menuconfig选项共同决定:
KERNEL_STACK_ADDR:标记内核RAM区域的结束地址,同时也是内核栈的栈底;KERNEL_RAM_START:标记内核RAM的起始地址,内核栈、内核与驱动的全部变量(BSS段)都存放于此。
根据官方文档的估算,当前内核BSS段约1KB,栈分配1KB基本够用,整体预留至少3KB内核RAM是比较稳妥、面向未来的选择。
用户栈地址怎么定?
no-MMU模式下,内核在运行用户程序前会把栈指针设置为KERNEL_RAM_START - 1。也就是说,用户程序可以轻松算出自己可用RAM的大小:只需执行SP - 0x4000即可。程序容量虽然比MMU模式略小,但胜在实现简单、移植门槛低。
MMU与no-MMU模式对比速查表
| 对比维度 | MMU模式 | no-MMU模式 |
|---|---|---|
| 硬件要求 | 需要16KB×4页的内存映射器 | 任意Z80兼容机即可 |
| 内核代码区 | Page 0(16KB,不可切换) | 0x0000 - 0x3FFF(16KB) |
| 用户程序加载地址 | 0x4000(48KB可用) | 0x4000(视RAM而定) |
| 内核数据存放 | Page 3(独立虚拟页) | KERNEL_RAM_START 起,需手动划分 |
| 程序最大体积 | 48KB | 通常小于48KB |
| 栈指针 | 0xFFFF | KERNEL_RAM_START - 1 |
| 支持嵌套程序(exec递归) | ✅(可配置深度) | ❌ |
到底怎么选?三种硬件场景的配置建议
场景一:拥有MMU的自制Z80电脑(如Zeal 8-bit Computer)
这是MMU模式的"主战场",直接参考 configs/zeal8bit/os.conf:开启CONFIG_TARGET_HAS_MMU=y,内核RAM物理地址设为0x88000,KERNEL_STACK_ADDR=0xFFFF。此时内核独占整个虚拟页,用户程序拥有完整的48KB空间,还能借助KERNEL_MAX_NESTED_PROGRAMS实现多层程序嵌套执行,体验最接近现代操作系统。
场景二:无MMU但RAM充足的兼容机(如Agon Light、TRS-80)
使用no-MMU模式,参考 configs/agon/os.conf 和 configs/trs80/os.conf:两者均未开启MMU,KERNEL_RAM_START=0xE000。如果你的机器RAM超过48KB,可以适当调低KERNEL_RAM_START,把更多空间让给用户程序。
场景三:RAM紧张的小内存设备
建议no-MMU模式并精打细算:把KERNEL_RAM_START与KERNEL_STACK_ADDR的间距控制在3KB左右,同时在menuconfig中关闭不需要的功能(如MBR支持、ZealFS v2),以压缩内核体积。
如何配置并编译?
整个配置流程都在内核源码目录下完成:
# 1. 进入内核配置界面,选择目标平台与内存模式 make menuconfig # 2. 保存配置后编译 make内核相关的全部选项定义在 kernel/Kconfig 中,包括内存模式、栈地址、嵌套程序深度、驱动数量上限等。MMU_INIT()在 kernel/boot.asm 中被调用完成映射器初始化;虚拟页地址换算宏KERNEL_MMU_PAGE_OF_VIRT_ADDR则被系统调用频繁使用,实现"虚拟地址 → 页索引"的快速转换。更深入的内存布局图与Syscall参数细节,可参考 docs/details/memory-mapping.md、docs/details/user-space.md 等文档。
总结
一句话概括:有MMU用MMU,没MMU用no-MMU,中间态则按RAM容量灵活裁剪。
- 追求程序体积与嵌套执行能力 → 选MMU模式;
- 追求硬件兼容性与移植简易度 → 选no-MMU模式;
- 想快速上手 → 直接套用仓库中对应的目标配置文件。
理解了Zeal 8-bit OS内存映射的本质,无论是开发应用还是移植新硬件,你都能少走很多弯路。如果这篇文章对你有帮助,欢迎收藏转发,让更多Z80爱好者少踩坑。
【免费下载链接】Zeal-8-bit-OSAn Operating System for Z80 computers, written in assembly项目地址: https://gitcode.com/gh_mirrors/ze/Zeal-8-bit-OS
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考