当前位置: 首页 > news >正文

手写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.asmuart.asm是我们的学习样板
  • include/drivers/:驱动接口头文件,如keyboard_h.asmvideo_text_h.asm
  • kernel_headers/sdcc/include/:C 语言驱动接口头文件(zos_keyboard.hzos_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并跳过该驱动,所以命名前先查重,SER0KBD0这类有编号的名字最稳妥。

Q:我的驱动需要独占硬件,不想暴露给用户程序?init返回ERR_DRIVER_HIDDEN即可,驱动仍可用但对外隐藏。

Q:键盘和串口同时注册,会不会抢占?不会。一个通过zos_vfs_set_stdin成为标准输入,一个通过zos_vfs_set_stdout成为标准输出,VFS 层各司其职。

总结:驱动开发其实只有三步

回顾全文,Zeal 8-bit OS 设备驱动开发可以浓缩为三步:

  1. 实现 8 个函数(不用的返回ERR_NOT_SUPPORTED即可)
  2. 把结构体放进DRV_VECTORSsection
  3. 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),仅供参考

http://www.cnnetsun.cn/news/4086150.html

相关文章:

  • VBrowser-Android架构剖析:DownloadManager任务调度与前台下载服务
  • memtest_vulkan显存稳定性测试上手教程:5分钟揪出显卡“暗病“
  • WarcraftHelper:魔兽争霸3终极优化工具,三步免费解锁宽屏、高帧率与大地图
  • Esp-radio 硬件接线完全教程:ESP8266、VS1053 与 TFT 显示屏的详细接线图解析
  • Linux 网络接口命名规则
  • auto-value-parcel是什么?Android开发者告别手写Parcelable的终极利器
  • 三个平台一套手感:跨平台文本编辑器 Notepad-- 从安装到上手的实战笔记
  • 网页视频只能看不能存?N_m3u8DL-RE让流媒体下载一学就会
  • Esp-radio 硬件清单终极指南:DIY 网络收音机必需的 8 类元件选购建议
  • 代码评审看性能数据,先确认测试测到了什么
  • JoliCi 服务管理实战:一键启动 MySQL、Redis 等 10 种测试依赖服务
  • 百度ERNIE-Image本地部署指南:如何在ComfyUI中十几分钟跑通文生图模型
  • 一致性协议实现升级前的兼容性检查
  • 为什么选择 awesome-deepseek-agent?DeepSeek V4 接入的 10 大优势
  • Glance项目深度解读:macOS上最全能的Quick Look插件为何值得每个开发者安装
  • 参与Zeal 8-bit OS开源贡献:路线图、贡献规范与提PR完整指南
  • 探秘 babel-plugin-istanbul 源码:Babel 插件如何在编译期完成代码插桩
  • Parabolic视频下载工具完整上手指南:一个界面搞定200+网站的视频与音频下载
  • r3f-game-demo移动系统揭秘:Moveable组件如何实现基于瓦片的平滑移动与碰撞检测
  • terraform-provider-snowflake 用户与角色管理指南:构建企业级访问控制体系的 8 个步骤
  • 视频生成显存占用高怎么解决?LightVAE 与 LightTAE 让速度、画质、显存兼得
  • Drawnix 新手入门完整教程:界面布局与核心操作一网打尽
  • GreatSQL入门完全指南:开源免费金融级数据库的五大核心特性一网打尽
  • LXMusic音源配置完整指南:5分钟零门槛免费听遍全网音乐
  • NCM转MP3快速免费方案:ncmdump免安装用法与批量转换步骤
  • DSML 工具调用格式完全指南:在 DeepSeek-V4-Pro-0813 中定义工具的 4 个步骤
  • Node.js入门教程(二十九):util 模块
  • Duo Navigation Drawer API 速查手册:核心方法与接口一网打尽
  • VBrowser-Android是什么?一款全网视频嗅探缓存APP的完整入门指南
  • SingGuard-NSFA-0.8B 的7个分类头详解:每个风险域如何独立检测与输出风险概率