Zeal 8-bit OS系统调用终极指南:25+个API逐一详解与调用技巧
【免费下载链接】Zeal-8-bit-OSAn Operating System for Z80 computers, written in assembly项目地址: https://gitcode.com/gh_mirrors/ze/Zeal-8-bit-OS
如果你正在为 Z80 计算机开发软件,那么Zeal 8-bit OS 系统调用(Syscall)就是你与操作系统内核打交道的唯一官方通道。Zeal 8-bit OS 是一款完全用 Z80 汇编语言编写的轻量级操作系统,内核代码不到 8KB,却提供了 27 个功能完整的系统调用 API,覆盖文件读写、目录操作、进程管理、时间日期与内存映射。本文将逐一详解全部Zeal 8-bit OS API,并分享新手最容易踩坑的调用技巧,帮你快速写出稳定高效的用户程序。
什么是 Zeal 8-bit OS 系统调用?
系统调用是用户程序请求内核执行特权操作的机制。Zeal 8-bit OS 借鉴了 Linux 与 CP/M 的设计思路,把"打开文件""读取键盘""映射内存"等硬件相关操作全部抽象成统一接口,用户程序只需把调用号放入 L 寄存器、参数放入指定寄存器,然后执行一条RST $08指令即可进入内核。
调用完成后,返回值统一放在A 寄存器中:0表示成功,非0则是错误码。完整系统调用表可查看文档docs/system-calls.md与docs/details/syscalls.md,内核分发逻辑位于kernel/syscalls.asm,调用号常量定义在include/syscalls_h.asm。
调用约定的核心规则
| 项目 | 约定 |
|---|---|
| 调用号 | 存入寄存器L(如ld l, 2表示 open) |
| 参数 | 按规则放入H、DE、BC、A等寄存器 |
| 触发指令 | RST $08 |
| 返回值 | A寄存器,0 为成功 |
| 次要返回值 | 如BC(读写的字节数)、DE(时间值)等 |
⚠️最重要的限制:传给系统调用的任何缓冲区都不能跨越 16KB 虚拟页边界!例如缓冲区起点在0x7FFF、长度为 2 字节,就会跨越页 1 和页 2 而报错。遇到大缓冲区,请分多次调用。
文件操作类系统调用:读写文件的六个核心 API
文件操作是使用频率最高的系统调用组,包括read、write、open、close、seek、dstat、stat,共 7 个 API。
open(#2):打开文件或驱动
open接受文件路径(存于BC)和标志位(存于H),成功返回描述符。路径支持三种写法:相对路径file.txt、当前磁盘绝对路径/dir/file.txt、跨磁盘绝对路径C:/dir/file.txt。
常用标志位定义在头文件kernel_headers/sdcc/include/zos_vfs.h中:
O_RDONLY(0)只读、O_WRONLY(1)只写、O_RDWR(2)读写O_TRUNC:打开即清空文件O_APPEND:每次写入前自动移动游标到文件末尾O_CREAT:文件不存在时创建O_NONBLOCK:非阻塞模式(仅对驱动有效)
小技巧:打开驱动而非文件时,名称必须以#开头且不超过 4 个字符,例如#SER0。
read(#0)与 write(#1):数据搬运
read:H=描述符,DE=缓冲区,BC=字节数,返回时BC=实际读取字节数。write参数完全相同,只是方向相反。标准输出DEV_STDOUT(0)和标准输入DEV_STDIN(1)也通过它们读写。
close(#3):及时释放资源
关闭描述符。内核能同时打开的文件/驱动数量有限,用完务必立即关闭,否则后续open可能失败。注意:程序退出时内核会自动关闭其打开的所有设备并重置标准输入输出。
seek(#6):移动文件游标
H=描述符,BCDE=32 位偏移量,A=基准位置。基准值有三种:SEEK_SET(0,从文件头)、SEEK_CUR(1,相对当前位置)、SEEK_END(2,相对文件末尾)。偏移量超出文件大小时,游标会被钳制到文件末尾。
dstat(#4)与 stat(#5):获取文件信息
dstat针对已打开的描述符(H),stat针对文件路径(BC),两者都把结果写入DE指向的结构体,包含文件标志、32 位大小、日期和名称。
目录与路径操作:六个目录系统调用详解
Zeal 8-bit OS 支持真正的目录树(取决于文件系统),相关 API 共 6 个。
- mkdir(#8):创建目录,路径存入
DE。路径中任何一级不存在都会失败。 - chdir(#9):切换当前工作目录,路径存入
DE。 - curdir(#10):把当前工作目录写入
DE指向的缓冲区,缓冲区至少要PATH_MAX(128)字节。 - opendir(#11):打开目录,
DE=路径,成功返回描述符。 - readdir(#12):读取目录下一个条目,
H=目录描述符,DE=缓冲区。返回ERR_NO_MORE_ENTRIES(21)表示条目已读完。 - rm(#13):删除文件或空目录,路径存入
DE。
目录条目结构为 1 字节标志 + 16 字节文件名,可用D_ISFILE/D_ISDIR宏判断类型。
进程管理:exec 与 exit 的进阶玩法
Zeal 8-bit OS 是单任务系统,但通过exec可以加载并运行子程序,形成嵌套执行树。
exec(#16):加载并执行程序
BC=程序路径,DE=字符串参数(可空),H=执行模式:
EXEC_OVERRIDE_PROGRAM(0):子程序直接覆盖当前程序,节省内存。EXEC_PRESERVE_PROGRAM(1):当前程序保留在 RAM(需额外 48KB),子程序退出后自动恢复执行。
注意:子程序会继承父程序打开的全部描述符、当前目录和寄存器,因此子程序退出前必须关闭自己打开的描述符,否则会"泄漏"给父程序。嵌套深度由menuconfig的CONFIG_KERNEL_MAX_NESTED_PROGRAMS决定。
exit(#15):退出并返回内核
H=退出码,退出后内核重新加载init.bin。若当前程序是被EXEC_PRESERVE_PROGRAM调起的子程序,则父程序会收到退出码(存于D)。
描述符技巧:dup 与 swap 重定向输入输出
- dup(#17):把描述符
H复制到新编号E,两个描述符共享同一个游标。注意:新编号必须是空的。 - swap(#24):交换描述符
H和E的指向,非常适合临时重定向标准输出,用完再换回来,比 dup 更干净。
这两个 API 是编写"输出重定向"类程序(如把命令结果写入文件)的关键工具。
设备控制:ioctl 与 mount
- ioctl(#7):对驱动执行控制命令。
H=驱动描述符,C=命令号,DE=16 位参数。命令含义由具体驱动定义,例如控制串口波特率、键盘 LED 等。 - mount(#14):挂载磁盘。
H=磁盘驱动描述符,D=盘符(A~Z,大小写均可),E=文件系统类型。系统最多支持 26 个磁盘,盘符 A 是系统默认盘。
时间与日期:五个时间系统调用
- msleep(#18):休眠指定毫秒数,
DE=时长(最大 65 秒)。 - settime(#19)/ gettime(#20):设置/读取毫秒计数器,
DE返回当前值。精度依赖硬件实现(可能是 1ms、16ms 等),编程时不要假设固定粒度。 - setdate(#21)/ getdate(#22):设置/读取系统日期,
DE指向 8 字节日期结构(年、月、日、星期、时、分、秒,BCD 格式),结构定义见kernel_headers/sdcc/include/zos_time.h。
无 RTC 的平台上,日期相关调用会返回ERR_NOT_IMPLEMENTED(2)。
内存管理:MMU 专属的三个系统调用
仅当内核启用 MMU(内存管理单元)时可用:
- map(#23):把物理地址
HBC映射到虚拟地址DE,地址会自动向下取整到 16KB 页边界。这是访问扩展内存、驱动帧缓冲的核心 API。 - palloc(#25):分配一页 16KB RAM,返回页索引(存于
B)。 - pfree(#26):释放先前分配的页,只能释放自己分配的内存页。
完整系统调用一览表
| 编号 | 名称 | 一句话功能 |
|---|---|---|
| 0 | read | 从描述符读取数据 |
| 1 | write | 向描述符写入数据 |
| 2 | open | 打开文件或驱动 |
| 3 | close | 关闭描述符 |
| 4 | dstat | 获取描述符统计信息 |
| 5 | stat | 获取文件统计信息 |
| 6 | seek | 移动文件游标 |
| 7 | ioctl | 对驱动执行控制命令 |
| 8 | mkdir | 创建目录 |
| 9 | chdir | 切换当前目录 |
| 10 | curdir | 获取当前目录 |
| 11 | opendir | 打开目录 |
| 12 | readdir | 读取目录条目 |
| 13 | rm | 删除文件或空目录 |
| 14 | mount | 挂载磁盘 |
| 15 | exit | 退出程序 |
| 16 | exec | 加载并执行程序 |
| 17 | dup | 复制描述符 |
| 18 | msleep | 休眠毫秒 |
| 19 | settime | 设置时间计数器 |
| 20 | gettime | 读取时间计数器 |
| 21 | setdate | 设置系统日期 |
| 22 | getdate | 读取系统日期 |
| 23 | map | 物理地址映射到虚拟地址 |
| 24 | swap | 交换两个描述符 |
| 25 | palloc | 分配一页内存 |
| 26 | pfree | 释放一页内存 |
实战:用系统调用实现 echo 命令
来看内核自带的最小示例——romdisk/init/echo.asm,它演示了write的标准用法:
; HL = ARGV,BC = ARGC call strlen ; 计算参数字符串长度,结果在 BC ex de, hl ; DE 指向字符串地址 S_WRITE1(DEV_STDOUT) ; 把字符串写到标准输出 S_WRITE3(DEV_STDOUT, newline, 1) ; 再输出一个换行 xor a ; 返回 0 表示成功 ret其中S_WRITE1、S_WRITE3是头文件kernel_headers/z88dk-z80asm/zos_sys.asm提供的宏,会自动设置调用号并执行RST $08,比手写寄存器更不易出错。如果你用 C 语言(SDCC 4.2+)开发,可以直接调用kernel_headers/sdcc/include/下的zos_vfs.h、zos_sys.h中的函数。
错误码速查:调试必备
失败时A返回的错误码定义在include/errors_h.asm,常用几个务必记住:
ERR_SUCCESS(0)成功ERR_NOT_IMPLEMENTED(2)目标不支持该功能ERR_NO_SUCH_ENTRY(4)文件/目录不存在ERR_INVALID_PARAMETER(6)参数非法ERR_NO_MORE_ENTRIES(21)目录条目已读完ERR_NO_MORE_MEMORY(22)内存不足
总结
Zeal 8-bit OS 的 27 个系统调用 API 覆盖了 8 位机上开发所需的一切:文件、目录、进程、设备、时间、内存。牢记三条黄金法则即可少踩 90% 的坑:缓冲区不要跨 16KB 页边界、用完立即 close、子程序退出前清干净自己打开的描述符。现在就去kernel_headers/examples/目录下的示例程序里动手试试吧!
【免费下载链接】Zeal-8-bit-OSAn Operating System for Z80 computers, written in assembly项目地址: https://gitcode.com/gh_mirrors/ze/Zeal-8-bit-OS
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考