STM32MP157驱动开发——Linux块设备驱动


参考文章:【正点原子】I.MX6U嵌入式Linux驱动开发——Linux 块设备驱动

一、简介

  之前学习的都是关于字符设备的驱动,包括 platform 子系统、I2C总线等,本质上都是对字符设备驱动的一层封装。这节就学习第二种驱动模式——块设备驱动。块设备驱动要远比字符设备驱动复杂得多,不同类型的存储设备又对应不同的驱动子系统。这一节使用开发板板载 RAM 模拟一个块设备,学习块设备驱动框架的使用。
  块设备是针对存储设备的,比如 SD 卡、EMMC、NAND Flash、Nor Flash、SPI Flash、机械硬盘、固态硬盘等。块设备与字符设备的区别如下:

1.块设备只能以块为单位进行读写访问,块是 linux 虚拟文件系统(VFS)基本的数据传输单位。而字符设备是以字节为单位进行数据传输的,不需要缓冲。
2.块设备在结构上是可以进行随机访问的,对于这些设备的读写都是按块进行的,块设备使用缓冲区来暂时存放数据,等到条件成熟以后再一次性将缓冲区中的数据写入块设备中。而字符设备是顺序的数据流设备,字符设备是按照字节进行读写访问的。字符设备不需要缓冲区,对于字符设备的访问都是实时的,而且也不需要按照固定的块大小进行访问。

块设备驱动框架的常用属性及使用流程就参考原子哥教程中的讲解,这里就不多赘述。

二、驱动开发

本节尝试使用开发板上的 RAM 模拟一段块设备,也就是 ramdisk,然后编写块设备驱动。

1.使用请求队列的方式

#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/init.h>
#include <linux/sched.h>
#include <linux/kernel.h>	
#include <linux/slab.h>		
#include <linux/fs.h>		
#include <linux/errno.h>	
#include <linux/types.h>	
#include <linux/fcntl.h>	
#include <linux/hdreg.h>
#include <linux/kdev_t.h>
#include <linux/vmalloc.h>
#include <linux/genhd.h>
#include <linux/blk-mq.h>
#include <linux/buffer_head.h>	
#include <linux/bio.h>

#define RAMDISK_SIZE	(2 * 1024 * 1024) 	/* 容量大小为2MB */
#define RAMDISK_NAME	"ramdisk"			/* 名字 */
#define RADMISK_MINOR	3					/* 表示有三个磁盘分区!不是次设备号为3!*/

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

struct ramdisk_dev *ramdisk = NULL;		/* ramdisk设备指针 */

/*
 * @description	: 处理传输过程
 * @param-req 	: 请求
 * @return 		: 0,成功;其它表示失败
 */
static int 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);
		
	return 0;

}

/*
 * @description	: 开始处理传输数据的队列
 * @hctx 		: 硬件相关的队列结构体
 * @bd			: 数据相关的结构体
 * @return 		: 0,成功;其它值为失败
 */
static blk_status_t _queue_rq(struct blk_mq_hw_ctx *hctx, const struct blk_mq_queue_data* bd)
{
    
    
	struct request *req = bd->rq; /* 通过bd获取到request队列*/
	struct ramdisk_dev *dev = req->rq_disk->private_data;
	int ret;
	
	blk_mq_start_request(req);    /* 开启处理队列 */
	spin_lock(&dev->lock);		  
	ret = ramdisk_transfer(req);  /* 处理数据 */
	blk_mq_end_request(req, ret); /* 结束处理队列 */
	spin_unlock(&dev->lock);
	
	return BLK_STS_OK;
	
}
/*
 * 队列操作函数
 */
static struct blk_mq_ops mq_ops = {
    
    
    .queue_rq = _queue_rq,
};

/*
 * @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	: 初始化队列相关操作
 * @set		 	: blk_mq_tag_set对象
 * @return 		: request_queue的地址
 */
static struct request_queue * create_req_queue(struct blk_mq_tag_set *set)
{
    
    
	struct request_queue *q;

#if 0

/*
 *这里是使用了blk_mq_init_sq_queue 函数
 *进行初始化的。
 */
	q = blk_mq_init_sq_queue(set, &mq_ops, 2, BLK_MQ_F_SHOULD_MERGE);
	
#else
	int ret;
	
	memset(set, 0, sizeof(*set));
	set->ops = &mq_ops;		//操作函数
    set->nr_hw_queues = 2;	//硬件队列
    set->queue_depth = 2;	//队列深度
	set->numa_node = NUMA_NO_NODE;//numa节点
	set->flags =  BLK_MQ_F_SHOULD_MERGE; //标记在bio下发时需要合并

	ret = blk_mq_alloc_tag_set(set); //使用函数进行再次初始化
	if (ret) {
    
    
		printk(KERN_WARNING "sblkdev: unable to allocate tag set\n");
		return ERR_PTR(ret);
	}
	
	q = blk_mq_init_queue(set); //分配请求队列
	if(IS_ERR(q)) {
    
    
		blk_mq_free_tag_set(set);
		return q;
	}
#endif

	return q;
}

/*
 * @description	: 创建块设备,为应用层提供接口。
 * @set		 	: ramdisk_dev对象
 * @return 		: 0,表示成功;其它值为失败
 */
static int create_req_gendisk(struct ramdisk_dev *set)
{
    
    
	struct ramdisk_dev *dev = set;

	/* 1、分配并初始化 gendisk */
    dev->gendisk = alloc_disk(RADMISK_MINOR);
	if(dev == NULL)
		return -ENOMEM;
	
	/* 2、添加(注册)disk */
	dev->gendisk->major = ramdisk->major; /* 主设备号 */
	dev->gendisk->first_minor = 0;		  /* 起始次设备号 */
	dev->gendisk->fops = &ramdisk_fops;	  /* 操作函数 */
	dev->gendisk->private_data = set;	  /* 私有数据 */
	dev->gendisk->queue = dev->queue;	  /* 请求队列 */
	sprintf(dev->gendisk->disk_name, RAMDISK_NAME); /* 名字 */
	set_capacity(dev->gendisk, RAMDISK_SIZE/512);	/* 设备容量(单位为扇区)*/
	add_disk(dev->gendisk);
	return 0;
}

/*
 * @description	: 驱动出口函数
 * @param 		: 无
 * @return 		: 无
 */
static int __init ramdisk_init(void)
{
    
    
	int ret = 0;
	struct ramdisk_dev * dev;
	printk("ramdisk init\r\n");
	
	/* 1、申请内存 */
	dev = kzalloc(sizeof(*dev), GFP_KERNEL);
	if(dev == NULL) {
    
    
		return -ENOMEM;
	}
	
	dev->ramdiskbuf = kmalloc(RAMDISK_SIZE, GFP_KERNEL);
	if(dev->ramdiskbuf == NULL) {
    
    
		printk(KERN_WARNING "dev->ramdiskbuf: vmalloc failure.\n");
		return -ENOMEM;
	}
	ramdisk = dev;
	
	/* 2、初始化自旋锁 */
	spin_lock_init(&dev->lock);

	/* 3、注册块设备 */
	dev->major = register_blkdev(0, RAMDISK_NAME); /* 由系统自动分配主设备号 */
	if(dev->major < 0) {
    
    
		goto register_blkdev_fail;
	}
	
	/* 4、创建多队列 */
	dev->queue = create_req_queue(&dev->tag_set);
	if(dev->queue == NULL) {
    
    
		goto create_queue_fail;
	}
	
	/* 5、创建块设备 */
	ret = create_req_gendisk(dev);
	if(ret < 0)
		goto create_gendisk_fail;
	
    return 0;

create_gendisk_fail:
	blk_cleanup_queue(dev->queue);
	blk_mq_free_tag_set(&dev->tag_set);
create_queue_fail:
	unregister_blkdev(dev->major, RAMDISK_NAME);
register_blkdev_fail:
	kfree(dev->ramdiskbuf);
	kfree(dev);
	return -ENOMEM;
}

/*
 * @description	: 驱动出口函数
 * @param 		: 无
 * @return 		: 无
 */
static void __exit ramdisk_exit(void)
{
    
    
	
	printk("ramdisk exit\r\n");
	/* 释放gendisk */
	del_gendisk(ramdisk->gendisk);
	put_disk(ramdisk->gendisk);

	/* 清除请求队列 */
	blk_cleanup_queue(ramdisk->queue);
	
	/* 释放blk_mq_tag_set */
	blk_mq_free_tag_set(&ramdisk->tag_set);
	
	/* 注销块设备 */
	unregister_blkdev(ramdisk->major, RAMDISK_NAME);

	/* 释放内存 */
	kfree(ramdisk->ramdiskbuf);
	kfree(ramdisk);
}

module_init(ramdisk_init);
module_exit(ramdisk_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("ALIENTEK");
MODULE_INFO(intree, "Y");

①首先是宏定义部分,RAMDISK_SIZE 就是模拟块设备的大小,这里设置为 2MB。RAMDISK_NAME 为本实验名字,RADMISK_MINOR 是本实验此设备号数量。注意:非次设备号。设备号数量决定了本块设备的磁盘分区数量。
②通过 ramdisk 的设备结构体定义一个全局变量 ramdisk_dev 类型的指针。
③先看一下驱动模块的加载和卸载,即ramdisk_init()和ramdisk_exit()。

  • 在ramdisk_init()中先为自定义的ramdisk结构体申请空间,使用kmalloc申请2MB大小空间。然后初始化一个自旋锁,用于在队列操作的时候做保护。接着使用 register_blkdev 函数向内核注册一个块设备,返回值就是注册成功的块设备主设备号。这里让内核自动分配一个主设备号,因此 register_blkdev 函数的第一个参数为 0。create_req_queue 函数用于创建一个多队列,这一部分主要用于操作块设备。create_req_gendisk 函数用于创建一个块设备和提供一些接口给应用层调用。
  • ramdisk_exit 函数在卸载块设备驱动模块的时候使用,需要将前面注册的对象进行卸载、实例化的对象要进行释放。

④create_req_queue 函数用于创建一个多队列,首先设置多队列的重要参数,比如一些操作函数、队列深度、硬件队列个数和标志位等。然后设置 blk_mq_tag_set 的 ops 成员变量,这就是块设备的队列操作集,由开发人员实现。再使用 blk_mq_alloc_tag_set 函数进行再次初始化 blk_mq_tag_set 对象,最后根据此对象分配请求队列。
也可以使用 blk_mq_init_sq_queue 函数一步到位,第一个参数为 blk_mq_tag_set 对象、第二个参数为操作函数集合、第三个参数为硬件队列个数,第四个参数为标志位。
⑤使用 create_req_gendisk 函数进行初始化块设备。先使用 alloc_disk 分配一个 gendisk,然后初始化申请到的 gendisk 对象,重点是设置 geddisk 的 fops 成员变量。再使用 set_capacity 函数设置本块设备容量大小。注意:这里的大小是扇区数,不是字节数,一个扇区是 512 字节。gendisk 初始化完成以后就可以使用 add_disk 函数将 gendisk 添加到内核中,也就是向内核添加一个磁盘设备。
⑥gendisk 的 fops 操作集。就是块设备的操作集 block_device_operations,本节仅实现了 open、release 和 getgeo,其中 open 和 release 函数都是空函数,重点是 getgeo 函数。此函数用来获取磁盘信息,保存在参数 geo 中。
⑦blk_mq_tag_set 的 ops 操作集,也就是请求处理函数集合。使用 blk_mq_start_request 函数开启多队列处理,blk_mq_end_request 函数去结束多队列处理。ramdisk_transfer 数据处理函数,使用 ramdisk_transfer 数据处理函数,使用 bio_data 函数获取请求中的 bio 保存的数据。rq_data_dio 函数判断当前是读还是写,如果是写的话就将 bio 中的数据拷贝到 ramdisk 指定地址(扇区),如果是读的话就从 ramdisk 中的指定打字(扇区)读取数据放到 bio 中。

总结:主要是两个重要的结构体:blk_mq_tag_set 和 gendisk。可以把 blk_mq_tag_set 看作真正的 IO 读写操作(ops 操作集就是 IO 操作),有了底层操作之后,还需要 gendisk 结构体为上层提供接口调用(fops 就是实现上层调用的操作)。

2.测试①

驱动编写完成后,就可以编译出.ko文件进行挂载测试。
另外,还需要在 buildroot 中的 busybox 使能 mkfs.vfat 命令。在 buildroot 源码目录下,使用sudo make busybox-menuconfig命令打开 busybox 配置界面,选中以下选项:
在这里插入图片描述
然后使用以下命令,编译出新的根文件系统:

sudo make busybox	#编译新的busybox
sudo make 			#打包出新的buildroot

然后就可以使用新的根文件系统进行启动。
在这里插入图片描述
驱动挂载成功后,可以使用fdisk -l命令查看磁盘信息。其中就包括 2MB 的ramdisk设备。
然后使用以下命令进行格式化,然后挂载,就可以操作这块磁盘了。

mkfs.vfat /dev/ramdisk	#格式化磁盘为 vfat 格式
mkdir /mnt/ram_disk -P  #创建 ramdisk 挂载目录
mount /dev/ramdisk /mnt/ram_disk #挂载 ramdisk

3.不使用请求队列的方式

请求队列会用到 I/O 调度器,适合机械硬盘这种存储设备。对于 EMMC、SD、ramdisk 这样没有机械结构的存储设备,可以直接访问任意一个扇区,因此可以不需要 I/O 调度器,也就不需要请求队列了。
参考 linux 内核的 drivers/block/zram/zram_drv.c,把 blk_mq_tag_set 相关的都删除掉,然后修改 create_req_queue 函数即可,在此函数里使用 create_req_queue 函数设置“制造请求”函数。

#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/init.h>
#include <linux/sched.h>
#include <linux/kernel.h>	
#include <linux/slab.h>		
#include <linux/fs.h>		
#include <linux/errno.h>	
#include <linux/types.h>	
#include <linux/fcntl.h>	
#include <linux/hdreg.h>
#include <linux/kdev_t.h>
#include <linux/vmalloc.h>
#include <linux/genhd.h>
#include <linux/blk-mq.h>
#include <linux/buffer_head.h>	
#include <linux/bio.h>

#define RAMDISK_SIZE	(2 * 1024 * 1024) 	/* 容量大小为2MB */
#define RAMDISK_NAME	"ramdisk"			/* 名字 */
#define RADMISK_MINOR	3					/* 表示有三个磁盘分区!不是次设备号为3!*/

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

struct ramdisk_dev *ramdisk = NULL;		/* 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-q 	: 请求队列
 * @return 		: 无
 */
static blk_qc_t ramdisk_make_request_fn(struct request_queue *q, struct bio *bio)
{
    
    
	int offset;
	struct bio_vec bvec;
	struct bvec_iter iter;
	unsigned long len = 0;
	struct ramdisk_dev *dev = q->queuedata;

	offset = (bio->bi_iter.bi_sector) << 9;	/* 获取要操作的设备的偏移地址 */
	spin_lock(&dev->lock);	
	/* 处理bio中的每个段 */
	bio_for_each_segment(bvec, bio, iter){
    
    
		char *ptr = page_address(bvec.bv_page) + bvec.bv_offset;
		len = bvec.bv_len;

		if(bio_data_dir(bio) == READ)	/* 读数据 */
			memcpy(ptr, dev->ramdiskbuf + offset, len);
		else if(bio_data_dir(bio) == WRITE)	/* 写数据 */
			memcpy(dev->ramdiskbuf + offset, ptr, len);
		offset += len;
	}
	spin_unlock(&dev->lock);
    bio_endio(bio);
    return BLK_QC_T_NONE;
}

/*
 * @description	: 初始化队列相关操作
 * @set		 	: blk_mq_tag_set对象
 * @return 		: request_queue的地址
 */
static struct request_queue * create_req_queue(struct ramdisk_dev *set)
{
    
    
	struct request_queue *q;

	q = blk_alloc_queue(GFP_KERNEL);
	
	blk_queue_make_request(q, ramdisk_make_request_fn);
	
    q->queuedata = set;
	return q;
}

/*
 * @description	: 创建块设备,为应用层提供接口。
 * @set		 	: ramdisk_dev对象
 * @return 		: 0,表示成功;其它值为失败
 */
static int create_req_gendisk(struct ramdisk_dev *set)
{
    
    
	struct ramdisk_dev *dev = set;

	/* 1、分配并初始化 gendisk */
    dev->gendisk = alloc_disk(RADMISK_MINOR);
	if(dev == NULL)
		return -ENOMEM;
	
	/* 2、添加(注册)disk */
	dev->gendisk->major = ramdisk->major; /* 主设备号 */
	dev->gendisk->first_minor = 0;		  /* 起始次设备号 */
	dev->gendisk->fops = &ramdisk_fops;	  /* 操作函数 */
	dev->gendisk->private_data = set;	  /* 私有数据 */
	dev->gendisk->queue = dev->queue;	  /* 请求队列 */
	sprintf(dev->gendisk->disk_name, RAMDISK_NAME); /* 名字 */
	set_capacity(dev->gendisk, RAMDISK_SIZE/512);	/* 设备容量(单位为扇区)*/
	add_disk(dev->gendisk);
	return 0;
}

/*
 * @description	: 驱动出口函数
 * @param 		: 无
 * @return 		: 无
 */
static int __init ramdisk_init(void)
{
    
    
	int ret = 0;
	struct ramdisk_dev * dev;
	printk("ramdisk init\r\n");
	
	/* 1、申请内存 */
	dev = kzalloc(sizeof(*dev), GFP_KERNEL);
	if(dev == NULL) {
    
    
		return -ENOMEM;
	}
	
	dev->ramdiskbuf = kmalloc(RAMDISK_SIZE, GFP_KERNEL);
	if(dev->ramdiskbuf == NULL) {
    
    
		printk(KERN_WARNING "dev->ramdiskbuf: vmalloc failure.\n");
		return -ENOMEM;
	}
	ramdisk = dev;
	
	/* 2、初始化自旋锁 */
	spin_lock_init(&dev->lock);

	/* 3、注册块设备 */
	dev->major = register_blkdev(0, RAMDISK_NAME); /* 由系统自动分配主设备号 */
	if(dev->major < 0) {
    
    
		goto register_blkdev_fail;
	}
	
	/* 4、创建多队列 */
	dev->queue = create_req_queue(dev);
	if(dev->queue == NULL) {
    
    
		goto create_queue_fail;
	}
	
	/* 5、创建块设备 */
	ret = create_req_gendisk(dev);
	if(ret < 0)
		goto create_gendisk_fail;
	
    return 0;

create_gendisk_fail:
	blk_cleanup_queue(dev->queue);
create_queue_fail:
	unregister_blkdev(dev->major, RAMDISK_NAME);
register_blkdev_fail:
	kfree(dev->ramdiskbuf);
	kfree(dev);
	return -ENOMEM;
}

/*
 * @description	: 驱动出口函数
 * @param 		: 无
 * @return 		: 无
 */
static void __exit ramdisk_exit(void)
{
    
    
	
	printk("ramdisk exit\r\n");
	/* 释放gendisk */
	del_gendisk(ramdisk->gendisk);
	put_disk(ramdisk->gendisk);

	/* 清除请求队列 */
	blk_cleanup_queue(ramdisk->queue);
	
	/* 注销块设备 */
	unregister_blkdev(ramdisk->major, RAMDISK_NAME);

	/* 释放内存 */
	kfree(ramdisk->ramdiskbuf);
	kfree(ramdisk);
}

module_init(ramdisk_init);
module_exit(ramdisk_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("ALIENTEK");
MODULE_INFO(intree, "Y");

重点为 create_req_queue 函数,使用 blk_alloc_queue 和 blk_queue_make_request 这两个函数取代了上一种方式中 blk_mq_tag_set 结构体相关的操作。blk_alloc_queue 函数用来申请一个请求队列,blk_queue_make_request 函数设置“制造请求”函数,这里设置的制造请求函数为 ramdisk_make_request_fn,由开发人员实现。
ramdisk_make_request_fn 函数第一个参数依旧是请求队列,但是实际上这个请求队列不包含真正的请求,所有的处理内容都在第二个 bio 参数里面,所以 ramdisk_make_request_fn 函数里面是全部是对 bio 的操作。

4.测试②

非请求队列方式的驱动测试与上面的测试①没有什么区别,这里就不再赘述。

猜你喜欢

转载自blog.csdn.net/weixin_45682654/article/details/128521812
今日推荐