【Linux驱动】块设备驱动(一)—— 注册块设备

针对块设备驱动将分为两部分介绍,第一部分是注册块设备,即将块设备成功添加到内核;第二部分是介绍如何读写块设备,因为没有实际块设备,这里选择使用内存来模拟块设备。

一、认识块设备

1、什么是块设备

块设备针对的是存储设备,如SD卡、EMMC、机械硬盘,与字符设备相比,块设备的读写是以块为单位,通过使用缓冲区来暂时保存数据,等满足一定条件再一次写入到块设备。这么做可以减少对块设备的擦除次数,降低块设备的读写频率,提高使用寿命。

2、块设备类型

Linux 内核提供了数据类型 block_device 来表示块设备,其中 gendisk 保存了块设备的属性信息及可执行的操作,如块设备号、设备容量、块设备分区等;bd_queue 缓存来自文件系统的读写请求,块设备会对这些请求逐个处理

struct block_device {/* ... */struct gendisk *	bd_disk;struct request_queue *  bd_queue;/* ... */
};struct gendisk {int major;			            /* major number of driver */int first_minor;int minors;                     /* maximum number of minors, =1 for* disks that can't be partitioned. */char disk_name[DISK_NAME_LEN];	/* name of major driver *//* ... */const struct block_device_operations *fops;struct request_queue *queue;void *private_data;
}

注意:block_device 和 gendisk 都包含 request_queue 类型的成员, 实际上他们指向的是同一个请求队列,因此,在初始化时,初始化其中一个即可,一般初始化gendisk中的请求队列。

二、模拟设备创建

现在我们打算通过内存来模拟实现一个块设备,与上面介绍的流程相差无几,相当于将交互对象由块设备替换成了内存。

#define blkdev_NAME             "blk-dev"              // 块设备名称
#define DISK_MINOR              1                      // 模拟块设备的分区数
#define DISK_SIZE               3 * 1024 * 1024        // 模拟块设备的大小(3MB),单位:字节struct blk_dev {uint8_t*                diskbuf;         // 一小块内存,用于模拟块设备int                     major;          // 块设备主设备号spinlock_t              lock;           // 自旋锁struct gendisk*         gendisk;        // 块设备(保存了块设备相关信息)struct request_queue*   queue;          // 请求队列
};
static struct blk_dev blkdev;

三、注册块设备

第1~3步都是在为第4步初始化gendisk做准备。

1、申请主设备号

无论是字符设备还是块设备,都有一个设备号,下面为当前块设备申请主设备号。

相关API:

/*** @brief       注册块设备* @param major 主设备号(0表示由OS自动分配设备号,1~255表示自定义主设备号)* @param name  块设备名称* @return      成功返回主设备号,失败返回负值*/
int register_blkdev(unsigned int major, const char *name);/*** @brief       注销块设备* @param major 主设备号* @param name  块设备名称*/
void unregister_blkdev(unsigned int major, const char *name);

实际应用:

/* 注册块设备 */
blkdev.major = register_blkdev(0, blkdev_NAME);
if (blkdev.major < 0)
{printk("block device register failed!\n");return -1;
}

2、申请gendisk

Linux内核为了方便保存块设备属性信息,提供了名为 gendisk 的数据类型。

相关API:

/*** @brief        申请gendisk* @param minors 申请的分区数(相当于在告诉内核块设备有多少个分区)* @return       成功返回gendisk的地址,失败返回NULL*/
struct gendisk *alloc_disk(int minors);/*** @brief       释放gendisk* @param gp    要删除的gendisk*/
void del_gendisk(struct gendisk *gp);

实际应用:

blkdev.gendisk = alloc_disk(DISK_MINOR);
if (blkdev.gendisk == NULL)
{printk("gendisk allocate failed\n");unregister_blkdev(blkdev.major, blkdev_NAME);return -1;
}

3、初始化请求队列

请求队列是一种用于存储待处理的请求的数据结构,当用户发起I/O操作时,Linux内核可以有效管理这些请求,如优先级较高的请求优先处理,这样可以提高系统的性能和响应速度。

相关API如下

/*** @brief        初始化请求队列* @param rfn    请求处理函数*               函数指针:void (request_fn_proc) (struct request_queue *q)* @param lock   自旋锁    * @return       成功返回请求队列的地址,失败返回NULL*/
request_queue *blk_init_queue(request_fn_proc *rfn, spinlock_t *lock);/*** @brief       删除请求队列* @param q     要删除的请求队列*/ 
void blk_cleanup_queue(struct request_queue *q);
  • rfn: 当块设备收到一个IO请求时,请求处理函数会被触发来处理这个请求
  • lock:自旋锁,需要自己从外部传递,请求队列作为临界资源,自然需要考虑互斥问题

实际应用:

spin_lock_init(&blkdev.lock);
blkdev.queue = blk_init_queue(request_handle, &blkdev.lock);
if (blkdev.queue == NULL)
{printk("gendisk allocate failed\n");del_gendisk(blkdev.gendisk); unregister_blkdev(blkdev.major, blkdev_NAME);return -1;
}

请求处理函数:

// 请求处理函数
void request_handle(struct request_queue *q)
{
}

 

4、初始化 gendisk

接下来将对 gendisk 结构体中的一部分成员进行初始化,有些是必须要初始化的,如主设备号、块设备操作函数、设备名、请求队列等。

每一个块设备都有大小,要设置块设备大小需要用到 set_capacity,函数声明如下:

/*** @brief        设置块设备大小* @param disk   gendisk 指针* @param size   块设备大小,单位: 扇区*/
void set_capacity(struct gendisk *disk, sector_t size);

初始化内容如下:

// 块设备操作函数
static struct block_device_operations blkdev_fops = {.owner = THIS_MODULE,
};blkdev.gendisk->major = blkdev.major;                    // 主设备号
blkdev.gendisk->first_minor = 0;                         // 起始次设备号
blkdev.gendisk->fops = &blkdev_fops;                     // 块设备操作函数
blkdev.gendisk->queue = blkdev.queue;                    // 块设备请求队列
strcpy(blkdev.gendisk->disk_name, blkdev_NAME);          // 块设备名称
set_capacity(blkdev.gendisk, DISK_SIZE/512);             // 告诉内核块设备大小,单位:扇区

5、添加到内核

最终将初始化好的 gendisk 添加到内核,相当于在告诉内核当前块设备的相关信息,因为内核无法主动得知块设备信息。

相关API:

/*** @brief        将gendisk添加到内核* @param disk   gendisk 指针*/
void add_disk(struct gendisk *disk);

实际应用:

add_disk(blkdev.gendisk);

 

四、补充:分配内存

前面只是设置了块设备的大小为3MB,但实际上并没有分配这块内存,因为到下一步读写块设备才会需要操作内存,所以申请一块3MB的内存在当前阶段非必须。

分配内存可以使用kmalloc 或者 kzalloc,效果一样,区别在于kzalloc在分配的同时会先清空内存,对应的使用 kfree 来释放内存。

/*** @param size    分配的内存大小,单位: 字节* @param flags   内存分配的标志,用于指定内存分配的行为(如内存对齐、内存映射类型等)* @return        返回一个指向分配的内存块的指针,如果分配失败则返回NULL*/
void* kzalloc(size_t size, gfp_t flags);/*** @param ptr    内存块的地址*/
void kfree(const void *ptr);
flags可选项解析
GFP_KERNEL表示内存分配应该在可抢占的内核上下文中进行,并且可以使用内核的页面置换机制(最常用的标志,用于普通的内核操作)
GFP_ATOMIC表示内存分配应该在不可抢占的内核上下文中进行,且不可被页面置换,以避免死锁或其他错误。这通常用于中断处理程序或其他不能延迟等待内存分配的上下文。
GFP_DMA表示内存分配应该返回可以通过内存DMA访问的内存块。这用于需要进行DMA传输的设备驱动程序。
GFP_DMA32类似于GFP_DMA,但是指定返回的内存应该位于32位物理地址空间内,以满足32位DMA的限制。如果系统支持大于4GB的物理地址空间,则该标志将被忽略。
GFP_HIGHUSER表示内存分配应该返回用户态进程可以访问的内存块。这用于在内核中为用户态进程分配内存。
GFP_NOFS表示内存分配应该在文件系统内部的上下文中进行,且不能导致内核执行文件系统操作(如页面缓存)。这通常用于文件系统代码中,以避免死锁或其他错误。
GFP_NOWAIT表示在无法立即获得所需的内存时,kmallocvmalloc函数应立即返回NULL,而不是等待内存可用再分配。
GFP_ZERO表示分配的内存块应该在分配后清零。这个标志可以用于kmallockzalloc函数。

实际使用:

blkdev.diskbuf = kzalloc(DISK_SIZE, GFP_KERNEL);
if (blkdev.diskbuf == NULL)
{return -1;
}

五、完整代码(待完善)

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/uaccess.h>
#include <linux/fs.h>           // 注册/注销块设备
#include <linux/genhd.h>        // 申请/释放gendisk
#include <linux/blkdev.h>       // 申请/释放请求队列#define blkdev_NAME             "blk-dev"       // 块设备名称
#define DISK_MINOR              1               // 模拟块设备的分区数
#define DISK_SIZE               3 * 1024 * 1024        // 模拟块设备的大小,单位:字节struct blk_dev {uint8_t*                diskbuf;         // 一小块内存,用于模拟块设备int                     major;          // 块设备主设备号spinlock_t              lock;           // 自旋锁struct gendisk*         gendisk;        // 块设备(保存了块设备相关信息)struct request_queue*   queue;          // 请求队列
};
static struct blk_dev blkdev;static struct block_device_operations blkdev_fops = {.owner = THIS_MODULE,
};void request_handle(struct request_queue *q)
{}static int __init blkdriver_init(void)
{/* 分配内存 */blkdev.diskbuf = kzalloc(DISK_SIZE, GFP_KERNEL);if (blkdev.diskbuf == NULL){return -1;}/* 注册块设备 */blkdev.major = register_blkdev(0, blkdev_NAME);if (blkdev.major < 0){printk("block device register failed!\n");return -1;}/* 申请 gendisk */blkdev.gendisk = alloc_disk(DISK_MINOR);if (blkdev.gendisk == NULL){printk("gendisk allocate failed\n");unregister_blkdev(blkdev.major, blkdev_NAME);return -1;}/* 申请请求队列 */spin_lock_init(&blkdev.lock);blkdev.queue = blk_init_queue(request_handle, &blkdev.lock);if (blkdev.queue == NULL){printk("gendisk allocate failed\n");del_gendisk(blkdev.gendisk); unregister_blkdev(blkdev.major, blkdev_NAME);return -1;}/* 初始化 gendisk */blkdev.gendisk->major = blkdev.major;                    // 主设备号blkdev.gendisk->first_minor = 0;                         // 起始次设备号blkdev.gendisk->fops = &blkdev_fops;                     // 块设备操作函数blkdev.gendisk->queue = blkdev.queue;                    // 块设备请求队列strcpy(blkdev.gendisk->disk_name, blkdev_NAME);          // 块设备名称set_capacity(blkdev.gendisk, DISK_SIZE/512);             // 告诉内核块设备大小,单位:扇区/* 添加到内核 */add_disk(blkdev.gendisk);return 0;	
}static void __exit blkdriver_exit(void)
{/* 释放内存 */kfree(blkdev.diskbuf);/* 注销块设备 */unregister_blkdev(blkdev.major, blkdev_NAME);/* 释放gendisk */del_gendisk(blkdev.gendisk); /* 清除请求队列 */blk_cleanup_queue(blkdev.queue);
}module_init(blkdriver_init);
module_exit(blkdriver_exit);MODULE_LICENSE("GPL");
MODULE_AUTHOR("author_name");

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

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

相关文章

浏览器内存泄漏排查指南

1、setTimeout执行原理 使用setInterval/setTimeOut遇到的坑 - 掘金 2、Chrome自带的Performance工具 当我们怀疑页面发生了内存泄漏的时候&#xff0c;可以先用Performance录制一段时间内页面的内存变化。 点击开始录制执行可能引起内存泄漏的操作点击停止录制 如果录制结束…

【Java基础】之进程与线程

进程与线程 1. 线程与进程1.1 概念1.2 区别与联系 2. 线程的5种状态和切换2.1 简单介绍2.2 状态切换2.2.1 重点情况 3. 线程中常见的方法4. 线程池 1. 线程与进程 1.1 概念 进程&#xff1a;资源分配的基本单元&#xff0c;如QQ音乐 线程&#xff1a;资源调度的基本单元&…

关于美图秀秀如何给证件照快速抠图换背景操作

日常生活中一些经常处理的小技巧&#xff0c;记录以备以后使用&#xff0c;也方便别人&#xff0c;希望能帮到大家。 1、先导入一张相片&#xff0c;点击AI人像抠图&#xff1b; 2、再点应用当前效果&#xff1b; 3、再点击自动抠图或手动抠图或形状抠图;就可以点击换背景 4、…

Pyecharts绘制多彩气泡图:从基础到高级定制【第49篇—python:多彩气泡图】

Pyecharts绘制多种炫酷气泡图参数说明代码实战 引言 数据可视化是数据分析中不可或缺的一环&#xff0c;而Pyecharts作为一款基于Echarts的Python图表库&#xff0c;提供了丰富的图表类型&#xff0c;其中气泡图是一种常用于展示三维数据的炫酷图表。本文将介绍如何使用Pyech…

Git 介绍 与 配置

Git 介绍 Git是一个分布式版本控制系统&#xff0c;用于跟踪文件的更改和协作开发。它可以管理项目的版本历史记录&#xff0c;并允许多个开发者在同一时间进行并行开发。 解决上图产生的问题就出现了git 分布式版本控制系统 看下图 Git 配置 Git的基本配置包括用户名和电子邮…

leetcode hot100岛屿的最大面积

本题是让求岛屿的最大面积&#xff0c;和上一个题求岛屿的数量类似&#xff0c;也是通过dfs或者bfs进行求解。 那么&#xff0c;首先我们判断dfs函数的参数&#xff0c;需要grid[][]&#xff0c;需要横坐标i&#xff0c;纵坐标j。那么&#xff0c;这里我们求的是最大面积&…

2万块的郎酒,都是我们惯的

文 | 琥珀酒研社 作者 | 五画 当我看到郎酒拿出快2万一瓶纪念酒的时候&#xff0c;我就知道&#xff0c;这场高价酒的喧嚣和吵闹&#xff0c;又到了一个新的高度。 和别的行业有所不同&#xff0c;白酒很少谈智商税&#xff0c;再高的价格&#xff0c;总有个冠冕堂皇的理由。…

Linux文本三剑客---awk经典案例

awk&#xff08;是一种处理文本文件的应用程序&#xff0c;它依次处理文件的每一行&#xff0c;并读取里面的每一个字段。&#xff09; awk 包含几个特殊的内建变量&#xff08;可直接用&#xff09;如下所示&#xff1a; 1、获取根分区剩余大小 #可以使用df -h命令来查看所有…

ElementUI Form:InputNumber 计数器

ElementUI安装与使用指南 InputNumber 计数器 点击下载learnelementuispringboot项目源码 效果图 el-radio.vue 页面效果图 项目里el-input-number.vue代码 <script> export default {name: el_input_number,data() {return {num: 1,num5: 1,num6: 1,num7: 1,num8:…

Logstash 7.7.1版本安装系统梳理

前言 上一篇文章介绍了 《ElasticSearch7.7.1集群搭建 & Kibana安装》&#xff0c;今天说一下 Logstash的安卓和配置&#xff1b; Logstash是一个开源的数据收集引擎&#xff0c;具有实时管道功能。它可以动态地将来自不同数据源的数据统一起来&#xff0c;并将数据标准化…

多线程代码案例之线程池

作者简介&#xff1a; zoro-1&#xff0c;目前大二&#xff0c;正在学习Java&#xff0c;数据结构&#xff0c;javaee等 作者主页&#xff1a; zoro-1的主页 欢迎大家点赞 &#x1f44d; 收藏 ⭐ 加关注哦&#xff01;&#x1f496;&#x1f496; 创建线程池 public class Poo…

Xmind文件转CSV、Excel文件

不知道小伙伴们有没有发现&#xff0c;新版Xmind文件转Excel是收费的功能。因为自己不经常使用Xmind&#xff0c;收到一个这样的文件&#xff0c;需要转换成Excel&#xff0c;也不不值得破解或者付费。在github上有一个工具XMind2TestCase&#xff0c;非常不错&#xff0c;可以…

网安人必看!CISP家族顶流证书攻略

网络安全已成为当今的热门领域&#xff0c;证书在职业发展中的重要性不言而喻。但是&#xff0c;证书市场五花八门&#xff0c;选择适合自己的证书可是个大问题。别担心&#xff0c;今天我们就来聊聊CISP家族的几个热门认证&#xff0c;让你在网络安全领域的发展更加顺利&#…

在虚拟环境中导出和安装requirements.txt文件

背景&#xff1a; ​ 一般在项目开放完成后&#xff0c;我们需要把项目工程所需要的虚拟环境依赖包导出&#xff0c;以便在服务器上进行安装和配置&#xff0c;这时候我们一般将所需要的python相关库导出一个txt文件&#xff0c;后续在服务器上之前pip安装即可。 措施&#x…

【HarmonyOS 4.0 应用开发实战】TypeScript入门之接口详讲

个人名片&#xff1a; &#x1f43c;作者简介&#xff1a;一名大三在校生&#xff0c;喜欢AI编程&#x1f38b; &#x1f43b;‍❄️个人主页&#x1f947;&#xff1a;落798. &#x1f43c;个人WeChat&#xff1a;hmmwx53 &#x1f54a;️系列专栏&#xff1a;&#x1f5bc;️…

Nicn的刷题日常之带空格直角三角形图案

1.题目描述 描述 KiKi学习了循环&#xff0c;BoBo老师给他出了一系列打印图案的练习&#xff0c;该任务是打印用“*”组成的带空格直角三角形图案。 输入描述&#xff1a; 多组输入&#xff0c;一个整数&#xff08;2~20&#xff09;&#xff0c;表示直角三角形直角边的长度&am…

测试环境搭建整套大数据系统(二:安装jdk,mysql)

一&#xff1a;安装JDK 参考 https://blog.csdn.net/weixin_43446246/article/details/123328558 二&#xff1a;安装mysql 1.因为我们安装cdh6.3.2。cdh支持的是5.6和5.7版本的mysql。 2. 步骤 wget https://downloads.mysql.com/archives/get/p/23/file/mysql-server_5.7.…

RT-Thread:STM32的PB3,PB4 复用IO配置为GPIO

说明&#xff1a;在使用 STM32F103CBT6 配置了 PB3 为IO&#xff0c;测试时发现读取这个IO的电平时钟是0&#xff0c;即便单管脚上的电平是1&#xff0c;读取的数据任然是0,查规格书后发现PB3,PB4是JTAG复用口&#xff0c;要当普通IO用需要配置。 配置工具&#xff1a;STM32Cu…

【C++进阶08】哈希的应用(位图and布隆过滤器)

一、位图 1.1 位图的概念 面试题 给40亿个不重复的无符号整数&#xff0c;没排过序 给一个无符号整数&#xff0c;如何快速判断一个数是否在 这40亿个数中。【腾讯】 能想到的解决思路&#xff1a; 遍历&#xff0c;时间复杂度O(N)排序(O(NlogN)) 利用二分查找: logN放到哈…

242. 有效的字母异位词(力扣)(C语言题解)

✨欢迎来到脑子不好的小菜鸟的文章✨ &#x1f388;创作不易&#xff0c;麻烦点点赞哦&#x1f388; 所属专栏&#xff1a;刷题 我的主页&#xff1a;脑子不好的小菜鸟 文章特点&#xff1a;关键点和步骤讲解放在 代码相应位置 前提&#xff1a; 看本文章之前&#xff0c;建…