☰
naos-riscv源码分析:device.h如何把设备注册、查找与读写串起来
2026/10/4 1:58:55 网站建设 项目流程

这篇原来主要列出设备类型和结构成员。这次围绕一个问题重新串起来:调用者手里只有设备号,为什么公共入口能调用到具体设备的 read?

本次补充以提交490d5f0d3adf83c5670a181a2708a5c4a670d4c6的kernel/src/dev/device.h和device.c为参照。该版本与原图的数组、分类和五类操作相符,但不声称已找回截图时的精确提交。新版已修改数据结构和回调参数,阅读时不要混用。

历史 device.h,历史 device.c。

1. 表容量、名字、分类:先分清不同用途

DEVICE_NR=256是数组容量;历史分配函数从下标 1 开始找空位,不能直接说它支持 256 个可分配设备。

NAMELEN=16是字符数组容量。若名字作为 C 字符串使用,就要给终止符留位置,通常最多 15 个字节。原实现使用strncpy(..., NAMELEN),长度达到容量时不会自动保证终止符。这是需要检查的边界,不是头文件替调用者保证的安全条件。

type表示字符、块、网络等大类,subtype区分控制台、输入、磁盘、分区等。不要仅因为存在 type 字段就推断所有代码都按它分组:所选实现的device_find实际按 subtype 筛选。

2. 一个设备对象,同时保存身份、数据和操作

成员用途
name展示或注册用的名字
type、subtype分类
dev公共设备号
parent父设备号,具体关系由注册者提供
ptr驱动私有上下文,不等于设备号
ioctl、poll、read、write、map操作回调,未实现的操作可以为空

读回调的历史声明:

ssize_t (*read)(void *dev, void *buf, uint64_t offset, size_t size, uint64_t flags);

这里是内核接口摘录,依赖内核类型,不是独立程序。尤其要注意,回调参数名虽然叫dev,实际传入的是结构体中的ptr,不是数值设备号。

这就是统一接口的价值:调用者通过设备号进入,公共层负责找到对象,驱动通过私有上下文工作。公共层不用了解每个驱动的内部结构。

3. 从注册到读取,连成一条路径

历史注册函数的过程是:找空槽位,保存分类和 ptr,分配设备号,保存回调,调用devfs_register_device,返回设备号。设备号构造为:

device->dev = (dev_major << 8) | dev_minor;

这里dev_major来自 subtype,dev_minor来自该子类型的递增计数。这是所选版本的编码规则,不代表任意操作系统都这样编码。计数超出低八位时怎样处理,仍需要额外检查。

实际读入口摘录:

ssize_t device_read(uint64_t dev, void *buf, uint64_t idx, size_t count, uint64_t flags) { device_t *device = device_get(dev); if (!device) return -ENODEV; if (device->read) return device->read(device->ptr, buf, idx, count, flags); return -ENOSYS; }

可以按下面的路径理解:

调用者提供:设备号 dev、缓冲区、位置、数量、flags -> device_get(dev) 搜索设备对象 -> 找不到:-ENODEV -> 找到但没有 read:-ENOSYS -> 有 read:把 ptr 和其余参数交给回调 -> 回调结果原样返回

例如一次演示读,设备号选中对象后,回调接到的是该对象保存的上下文、位置 2、长度 3,而不是设备号本身。若演示驱动的数据为abcdef,返回的三个字节可以是cde。这是本次用户态替身测试的例子,不是 naos 磁盘驱动实测结果。

device_get(dev)按设备号找对象;device_find(subtype, idx)找的是某子类型中的第 idx 个对象。后者的 idx 不是数组下标,也不能无条件当设备号使用。

4. 和 block.h 的差别:统一入口不等于统一单位

控制命令里有起始 LBA、扇区数量和扇区大小,不代表每一种设备都必须支持它们。

device.h是通用设备对象和分发接口;block.h 专门处理块设备相关属性与请求。两篇参照的是不同历史提交,不把它们拼成一个已验证的共同运行版本。

尤其不能看到通用 read 的 offset/size 就断言它们在所有设备中都是字节:公共层在这里原样透传参数,真正约定由具体回调决定。想确认磁盘或分区的单位,要继续追踪对应适配函数,不能停在头文件。

5. 验证与尚未解决的边界

本次对照固定提交的注册、查找与分发实现;用用户态替身核对 ptr、位置、长度和 flags 的传递,另测不存在设备、缺少回调两个分支。不运行完整内核,也不冒充硬件验证。

历史代码还有值得追问的地方:表满时注册者是否处理空指针,设备名是否终止,编号计数是否限制,热拔插后怎么处理已持有的引用,并发查找与修改怎样同步。它们是后续审查问题,不是因为提供一个结构体就自动解决了。

新人可以先看懂“注册保存对象,读取查找对象,再调用回调”;熟悉 C 的读者则可以从接口契约继续追问单位、所有权、错误码和并发。这样一份头文件才真正连到了程序行为上。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询