3. 驱动框架(块设备)

块设备

  • [1. 块设备驱动](#1. 块设备驱动)
    • [1.1 块设备驱动---驱动框架](#1.1 块设备驱动---驱动框架)

1.

块设备驱动相比字符设备驱动的主要区别如下:

①、块设备只能以块为单位进行读写,块是 linux 虚拟文件系统(VFS)基本的数据传输单位。字符设备是以字节为单位进行数据传输的,不需要缓冲

②、块设备在结构上是可以进行随机访问的,对于这些设备的读写都是按块进行的,块设备使用缓冲区来暂时存放数据,等到条件成熟以后再一次性将缓冲区中的数据写入块设备中。这么做的目的为了提高块设备寿命,大家如果仔细观察的话就会发现有些硬盘或者 NAND Flash 就会标明擦除次数(flash 的特性,写之前要先擦除),比如擦除 100000 次等。因此,为了提高块设备寿命引入了缓冲区,数据先写入到缓冲区中,等满足一定条件后再一次性写入到真正的物理存储设备中,这样就减少了对块设备的擦除次数,提高了块设备寿命

1.1 ---驱动框架

  1. module_init(xxx_init) 向 Linux 内核注册一个模块加载函数(xxx_init),当使用"insmod"命令加载驱动时,xxx_init 函数就会被调用

    1.1 kzalloc()申请用于ramdisk内存

    1.2 register_blkdev()向 Linux 内核注册块设备block_device

    1.3 alloc_disk()分配并初始化磁盘ramdisk.gendisk

    1.4 blk_init_queue()分配并初始化请求队列ramdisk.queue,并指定请求处理函数xxx_request_fn()

      1.4.1 blk_fetch_request()循环处理请求队列中的每个请求

      1.4.2 读写 bio 中的数据缓冲区来读写磁盘

      1.4.3 __blk_end_request_cur()处理完请求队列中的所有请求

    1.5 add_disk()将申请并初始化好的磁盘ramdisk.gendisk添加到内核中

  2. module_exit(xxx_exit) 向 Linux 内核注册一个模块卸载函数(xxx_exit),当使用"rmmod"命令卸载驱动时, xxx_exit 函数就会被调用

    2.1 del_gendisk()删除磁盘ramdisk.gendisk

    2.2 put_disk()减少磁盘ramdisk.gendisk 的引用计数

    2.3 blk_cleanup_queue()删除请求队列ramdisk.queue

    2.4 unregister_blkdev()向 Linux 内核注销块设备

    2.5 kfree()释放申请ramdisk内存

  3. 添加模块 LICENSE 信息和作者信息

    3.1 MODULE_LICENSE("GPL");

    3.2 MODULE_AUTHOR("xiedong");



c 复制代码
#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/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/i2c.h>
#include <linux/genhd.h>
#include <linux/blkdev.h>
#include <linux/hdreg.h>

#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>


#define RAMDISK_SIZE	(2 * 1024 * 1024) 	/* 容量大小为2MB */

/* ramdisk设备结构体 */
struct ramdisk_dev{
	int major;					// 主设备号
	unsigned char *ramdiskbuf;	// ramdisk内存空间,用于模拟块设备
	spinlock_t lock;			// 自旋锁 
	struct gendisk *gendisk; 	// gendisk
	struct request_queue *queue;// 请求队列 

};
struct ramdisk_dev ramdisk;		/* ramdisk设备 */

/*
* @description		: 打开块设备
* @param - dev 	: 块设备
* @param - mode 	: 打开模式
* @return 			: 0 成功;其他 失败
*/
int ramdisk_open(struct block_device *dev, fmode_t mode)
{
	printk("ramdisk open\r\n");
	return 0;
}

/*
* @description		: 释放块设备
* @param - disk 	: gendisk
* @param - mode 	: 模式
* @return 			: 0 成功;其他 失败
*/
void ramdisk_release(struct gendisk *disk, fmode_t mode)
{
	printk("ramdisk release\r\n");
}

/*
* @description		: 获取磁盘信息
* @param - dev 	: 块设备
* @param - geo 	: 模式
* @return 			: 0 成功;其他 失败
*/
int ramdisk_getgeo(struct block_device *dev, struct hd_geometry *geo)
{
	/* 这是相对于机械硬盘的概念 */
	geo->heads = 2;			/* 磁头 */
	geo->cylinders = 32;	/* 柱面 */
	geo->sectors = RAMDISK_SIZE / (2 * 32 *512); /* 一个磁道上的扇区数量 */
	return 0;
}

// 块设备操作函数 
static struct block_device_operations ramdisk_fops =
{
	.owner	 	= THIS_MODULE,
	.open	 	= ramdisk_open,
	.release 	= ramdisk_release,
	.getgeo  	= ramdisk_getgeo,
};

/*
* @description	: 处理传输过程
* @param-req 	: 请求
* @return 		: 无
*/
static void ramdisk_transfer(struct request *req)
{	
	unsigned long start = blk_rq_pos(req) << 9;  	/* blk_rq_pos获取到的是扇区地址,左移9位转换为字节地址 */
	unsigned long len  = blk_rq_cur_bytes(req);		/* 大小   */

	/* bio中的数据缓冲区
	 * 读:从磁盘读取到的数据存放到buffer中
	 * 写:buffer保存这要写入磁盘的数据
	 */
	void *buffer = bio_data(req->bio);		
	
	if(rq_data_dir(req) == READ) 		/* 读数据 */	
		memcpy(buffer, ramdisk.ramdiskbuf + start, len);
	else if(rq_data_dir(req) == WRITE) 	/* 写数据 */
		memcpy(ramdisk.ramdiskbuf + start, buffer, len);

}

/*
* @description	: 请求处理函数
* @param-q 	: 请求队列
* @return 		: 无
*/
void ramdisk_request_fn(struct request_queue *q)
{
	int err = 0;
	struct request *req;

	/* 循环处理请求队列中的每个请求 */
	req = blk_fetch_request(q);
	while(req != NULL) {

		/* 针对请求做具体的传输处理 */
		ramdisk_transfer(req);

		/* 判断是否为最后一个请求,如果不是的话就获取下一个请求
		 * 循环处理完请求队列中的所有请求。
		 */
		if (!__blk_end_request_cur(req, err))
			req = blk_fetch_request(q);
	}
}

static int __init xxx_init(void)
{
	int ret = 0;
	printk("ramdisk init\r\n");

	// 1、申请用于 ramdisk 内存
	ramdisk.ramdiskbuf = kzalloc(RAMDISK_SIZE, GFP_KERNEL);
	if(ramdisk.ramdiskbuf == NULL) {
		ret = -EINVAL;
		goto ram_fail;
	}

	// 2、初始化自旋锁
	spin_lock_init(&ramdisk.lock);

	// 3、向 Linux 内核注册块设备
	ramdisk.major = register_blkdev(0, "ramdisk"); // 由系统自动分配主设备号
	if(ramdisk.major < 0) {
		goto register_blkdev_fail;
	}  
	printk("ramdisk major = %d\r\n", ramdisk.major);

	// 4、分配并初始化磁盘 gendisk
	ramdisk.gendisk = alloc_disk(3);
	if(!ramdisk.gendisk) {
		ret = -EINVAL;
		goto gendisk_alloc_fail;
	}

	// 5、分配并初始化请求队列 request_queue
	ramdisk.queue = blk_init_queue(ramdisk_request_fn, &ramdisk.lock);
	if(!ramdisk.queue) {
		ret = EINVAL;
		goto blk_init_fail;
	}

	// 6、将申请并初始化好的 gendisk 添加到内核中
	ramdisk.gendisk->major 			= ramdisk.major;		// 主设备号
	ramdisk.gendisk->first_minor 	= 0;					// 第一个次设备号(起始次设备号)
	ramdisk.gendisk->fops 				= &ramdisk_fops; 	// 操作函数
	ramdisk.gendisk->private_data 	= &ramdisk;				// 私有数据
	ramdisk.gendisk->queue 			= ramdisk.queue;		// 请求队列
	sprintf(ramdisk.gendisk->disk_name, "ramdisk"); 		// 名字
	set_capacity(ramdisk.gendisk, RAMDISK_SIZE/512);		// 设备容量(单位为扇区)
	add_disk(ramdisk.gendisk);

	return 0;

blk_init_fail:
	put_disk(ramdisk.gendisk);
gendisk_alloc_fail:
	unregister_blkdev(ramdisk.major, "ramdisk");
register_blkdev_fail:
	kfree(ramdisk.ramdiskbuf);
ram_fail:
	return ret;
}

static void __exit xxx_exit(void)
{
	printk("ramdisk exit\r\n");
	del_gendisk(ramdisk.gendisk);					//1. 删除 gendisk
	put_disk(ramdisk.gendisk);						//2. 减少 gendisk 的引用计数
	blk_cleanup_queue(ramdisk.queue);				//3. 删除请求队列 request_queue
	unregister_blkdev(ramdisk.major, "ramdisk");	//4. 向 Linux 内核注销块设备
	kfree(ramdisk.ramdiskbuf); 						//5. 释放申请的内存(模拟块设备)
}

module_init(xxx_init);
module_exit(xxx_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("xiedong");

*[块设备驱动]: 块设备驱动其实就是这些存储设备驱动,比如 SD 卡、EMMC、NAND Flash、Nor Flash、SPI Flash、机械硬盘、固态硬盘等

相关推荐
静止了 所有的花开3 小时前
Ubuntu 26.04 静态 IP 配置不生效解决方法
linux·ubuntu·静态ip
caimouse4 小时前
ReactOS 图形系统分析(3):显示驱动 DDI framebuf.dll
linux·运维·网络
TonyLee0175 小时前
Claude Code + DeepSeek:服务器安装与使用笔记
linux·服务器·deepseek·claude code
buhuizhiyuci5 小时前
【Linux 篇】数字世界的水流蓄水池 —— IO 缓冲区机制深度实战解析
linux·运维·服务器·c++
骇客野人5 小时前
Linux 服务器 Python 版 MCP 服务部署整体方案(适配 Claude/Cursor/自研Agent)
linux·服务器·python
天疆说6 小时前
04 稳定性与性能调优实录:OOM 崩溃、并发槽位、前缀缓存与 DSpark 调参
linux·运维·缓存
星卯教育tony6 小时前
NOI Linux 2.0 服务器多用户网页桌面部署方案(腾讯云轻量Ubuntu20.04专属版 CSP复赛比赛环境搭建免安装虚拟机 )
linux·服务器·腾讯云
Forever Nore6 小时前
LeetCode 14 最长公共前缀 - 纵向扫描
linux·服务器·leetcode
拂拉氏7 小时前
【知识讲解】 Linux权限相关知识讲解
linux·权限