Linux内核模块中如何通过struct gendisk读取磁盘指定已知扇区
持有struct gendisk指针读取指定扇区的实现步骤
前置检查
- 首先获取磁盘的逻辑块大小,确认访问对齐:
逻辑块大小通过queue_logical_block_size(gendisk->queue)获取,你要读取的起始扇区偏移、总读取长度必须和该值对齐,否则IO会直接报错。注意内核默认的bi_sector字段以512B为单位,如果你要读取的扇区单位是4K,需要提前做单位转换。
核心实现流程
我们以最简单的同步读场景为例(适用于进程上下文,允许阻塞):
- 分配IO所需的内存页
读取到的数据会存在这个页里,单页最大支持4K读取,如果你要读更长的内容可以分配多个页:struct page *page = alloc_page(GFP_KERNEL); if (!page) { // 处理分配失败逻辑 return -ENOMEM; } - 分配并初始化bio结构体
bio是内核块IO的核心载体:struct bio *bio = bio_alloc(gendisk->queue, 1, GFP_KERNEL); if (!bio) { __free_page(page); return -ENOMEM; } bio_set_dev(bio, gendisk->bdi->dev); // 替换成你要读取的目标扇区号(512B为单位) bio->bi_iter.bi_sector = target_sector; // 把页挂载到bio上,这里的读取长度按你实际需求填,最大为PAGE_SIZE if (!bio_add_page(bio, page, read_len, 0)) { bio_put(bio); __free_page(page); return -EINVAL; } - 提交同步IO请求
5.10及之后的内核可以直接用submit_bio_wait简化操作,不需要手动写回调:
如果是更老的内核,需要自己定义完成量和end_io回调来等待IO完成,示例回调如下:int ret = submit_bio_wait(REQ_OP_READ, bio); if (ret) { // IO执行失败,处理错误 bio_put(bio); __free_page(page); return ret; }static void bio_read_end_io(struct bio *bio) { complete(bio->bi_private); bio_put(bio); } // 提交逻辑 DECLARE_COMPLETION_ONSTACK(bio_done); bio->bi_end_io = bio_read_end_io; bio->bi_private = &bio_done; submit_bio(REQ_OP_READ, bio); wait_for_completion(&bio_done); // 检查bio状态判断是否成功 - 读取数据并释放资源
IO成功之后就可以映射页到内核地址空间访问数据了:void *data_buf = kmap(page); // 这里直接访问data_buf就可以拿到读取到的扇区内容 // 访问完成后释放资源 kunmap(page); __free_page(page); bio_put(bio);
注意事项
- 不要在原子上下文(比如中断处理、持有自旋锁)中调用同步IO接口,这类场景必须使用异步回调处理IO结果,避免阻塞内核。
- 如果要读取的长度超过4K,你可以给bio添加多个页,或者拆分多个bio分批提交。
- 所有错误分支都要记得释放已经分配的资源,避免内核内存泄漏。
内容的提问来源于stack exchange,提问作者K_K
相关产品推荐
相关产品推荐

