我原来把这个文件看成“安装设备,加上一组统一转发函数”。这个理解仍然是主线,但要把它用起来,还得分清三个东西:设备表位置、设备号,以及设备大类/子类型。
本次核对本地na-kernel的历史版本 490d5f0d3adf83c5670a181a2708a5c4a670d4c6,文件为 kernel/src/dev/device.c 和 device.h。其设备表、编号公式和转发结构与原截图对应;没有证据证明原截图恰好来自同一个提交,不把不同日期的块设备代码强行拼成一个可运行版本。历史源码。
1. 设备表不是设备号数组

devices保存device_t结构,devices_idxs记录各子类型已经分配的次编号。设备在devices里的下标不等于dev字段。这个版本查找设备号时是线性扫描,而不是直接用dev当数组下标。

原文说“name设置为null”不准确:这里name被复制字符串 "null",并不是名字指针被设为NULL。type/subtype设为DEV_NULL、dev置零,部分回调置NULL。
devices是静态存储对象,首次启动时其未显式写的字段也会零初始化;但device_init并没有显式重置ptr、poll、map全部字段。所以不能从“第一次初始化没问题”推导出它支持随时完整重置设备表。本次测试不修改内核实现,仅标明这个边界。
2. 安装是取得槽位、填写字段,再注册到devfs

get_null_device从下标1开始,用type==DEV_NULL判断空闲,找不到返回NULL。保留槽位0和给设备号赋值是两件事。

ptr不是指向device_t自身的必然指针,它由驱动传入,可以指向私有状态。统一层只保存它,之后再原样交回回调。name、parent和各回调也都在此时填入,最后调用devfs_register_device。
这份历史代码没有在取槽后检查NULL,也未在此函数内验证subtype范围、长名称的终止情况或并发分配。阅读时要记录这些前置条件;本文不执行越界或空指针输入,不把“正常路径测试通过”说成注册接口已经健壮。

编号公式是 (subtype << 8) | minor,minor来自该子类型的计数再自增。它是这个内核的编码方式,不是现代Linux dev_t的通用布局。当前表共256槽、安装跳过槽0;若以后扩大容量或支持反复注销安装,也需重审minor位宽与编号唯一性。
3. device_find按子类型枚举,不按大类筛选

这是原文最需要修正的一句:device_find(DEV_BLOCK, 0) 并不表示“第一块设备”。DEV_BLOCK是type枚举,函数比较的是subtype,应传DEV_DISK、DEV_PART等具体子类型。
device_t *first_disk = device_find(DEV_DISK, 0);
device_t *second_disk = device_find(DEV_DISK, 1);
idx表示按设备表扫描顺序遇到的第几个匹配项,不是设备号minor字段的查询条件。这份只安装不注销的正常流程可能让两者恰好一致,但函数实现并没有比较minor。

device_get扫描dev字段。初始化后大量空槽dev都是0,因此这个版本 device_get(0) 可以返回槽位0,不会自动得到NULL。调用者不能把“非NULL”直接当作“已安装的活跃设备”。
4. 统一转发层要看三个分支

| 状态 | ioctl/read/write/poll统一层的行为 |
|---|---|
| 找不到设备号 | 返回-ENODEV |
| 找到设备但对应回调为空 | 返回-ENOSYS |
| 回调存在 | 传入device->ptr和操作参数,返回驱动结果 |
“不存在”和“不支持”不是一个错误。回调自己的负错误码也应原样传回,不能在封装层吞掉变成成功。
已确认的局部链路:
device_ioctl(dev, cmd, args)
-> device_get(dev)
-> device->ioctl(device->ptr, cmd, args)
原文写“用户ioctl -> VFS -> device_ioctl”,那是一条需要继续查调用者的更长链路,仅凭这个文件不能证明系统调用、文件描述符与devfs上层都已经接通。本文的实测止于上面的局部接口。

这张历史块设备图使用整数drive/cmd/arg,而本版本device.h中的ioctl签名是 int (*)(void *, int, void *)。它能展示“按操作号分发”的思路,但不能据图断言两个版本的函数可直接互换。本次驱动桩严格使用device.h的回调签名。



read/write的返回类型是ssize_t,允许表示负错误;idx、count、flags都被继续传入。统一层本身并不转换字节与扇区单位,单位要查具体驱动的约定。

map不能照搬普通read的返回值判定:这里用转换为指针的负错误值表示失败,上层必须遵循该内核的错误指针约定,不能只判断NULL就把它当可访问地址。这个约定与可移植C接口也要分开看。
5. 怎么验证,不用真的去访问磁盘
本次从历史提交提取真实device.c与device.h,仅把三个内核头文件依赖换为宿主测试头,EXPORT_SYMBOL在测试中置空;注册devfs使用记录调用的桩。没有另写一份相似的设备层代替原代码。
驱动桩保存一个可识别的状态对象:ioctl验证收到的ptr、cmd、args;read/write/poll/map也记录各自参数和返回值。这样能发现“驱动被调用了,但拿到的却是错误对象或参数”。
| 测试项 | 核对内容 |
|---|---|
| 安装两种子类型 | 空槽从1分配;计数按子类型独立;devfs桩得到同一设备对象 |
| find/get | 按子类型找第0/1项;按dev找原对象;越过已安装数量返回NULL |
| 正常与缺失回调 | 私有状态及操作参数不丢失,驱动返回值原样传回 |
| 不存在设备 | 四种整数返回接口及map的错误表示 |
| 初始化约定 | 首次静态零初始化;device_get(0)确实命中空槽;不声称完整重置 |
宿主环境用GCC编译并运行断言。这个版本的注册参数使用void*传入回调,依赖内核工具链支持,不能把测试通过说成ISO C对任意平台都保证这种转换。
本次不是启动naos,不是硬件读写,也没有验证VFS系统调用端到端。它证实的是对应历史实现的注册和转发行为;缺失的容量检查、重置语义与版本差异仍然是审查发现,不因正常测试通过就被抹掉。
和device.h分析一起看,头文件定义了驱动能力,device.c把能力装进统一入口。读这种封装层,除了看“它转发了”,还要追问:它找的是谁,传给谁的什么状态,失败如何向上表示。
522



被折叠的 条评论
为什么被折叠?



