【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");

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

抵御.360勒索病毒威胁:解密文件的有效方法与预防措施

导言&#xff1a; 近来&#xff0c;网络犯罪的一种新型形式——.360勒索病毒&#xff0c;备受关注。这种病毒通过加密用户文件&#xff0c;要求支付赎金以获取解密密钥。本文91数据恢复将深入介绍.360勒索病毒的特点&#xff0c;同时提供一些有效的恢复方法&#xff0c;并分享…

盘点那些硬件+项目学习套件:STM32MP157 Linux开发板及入门常见问题解答

华清远见20岁了~过去3年里&#xff0c;华清远见研发中心针对个人开发板业务&#xff0c;打造了多款硬件项目学习套件&#xff0c;涉及STM32单片机、嵌入式、物联网、人工智能、鸿蒙、ESP32、阿里云IoT等多技术方向。华清远见20岁了~过去3年里&#xff0c;华清远见研发中心针对个…

资深Android逆袭、华为鸿蒙为安卓程序员开辟了一条新道路

本文章主要从以下5个方面来展开聊聊这个话题&#xff1a; 1.什么是鸿蒙 2.鸿蒙系统发展时间线 3.鸿蒙是套壳Android吗&#xff1f; 4.鸿蒙的生态&#xff08;用户以及开发者&#xff09; 5.一些建议 1月18日&#xff0c;在鸿蒙生态千帆启航仪式上&#xff0c;华为宣布了继鸿蒙4…

【排序算法】C语言实现随机快排,巨详细讲解

文章目录 &#x1f680;前言&#x1f680;快排的核心过程partition&#xff08;划分过程&#xff09;&#x1f680;快排1.0&#x1f680;随机快速排序&#x1f680;稳定性 &#x1f680;前言 铁子们好啊&#xff01;继续我们排序算法今天要讲的是快排&#xff0c;通常大家所说…

ROS方向第二次汇报(2)

文章目录 1.本方向内学习内容&#xff1a;1.1.动作&#xff1a;1.1.1.案例接口定义:1.1.2.案例通信模型&#xff1a;1.1.3.服务器端代码&#xff1a;1.1.4.客户端源代码&#xff1a;1.1.5.动作命令行操作&#xff1a; 1.2.参数&#xff1a;1.2.1.查看参数列表&#xff1a;1.2.2…

14篇最新Transformer热门论文!涵盖注意力机制、架构改进、适用性扩展等

在深度学习技术的飞速发展中&#xff0c;Transformer模型无疑成为了当今研究的热点&#xff0c;它凭借其独特的架构和强大的表达能力&#xff0c;在自然语言处理、计算机视觉和语音识别等领域取得了令人瞩目的成果。 今天&#xff0c;特意为大家整理了14篇Transformer热门论文&…

locust--python实现的分布式性能测试工具

1.locust特点&#xff1a; 1.1 支持Python编写测试用例方案&#xff1b; 1.2 使用requests发送http请求&#xff1b; 1.3 使用协程实现&#xff0c;高并发时消耗更低&#xff1b; 1.4 使用Flask提供 Web UI&#xff1b; 1.5 有第三方插件支持扩展&#xff1b; 2.创建locust 性能…

嵌入式学习第十四天

1.结构体&#xff08;2&#xff09;: &#xff08;1&#xff09;结构体类型定义 &#xff08;2&#xff09;结构体变量的定义 &#xff08;3&#xff09;结构体元素的访问 &#xff08;4&#xff09;结构体的存储: 内存对齐: char 按照1字节对齐 …

人工智能与机器学习——开启智能时代的里程碑

写在前面 前言人工智能与机器学习的概述监督学习、无监督学习和强化学习的基本原理监督学习&#xff1a;无监督学习&#xff1a;强化学习&#xff1a; 机器学习的算法和方法常见的机器学习算法和方法线性回归&#xff1a;决策树&#xff1a;支持向量机&#xff1a;神经网络&…

Vue3项目封装一个Element-plus Pagination分页

前言:后台系统分页肯定是离不开的,但是ui框架都很多,我们可以定义封装一种格式,所有项目按到这个结构来做. 实例: 第一步:在项目components组件新建一个分页组件,用来进行封装组件. 第二步:根据官方的进行定义,官方提供的这些,需要我们封装成动态模式 第三步:代码改造 <!-…

【C/C++】深入理解--函数重载(什么是函数重载?为什么要有函数重载?)

目录 一、前言 二、 函数重载 &#x1f34e;什么是函数重载 &#x1f350;函数重载的条件 &#x1f347;函数重载的注意点 &#x1f349;为什么要有函数重载 &#x1f353;为何C语言不支持函数重载&#xff0c;反倒C可以&#xff1f; &#x1f4a6; Linux环境下演示函数重…

【Git管理工具】

Git管理工具 分支约定主分支辅助分支使用规范&#xff1a;代码提交规范项目权限分支使用 俗话说&#xff1a;没有规矩&#xff0c;不成方圆。遵循一个好的规章制度能让你的工作事半功倍。同时也可以展现出你做事的认真的态度以及你的专业性&#xff0c;不会显得杂乱无章&#x…

【Cocos入门】Cocos中的定时器 (setTimeOut 、setInterval、Schedule )

目录 一、setTimeOut二、setInterval三、Schedule四、全局的schedule 一、setTimeOut 只执行一次 3秒后打印abc。 setTimeout(()>{console.log("abc"); }, 3000);删除计时器&#xff0c;3秒后不会输出abc。 let timeIndex; timeIndex setTimeout(()>{conso…

2024西湖论剑misc方向wp

每年的misc都是最无聊坐牢的 数据安全-easy_tables import pandas as pd import hashlib from datetime import datetimeusers_df pd.read_csv(users.csv) permissions_df pd.read_csv(permissions.csv) tables_df pd.read_csv(tables.csv) actionlog_df pd.read_csv(acti…

外汇监管牌照解析:确保交易安全与合规性

外汇交易中&#xff0c;资金安全与平台监管是大家最关心的话题。监管是评估外汇经纪商是否值得信赖、是否具备相关资质的关键依据&#xff0c;因此选择一家拥有海外合法监管的经济商至关重要。 那么&#xff0c;今天我们就来聊聊全球权威的几大监管机构 — FCA、ASIC、NFA、FSA…

(2024,定性评估、定量评估、人类评估)神经风格转移评估:综述

Evaluation in Neural Style Transfer: A Review 公和众和号&#xff1a;EDPJ&#xff08;进 Q 交流群&#xff1a;922230617 或加 VX&#xff1a;CV_EDPJ 进 V 交流群&#xff09; 目录 0. 摘要 1. 简介 2. 神经风格转移方法 0. 摘要 神经风格转移&#xff08;Neural St…

2024全力推进七大流域数字孪生整体立项建设

2024年伊始&#xff0c;各大流域委密集召开会议或发布重要文件&#xff0c;部署开展各流域数字孪生建设。 1月中旬&#xff0c;《中国水利》杂志刊发了珠江委党组书记、主任王宝恩署名文章《坚定不移推动高质量发展 为中国式现代化贡献珠江水利力量》。珠江委积极践行“江河战略…

PYTHON蓝桥杯——每日一练(简单题)

题目 对于长度为5位的一个01串&#xff0c;每一位都可能是0或1&#xff0c;一共有32种可能。它们的前几个是&#xff1a; 00000 00001 00010 00011 00100 请按从小到大的顺序输出这32种01串。 输入格式 本试题没有输入。 输出格式 输出32行&#xff0c;按从小到大的…

大数据学习之Redis,十大数据类型的具体应用(一)

目录 3. 数据类型命令及落地应用 3.1 备注 3.2 Redis字符串&#xff08;String&#xff09; 单值单value 多值操作 获取指定区间范围内的值 数值增减 获取字符串长度和内容追加 分布式锁 getset(先get后set) 3.3 Redis列表&#xff08;List&#xff09; 简单说明 …

pve web无法访问

一、问题描述 我这边修改了网络,导致ip发生了变更,pve网页版直接登不上了,ssh又可以登录。 二、解决方法 首先确认是不是网络的问题&#xff0c;我这边是内网&#xff0c;有多个路由器&#xff0c;笔记本连的是一个网段&#xff0c;pve又是一个网段&#xff0c;通过ping&…