原来读这份头文件时,我主要在辨认每个字段。这次再向前走一步:上层想读磁盘上的 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 | 一个逻辑块的字节数;注册时为零会使用 512 |
max_op_size | 用来约束一次底层请求的字节规模 |
上层 offset、len | 字节偏移、字节长度 |
回调 lba | 起始逻辑块编号 |
回调参数 size | 本次请求的逻辑块数量,不能混成字节数 |
| 回调返回值 | 该实现按完成的逻辑块数量比较 |
blkdev_read 成功返回值 | 返回复制给上层的字节数 |
结构体自身的 size 保存注册者传入的容量值,IOCTL_GETSIZE 原样返回;完整容量单位还要检查各驱动的注册调用,不能仅凭变量名断言所有驱动都一致。
最容易忽略的就是回调的 size:block.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 则继续看不同类型设备怎样接入同一套入口。
1945



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



