ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

naos-riscv源码分析:block.h如何把字节请求交给块设备驱动

naos-riscv源码分析:block.h如何把字节请求交给块设备驱动 原来读这份头文件时我主要在辨认每个字段。这次再向前走一步上层想读磁盘上的 20 个字节驱动为什么收到的却是扇区号和扇区数量从这个问题看结构体里的成员就不再是孤立名词。1. 先对齐版本避免拿新代码硬套旧截图保留原来的数组式管理截图。本次补充明确以历史提交5971db63b3c56cddfbcac98a5475d41d968799b2的kernel/src/block/block.h与block.c为参照这个版本的核心结构与原图一致但不宣称它就是当时截图的精确提交。对应 block.h对应 block.c。较新的工作树已改用链表接口也有扩展本文不是最新版 API 手册。MAX_BLKDEV_NUM为 64是这个实现的表容量不是系统设计中永远只能有 64 个设备也不代表注册函数自动完成了所有越界保护。2. 两层接口同名 size 可能不是同一个单位核心字段摘录如下可复制对照typedef struct blkdev { char *name; void *ptr; uint64_t block_size; uint64_t size; uint64_t max_op_size; uint64_t (*read)(void *data, uint64_t lba, void *buffer, uint64_t size); uint64_t (*write)(void *data, uint64_t lba, const void *buffer, uint64_t size); } blkdev_t;这是一份内核头文件摘录依赖内核类型定义不能当成独立的用户态程序直接编译。位置在所选实现中的含义block_size一个逻辑块的字节数注册时为零会使用 512max_op_size用来约束一次底层请求的字节规模上层offset、len字节偏移、字节长度回调lba起始逻辑块编号回调参数size本次请求的逻辑块数量不能混成字节数回调返回值该实现按完成的逻辑块数量比较blkdev_read成功返回值返回复制给上层的字节数结构体自身的size保存注册者传入的容量值IOCTL_GETSIZE原样返回完整容量单位还要检查各驱动的注册调用不能仅凭变量名断言所有驱动都一致。最容易忽略的就是回调的sizeblock.c会调用dev-read(..., chunk_sectors)并用返回值和chunk_sectors比较。这个调用点比“size 看起来应该是大小”的猜测可靠。3. ptr 与函数指针将数据和行为一起交给公共层注册时公共层记住名字、几何参数、驱动私有数据ptr和 read/write 回调。之后公共层不必知道具体硬件细节它把ptr原样交回回调驱动再把它解释成自己的上下文。调用路径可以写成上层blkdev_read(drive, 字节偏移, 用户缓冲区, 字节长度) - 根据 drive 找到 blkdev_t - 字节请求换算为逻辑块请求 - dev-read(dev-ptr, 起始LBA, 临时缓冲区, 块数) - 从临时缓冲区复制需要的字节 - 向上层返回字节数drive是这一层管理的设备索引ptr是驱动上下文两者不是同一件事。这个注册表也不等同于另一篇device.h的通用设备表。4. 真正走一次偏移 500读取 20 字节假设逻辑块大小 512 字节请求从字节偏移 500 开始读 20 字节且单次请求限制足以读两个块。所选实现的换算start_sector 500 / 512 0 end_sector (500 20 - 1) / 512 1 sector_count 1 - 0 1 2 offset_in_block 500 % 512 500注意减一长度 20 的最后一个字节在偏移 519不是 520。块0 [0 ................ 499 | 500 .... 511] 取12字节 块1 [512 ... 519 | 520 ................ 1023] 取8字节 对驱动从 LBA 0 读 2 块 对上层交付 20 字节驱动不是只读 20 个字节而是提供覆盖请求范围的块。块层再裁出上层需要的那一段。相反偏移 512、长度 512 恰好对齐只覆盖 LBA 1 一个块。5. ioctl 与写操作不能只看名字猜功能IOCTL_GETBLKSIZE与IOCTL_GETSIZE在这个版本里只是查询已有字段不会自动触发格式化等操作。写操作还有一个读取没有的要求只覆盖块中某一段时不能破坏同块其余字节。所以历史blkdev_write有先读旧块、修改指定范围、再写回的分支。这就是读改写。它也意味着部分块写入可能需要有效的 read 回调而不只是 write 回调。6. 我怎样核对这段解释本次实际读取了固定提交的源码用独立的字节区间模型核对了跨块、对齐和单字节案例。模型不运行内核也不证明磁盘驱动正确。历史实现还值得继续审查零长度下offset len - 1的计算、偏移加法溢出、设备索引范围、临时缓冲区是否覆盖实际整块 I/O以及max_op_size太小时如何保证循环前进。看懂换算不代表这些边界已经处理完善本文不把旧实现包装成经过完整验证的生产代码。读这份头文件最后我想记住的不是每个字段的中文翻译而是三个层次上层说字节驱动说块公共层负责换算和衔接。下一篇 device.h 则继续看不同类型设备怎样接入同一套入口。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进