手写Zeal 8-bit OS设备驱动:从0到1实现键盘与串口驱动的完整教程
手写Zeal 8-bit OS设备驱动:从0到1实现键盘与串口驱动的完整教程
【免费下载链接】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 计算机设计的开源操作系统,整个内核用汇编语言编写,而设备驱动体系正是理解它内部运作的最佳入口。本教程面向新手,不要求你熟悉汇编细节,只需跟随步骤,即可完成键盘驱动与串口驱动从注册到调用的完整开发流程,真正搞懂 Zeal 8-bit OS 设备驱动的工作方式。
什么是 Zeal 8-bit OS 设备驱动?先看驱动结构
在 Zeal 8-bit OS 中,一个驱动程序本质上是一张函数指针表:4 字节的名字 + 8 个函数地址。内核通过这张表统一调用硬件,用户程序则通过 VFS 层访问驱动,完全不需要关心底层细节。
驱动必须实现的 8 个函数分别是:
| 函数 | 作用 |
|---|---|
init | 开机时初始化硬件,返回 0 表示成功 |
read | 从设备读取数据 |
write | 向设备写入数据 |
open/close | 打开 / 关闭设备 |
seek | 移动读写位置 |
ioctl | 设置设备参数(波特率、输入模式等) |
deinit | 卸载驱动时清理资源 |
驱动名最长 4 个字符,例如SER0(串口)、KBD0(键盘),要求全部是字母数字。驱动结构体的完整定义在 drivers_h.asm 中,官方驱动文档见 drivers.md。
驱动注册的两步走:放对位置 + 返回状态码
注册驱动非常简单:把上面这张结构体放进名为DRV_VECTORS的 section 即可。开机时,内核的驱动管理器会遍历整个 section,逐个调用init函数:
SECTION DRV_VECTORS DEFB "SER0" ; 驱动名 DEFW uart_init ; init DEFW uart_read ; read DEFW uart_write ; write DEFW uart_open ; open DEFW uart_close ; close DEFW uart_seek ; seek DEFW uart_ioctl ; ioctl DEFW uart_deinit ; deinit这里有个关键约定:如果驱动 A 依赖驱动 B,B 的结构体必须放在 A 前面,因为依赖关系在编译期就要解析完毕。这个遍历与注册逻辑实现在 drivers.asm 的zos_drivers_init中。
init的返回值决定驱动的最终命运:
- 返回
ERR_SUCCESS(0):驱动注册成功,用户程序可以 open/read/write - 返回
ERR_DRIVER_HIDDEN:驱动注册但对外隐藏,常用于只给文件系统用的磁盘驱动 - 返回其他错误码:注册失败,跳过该驱动
准备工作:获取源码与目录速览
动手前先克隆仓库:
git clone https://gitcode.com/gh_mirrors/ze/Zeal-8-bit-OS项目里与驱动开发最相关的目录:
kernel/:内核本体,drivers.asm是驱动管理器target/zeal8bit/:Zeal 8-bit 目标机的硬件驱动,keyboard.asm与uart.asm是我们的学习样板include/drivers/:驱动接口头文件,如keyboard_h.asm、video_text_h.asmkernel_headers/sdcc/include/:C 语言驱动接口头文件(zos_keyboard.h、zos_serial.h)
实现键盘驱动:从裸按键到带缓冲的输入
键盘驱动位于target/zeal8bit/keyboard.asm,它的核心职责是:读取按键 → 处理 → 存入 FIFO 缓冲区 → 供用户程序 read。
第一步:init 中注册为标准输入
keyboard_init只做三件事:初始化底层键盘实现(PS/2 或并口)、清空 FIFO、调用zos_vfs_set_stdin把自己注册为系统默认标准输入。这样 shell 和所有程序都能直接读取键盘,无需关心具体硬件。
第二步:用 ioctl 实现三种输入模式
键盘驱动对外只提供一个 ioctl 命令KB_CMD_SET_MODE,用于切换输入模式,定义见 keyboard_h.asm:
- RAW 模式:不做任何处理,按下/松开原样上报。比如按下左 Shift 再按 A,程序收到
0x93(左 Shift)和0x61(小写 a) - COOKED 模式(默认):按键先经过处理再存入缓冲区,支持大小写、方向键、退格、Tab 等,遇到回车才刷新
- HALFCOOKED 模式:介于两者之间,遇到不支持的键会填入缓冲区并返回
ERR_SPECIAL_STATE
另外还有阻塞/非阻塞读取标志。C 语言下可通过 zos_keyboard.h 直接使用这些常量,例如设置原始模式:
ioctl(fd, KB_CMD_SET_MODE, KB_MODE_RAW | KB_READ_NON_BLOCK);第三步:read 返回按键码
在 RAW 模式下,read 返回的是按键码:普通字符直接是 ASCII(如'a'= 0x61),特殊键使用 0x80~0xFC 的扩展码(如KB_LEFT_SHIFT= 0x93)。松开按键时,前面会带一个KB_RELEASED(0xFE)前缀:
0x61 [...] 0xFE 0x61 A ... A 已松开实现串口驱动:UART 驱动的完整骨架
串口驱动target/zeal8bit/uart.asm是另一个经典范例,它的init除了初始化波特率外,还承担一个重要任务:当系统配置了"UART 作为标准输出"时,调用zos_vfs_set_stdout把自己挂到 stdout 上,并发送 ANSI 转义序列清屏、设置光标。也就是说,串口驱动可以同时扮演显示驱动,非常适合没有屏幕的裸机调试场景。
串口的 ioctl 命令与键盘完全不同,定义在 zos_serial.h,包括:
SERIAL_CMD_SET_ATTR/SERIAL_CMD_GET_ATTR:设置/读取属性(如 RAW 模式)SERIAL_CMD_SET_BAUDRATE/SERIAL_CMD_GET_BAUDRATE:设置/读取波特率SERIAL_SET_TIMEOUT/SERIAL_SET_BLOCKING:超时与阻塞控制
syscall 层如何把参数传递给驱动的 read/write,规则与系统调用表完全一致,可参考 syscalls.md。如果你想写一个自己的串口小工具,romdisk 里的 uart.asm 是现成的用户态参考。
编译测试:让驱动跑起来
Zeal 8-bit OS 使用 Kconfig + Makefile 构建。驱动是否作为标准输入/输出,由目标机配置决定,可在configs/zeal8bit/os.conf等配置文件中调整,例如:
- 配置
CONFIG_TARGET_STDOUT_UART后,串口会自动成为标准输出 - 键盘默认注册为标准输入,无需额外配置
构建整个系统:
make烧录到模拟器或真机后,开机日志会打印类似Driver: SER0 init success的信息,这正是zos_drivers_init逐一遍历DRV_VECTORS的成果。之后你在 shell 里输入字符、通过串口收发数据,背后都是我们刚刚拆解的那套驱动机制在运转。
新手常见问题速查
Q:驱动名字冲突了怎么办?内核会返回ERR_ALREADY_EXIST并跳过该驱动,所以命名前先查重,SER0、KBD0这类有编号的名字最稳妥。
Q:我的驱动需要独占硬件,不想暴露给用户程序?让init返回ERR_DRIVER_HIDDEN即可,驱动仍可用但对外隐藏。
Q:键盘和串口同时注册,会不会抢占?不会。一个通过zos_vfs_set_stdin成为标准输入,一个通过zos_vfs_set_stdout成为标准输出,VFS 层各司其职。
总结:驱动开发其实只有三步
回顾全文,Zeal 8-bit OS 设备驱动开发可以浓缩为三步:
- 实现 8 个函数(不用的返回
ERR_NOT_SUPPORTED即可) - 把结构体放进
DRV_VECTORSsection - 在
init中返回正确的状态码,必要时调用zos_vfs_set_stdin/stdout挂载为标准设备
掌握了键盘驱动与串口驱动这两个样板,你就已经具备了读写 Zeal 8-bit OS 内核驱动代码的能力。下一步不妨尝试阅读 video.asm(显示驱动)或 compactflash.asm(存储驱动),把同样的方法论应用到更多硬件上。祝你在 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),仅供参考
