【Linux驱动】块设备驱动(三)—— 块设备读写(不使用请求队列)

并非每种块设备都会用到请求队列,从上节可以知道,请求队列的作用是管理和调用IO请求,那么反过来想,如果IO请求较少,那就可以无需使用请求队列。在以下情况中,可以不使用请求队列。

  • 单任务环境: 当系统中只有单个任务(线程或进程)需要对存储设备进行读写操作时,IO操作可以直接被发起,而无需经过请求队列进行调度。

  • IO操作不频繁: 当系统中的IO操作非常稀少并且不频繁时,IO操作可以被直接发起,并由底层设备来处理,而无需经过请求队列进行处理。

  • 闪存设备:闪存设备通常由多个存储单元(通常是存储芯片)组成,每个存储单元可以存储大量的数据,通常以块(页)为单位进行读写,每个块的大小通常为几十到几百KB,因此,闪存设备可以直接以块为单位进行读写,无需像传统设备那样以扇区为单位进行读写。

本篇的重点是在不使用请求队列的情况下读写块设备,依然需要用到请求处理函数,只不过内部的逻辑与之前有所不同。


目录

一、制造请求

1、API 介绍

2、代码实现

二、请求处理函数

1、遍历 bio 结构退数组

2、获取扇区起始地址

3、获取内存页起始地址及页偏移

4、获取数据长度

5、读写操作实现

6、关闭 bio 操作

7、完整示例


一、制造请求

1、API 介绍

现在不使用请求队列,所以下面要换一个请求处理函数的原型,使用 blk_queue_make_request 来注册一个新的请求处理函数。函数声明如下,这里虽然还注册了一个请求队列,但是我们后续不会用到这个请求队列。

/*** @param q     请求队列* @param hanle 请求处理函数指针*/
void blk_queue_make_request(struct request_queue * q, make_request_fn * handle);

make_request_fn 函数指针的原型如下,q 为请求队列,bio 为 bio 结构体数组,bio 是一种描述块设备IO操作的数据结构,如起始地址、操作的数据长度、目的地址等信息都在这个结构体中。

ps:上一节在介绍每个请求的结构提到过。(详情参考上一篇的第三部分内容)

/*** @param q     请求队列* @param hanle 请求处理函数指针*/
void (* make_request_fn)(struct request_queue *q, struct bio *bio);

注意:后续测试不会使用这个请求队列,这个请求队列只是以备不时之需。 

2、代码实现

这里就直接在之前的基础上进行修改,修改的地方只有一处,将原本的 “申请请求队列” 替换为 “制造请求”即可。其中 blk_alloc_queue 用于动态创建并初始化一个请求队列。

// 请求处理函数
void make_request_hanlde(struct request_queue *q, struct bio *bio)
{
}// 驱动入口函数
static int __init blkdriver_init(void)
{// ... ... /*制造请求*/blkdev.queue = blk_alloc_queue(GFP_KERNEL);if(blkdev.queue == NULL){del_gendisk(blkdev.gendisk); unregister_blkdev(blkdev.major, blkdev_NAME);return -1;}blk_queue_make_request(blkdev.queue, make_request_hanlde);// ... ...
}

二、请求处理函数

bio 中包含了两个重要结构体对象,一个是 bio_vec 结构体对象 bi_io_vec,保存了内存页的相关信息,如页地址、页偏移;另一个是 bvec_iter 结构体对象 bi_iter,保存了块设备扇区相关信息,如扇区地址。

1、遍历 bio 结构退数组

遍历 bio 数组使用的是宏 bio_for_each_segment,本质是一个for 循环,bvec、bio 和 iter 都是 for 循环中的循环项,该宏的声明如下。

/** @param bvec: bio_vec结构体对象 (保存的是内存页信息)* @param bio: bio 结构体指针* @param iter: bvec_iter结构体对象 (保存的是扇区信息)*/
#define bio_for_each_segment(bvec, bio, iter)

传入的 bvec、bio 和 iter 之间关系如下,随着循环的推进,bio 指针也会跟着变化。

iter = bio->bi_iter;
bvec = bio->bi_io_vec;
bio = bio + sizeof(bio);

2、获取扇区起始地址

因为这里是将虚拟内存 diskbuf 模拟成一个块设备,所以这里无需手动获取扇区地址。若是在使用真实块设备时,可以使用传入的 iter 循环项来获取扇区起始地址。

sector_t start = iter.bi_sector;    // sector_t 本质是unsigned long 类型

3、获取内存页起始地址及页偏移

获取页偏移可以直接通过 bvec 循环项获取。

int offset = bvec.bv_offset;

获取内存页起始地址有两种方法:

① bio_data 一步到位

bio_data 是内核提供的API,能够获取内存中数据页的地址,在上一篇也用到了,函数声明如下:

/*** @param bio 指向bio结构体的指针* @return    成功返回指向指定数据页的地址*/
void *bio_data(struct bio *bio);

② 逐步获取

这种方法其实就是根据 bvec 循环项先获取到 page 结构体成员,然后将 page 转换成数据页地址

struct page* pagestruct = bvec.bv_page;        // 先获取到与数据页相关的数据类型
void* buffer = page_address(pagestruct);       // 从 page 结构体获取到页地址

4、获取数据长度

在遍历 bio 数组时,bio_vec 的 bv_len 代表当前数据段的长度;bvec_iter 的 bi_size 是当前迭代器所指向的数据段的字节数,这两者是一致的。一般使用 bv_len

int len = bvec.bv_len;

5、读写操作实现

这里的读写操作和上一篇基本一致,使用 bio_data_dir 来判断读写方向

if (bio_data_dir(bio) == WRITE)memcpy(blkdev.diskbuf, buffer + offset, len);
else if (bio_data_dir(bio) == READ)memcpy(buffer + offset, blkdev.diskbuf, len);

6、关闭 bio 操作

和上一篇 blk_end_request 一样,每次请求处理结束,需要做一些收尾工作,如释放用于 I/O 操作的资源、更新状态信息、通知上层文件系统或者块层,还可以处理错误情况。使用的API原型如下:

/*** @param bio     指向bio结构体的指针* @param error   I/O操作的错误码*/
void bio_endio(struct bio *bio, int error);

7、完整示例

void make_request_hanlde(struct request_queue *q, struct bio *bio)
{int offset = 0;int len = 0;struct bio_vec bvec;            // 内存页信息struct bvec_iter iter;          // 块设备扇区信息printk("请求处理函数被触发!\n");bio_for_each_segment(bvec, bio, iter) {// 扇区起始地址// 内存页起始地址void* buffer = page_address(pageAddr);// 内存偏移offset = bvec.bv_offset;// 涉及的数据长度len = bvec.bv_len;if (bio_data_dir(bio) == WRITE){memcpy(blkdev.diskbuf, buffer + offset, len);}else if (bio_data_dir(bio) == READ){memcpy(buffer + offset, blkdev.diskbuf, len);}}bio_endio(bio, 0);
}

替换请求处理函数以后,将块设备注册到内核无异常,输入 fdisk -l 可以看到我们注册的块设备 

此外,fdisk -l 会触发请求处理函数,所以会打印预设的内容。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/676290.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

VRRP配置

目录 网络拓扑图 配置要求 配置步骤 网络拓扑图 配置要求 按照图示配置 IP 地址和网关在 SW1,SW2,SW3 上创建 Vlan10 和 Vlan20,对应 IP 网段如图,交换机之间链路允许所有 VLAN 通过在 SW1 和 SW2 上配置 VRRP,要求…

在windows server2016部署域控服务器DC

1.正常配置vmware虚拟机基础环境 2.启动虚拟机,会先到efi network,等待几分钟 3.进入boot manager,选择启动方式,记得提示CD启动的时候需要按回车,不然又会回到这个界面 4.选择安装版本为桌面版(开始直接…

无题2024

念旧 阿悠悠 专辑:念旧 发行时间 2019-08-25 念旧 播报编辑讨论1上传视频 阿悠悠演唱歌曲 《念旧》是由一博作词,一博和张池作曲,阿悠悠演唱的歌曲,发行于2019年8月25日。 [1]收录于同名专辑《念旧》中。 相关星图 查…

(四)elasticsearch 源码之索引流程分析

https://www.cnblogs.com/darcy-yuan/p/17024341.html 1.概览 前面我们讨论了es是如何启动,本文研究下es是如何索引文档的。 下面是启动流程图,我们按照流程图的顺序依次描述。 其中主要类的关系如下: 2. 索引流程 (primary) 我们用postman发送请求&…

Windows Server 2025 Hyper-V 新变化

今天简单跟大家聊聊Windows Server 2025 Hyper-V一些新功能新变化,具体如下: 在 VM 之间共享 GPU 随着图形处理器的重要性日益增加,特别是由于它们在 AI 应用程序中的核心作用,Hyper-V 中对 GPU 的现有支持已不再足够。到目前为…

[Java][算法 哈希]Day 01---LeetCode 热题 100---01~03

LeetCode 热题 100---01~03 ------->哈希 第一题 两数之和 思路 最直接的理解就是 找出两个数的和等于目标数 这两个数可以相同 但是不能是同一个数字(从数组上理解就是内存上不是同一位置) 解法一:暴力法 暴力解万物 按照需求 …

报错ValueError: Unknown CUDA arch (8.6) or GPU not supported

文章目录 问题描述解决方案参考文献 问题描述 报错 ValueError: Unknown CUDA arch (8.6) or GPU not supported 本人显卡为 RTX 3060,CUDA 为 10.2,PyTorch 为 1.5 解决方案 修改 C:\Users\Administrator\Envs\test\Lib\site-packages\torch\utils\c…

【PyQt】08 - 编辑Tab顺序

文章目录 前言一、Tab顺序二、编辑Tab顺序总结 前言 介绍了什么是Tab顺序,以及如何修改Tab顺序。 一、Tab顺序 当你的界面设计好之后,在输入栏按住Tab按键,他会按照你摆放的顺序一次转跳 二、编辑Tab顺序 方法一 然后鼠标左击就可以改变…

前端JavaScript篇之对this对象的理解

目录 对this对象的理解1. 函数调用模式:2. 方法调用模式:3. 构造器调用模式:4. apply、call和bind调用模式: 对this对象的理解 在JavaScript中,this关键字是一个非常重要的概念,它用于指向当前执行上下文中…

【CV论文精读】EarlyBird: Early-Fusion for Multi-View Tracking in the Bird’s Eye View

【CV论文精读】EarlyBird: Early-Fusion for Multi-View Tracking in the Bird’s Eye View 0.论文摘要 多视图聚合有望克服多目标检测和跟踪中的遮挡和漏检挑战。多视图检测和3D对象检测中的最新方法通过将所有视图投影到地平面并在鸟瞰视图(BEV)中执…

面试经典150题 -- 栈(总结)

总的链接 面试经典 150 题 - 学习计划 - 力扣(LeetCode)全球极客挚爱的技术成长平台 关于栈 -- stack 的学习链接 c的STL中的栈 -- stack-CSDN博客 20 . 有效的括号 这题直接用栈模拟就好了; 这里用一种取巧的方法 , 当遇见左括号,加入右…

JAVA设计模式之建造者模式详解

建造者模式 1 建造者模式介绍 建造者模式 (builder pattern), 也被称为生成器模式 , 是一种创建型设计模式. 定义: 将一个复杂对象的构建与表示分离,使得同样的构建过程可以创建不同的表示。 **建造者模式要解决的问题 ** 建造者模式可以将部件和其组装过程分开…

Dynamo批量处理多个Revit文件?

Hello大家好!我是九哥~ 最近很多小伙伴都在咨询Dynamo如何批量处理多个Revit文件,之前写过一篇《Dynamo批量修改多文件项目基点参数》,利用的是后台打开Revit的方式,可以实现一些批量操作的功能。 但是这个方法,对于一…

物理信息神经网络(PINN): 将物理知识融合到深度学习中

物理信息神经网络(PINN): 将物理知识融合到深度学习中 物理信息神经网络(PINN)简介PINN的工作原理PINN模型如何利用物理法则指导模型训练1. 定义物理问题和相应的物理定律2. 构建神经网络3. 定义损失函数数据误差项 (Data-fidelit…

IoC原理

Spring框架的IOC是基于Java反射机制实现的,那具体怎么实现的,下面研究一下 反射 Java反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法…

多 split 窗口 in Gtkmm4

文章目录 效果预览实现概要源代码 效果预览 实现概要 使用Gtk::Paned虽然 Paned 只能装两个子控件, 但是我可以嵌套 paned1 装 box1 和 box2 paned2 装 paned1 和 box3 源代码 #include <gtkmm.h> class ExampleWindow : public Gtk::Window { public:ExampleWindow()…

大模型基础架构的变革:剖析Transformer的挑战者(下)

上一篇文章中&#xff0c;我们介绍了UniRepLKNet、StripedHyena、PanGu-π等有可能会替代Transformer的模型架构&#xff0c;这一篇文章我们将要介绍另外三个有可能会替代Transformer的模型架构&#xff0c;它们分别是StreamingLLM、SeTformer、Lightning Attention-2&#xff…

07 A B 从计数器到可控线性序列机

07. A.从计数器到可控线性序列机 让LED灯按照亮0.25秒。灭0.75秒的状态循环亮灭让LED灯按照亮0.25秒&#xff0c;灭0.5秒&#xff0c;亮0.75秒&#xff0c;灭1秒的状态循环亮灭让LED灯按照指定的亮灭模式亮灭&#xff0c;亮灭模式未知&#xff0c;由用户随即指定。以0.25秒为一…

高职单招怎么搜答案? #经验分享#微信#笔记

当今社会&#xff0c;随着信息技术的迅猛发展&#xff0c;大学生们在学习过程中面临着各种各样的困难和挑战。而在这些挑战中&#xff0c;面对繁重的作业和复杂的题目&#xff0c;大学生搜题软件应运而生 1.题老大 这是一个公众号 亿级数量题库&#xff0c;可截图搜题&#…

动态SQl简单创建

创建pojo实体类&#xff0c;使用lombok注解 package com.example.pojo;import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor;import java.time.LocalDate; import java.time.LocalDateTime;Data NoArgsConstructor AllArgsConstructor pu…