【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.hqwc.cn/news/460533.html

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

相关文章

SUSE 12 SP5安装Ansible2.9.6

安装依赖 zypper install gcc python-devel安装pip wget https://pypi.python.org/packages/source/s/setuptools/setuptools-11.3.tar.gz tar -xf setuptools-11.3.tar.gz cd setuptools-11.3 python setup.py install easy_install https://mirrors.aliyun.com/pypi/packag…

单片机的省电模式及策略

目录 一、单片机省电的核心策略 二、单片机IO口的几种模式 三、单片机的掉电运行模式 (1) 浅谈cpu运行为什么会需要时钟? (2)STC15系列单片机内部可以配置时钟 (3)分频策略,降低…

机器学习——有监督学习和无监督学习

有监督学习 简单来说,就是人教会计算机学会做一件事。 给算法一个数据集,其中数据集中包含了正确答案,根据这个数据集,可以对额外的数据希望得到一个正确判断(详见下面的例子) 回归问题 例如现在有一个…

vue3的常用功能

文章目录 一、前言二、自动注册全局组件2.1、自动注册components目录下所有vue组件并以组件的文件名为组件的名称2.2、使用这个插件2.3、为全局组件添加类型提示 三、函数式图片预览四、手动封装 svgIcon 组件五、封装拖拽钩子函数六、vscode 中 vue3 代码片段七、最后 一、前言…

vue3:25—其他API

目录 1、shallowRef和shallowReactive 2、readonly与shallowReadonly readonly shallowReadonly 3、toRaw和markRaw toRaw markRaw 4、customRef 1、shallowRef和shallowReactive shallowRef 1.作用:创建一个响应式数据,但只对顶层属性进行响应式处理。2…

[Vulnhub靶机] DriftingBlues: 4

[Vulnhub靶机] DriftingBlues: 4靶机渗透思路及方法(个人分享) 靶机下载地址: https://download.vulnhub.com/driftingblues/driftingblues4_vh.ova 靶机地址:192.168.67.23 攻击机地址:192.168.67.3 一、信息收集 …

Python数据可视化库之mplfinance使用详解

概要 Python 是一种强大的编程语言,拥有众多用于数据可视化的库和工具。其中之一是 mplfinance(Matplotlib Finance),它是基于 Matplotlib 的库,专门用于创建金融图表和交互式金融数据可视化。本文将深入介绍 mplfinance,包括其基本概念、功能特性以及如何使用示例代码创…

“手把手教你玩转函数递归,建议收藏!“

目录 1. 什么是递归 2. 递归的限制条件 3. 递归的举例 4. 递归与迭代 正⽂开始 1. 递归是什么? 递归是学习C语⾔函数绕不开的⼀个话题,那什么是递归呢? 递归其实是⼀种解决问题的⽅法,在C语⾔中,递归就是函数⾃…

阿里云服务器多少钱?2024年阿里云服务器租用费用表大全

2024年阿里云服务器租用价格表更新,云服务器ECS经济型e实例2核2G、3M固定带宽99元一年、ECS u1实例2核4G、5M固定带宽、80G ESSD Entry盘优惠价格199元一年,轻量应用服务器2核2G3M带宽轻量服务器一年61元、2核4G4M带宽轻量服务器一年165元12个月、2核4G服…

免费生成ios证书的方法(无需mac电脑)

使用hbuilderx的uniapp框架开发移动端程序很方便,可以很方便地开发出移动端的小程序和app。但是打包ios版本的app的时候却很麻烦,官方提供的教程需要使用mac电脑来生成证书,但是mac电脑却不便宜,一般的型号都差不多上万。 因此&a…

java_error_in_pycharm.hprof文件是什么?能删除吗?

java_error_in_pycharm.hprof文件是什么?能删除吗? 🌵文章目录🌵 🌳引言🌳🌳hprof格式文件介绍🌳🌳java_error_in_pycharm.hprof文件什么情况下能删除🌳&…

C#静态数组删除数组元素不改变数组长度 vs 动态数组删除数组元素改变数组长度

目录 一、使用的方法 1.对静态数组删除指定长度并不改变数长度的方法 (1)静态数组 (2)对静态数组删除元素不得改变其长度 2.对动态数组删除指定长度并改变数长度的方法 (1)动态数组 (2&a…