块设备驱动

块设备驱动是 Linux 三大驱动类型之一,块设备驱动比字符设备驱动复杂得多,不同类型的存储设备又对应不同的驱动子系统,下面介绍块设备驱动框架及使用

一、块设备介绍

块设备是针对存储设备的,比如 SD 卡、EMMC、NAND Flash、Nor Flash、SPI Flash、机械硬盘、固态硬盘等。因此块设备驱动其实就是这些存储设备驱动,块设备驱动相比字符设备
驱动的主要区别如下:

  • 块设备只能以块为单位进行读写访问,块是 linux 虚拟文件系统(VFS)基本的数据传输单位;字符设备是以字节为单位进行数据传输的,不需要缓冲
  • 块设备在结构上是可以进行随机访问的,对于这些设备的读写都是按块进行的,块设备使用缓冲区来暂时存放数据,等到条件成熟以后在一次性将缓冲区中的数据写入块设备中
二、块设备驱动框架
2.1 block_device 结构体

内核使用 block_device 表示块设备,block_device 为一个结构体,定义在 include/linux/fs.h 文件中

struct block_device {
	dev_t bd_dev; 	/* not a kdev_t - it's a search key */
	int bd_openers;
	struct inode *bd_inode; 	/* will die */
	struct super_block *bd_super;
	struct mutex bd_mutex; 	/* open/close mutex */
	struct list_head bd_inodes;
	void * bd_claiming;
	void * bd_holder;
	int bd_holders;
	bool bd_write_holder;
#ifdef CONFIG_SYSFS
	struct list_head bd_holder_disks;
#endif
	struct block_device *bd_contains;
	unsigned bd_block_size;
	struct hd_struct *bd_part;
	/*number of times partitions within this device have been opened.*/
	unsigned bd_part_count;
	int bd_invalidated;
	/*  bd_disk成员变量为gendisk结构体指针类型,内核使用block_device来表示
	具体块设备对象(硬盘/分区),若是硬盘的话bd_disk就指向通用磁盘结构gendisk */
	struct gendisk *bd_disk;  
	struct request_queue *bd_queue;
	struct list_head bd_list;
	unsigned long bd_private;
	/* The counter of freeze processes */
	int bd_fsfreeze_count;
	/* Mutex for freeze */
	struct mutex bd_fsfreeze_mutex;
};
  • 注册块设备:向内核注册新的块设备、申请设备号
int register_blkdev(unsigned int major, const char *name)
//major:主设备号
//name:块设备名字
//返回值:若major在1~255之间,表示自定义主设备号,则返回0表示注册成功,返回负值表示注册失败
//若major为0,表示由系统自动分配主设备号,成功则返回分配的主设备号,返回负值表示注册失败
  • 注销块设备:从内核注销块设备
void unregister_blkdev(unsigned int major, const char *name)
//major:要注销的块设备主设备号
//name:要注销的块设备名字
2.2 gendisk 结构体

linux 内核使用 gendisk 结构体来描述一个磁盘设备,定义在 include/linux/genhd.h 中

struct gendisk { 
	int major; 			/* 磁盘设备的主设备号 */ 
	int first_minor;	/* 磁盘的第一个次设备号 */
	int minors;  		/* 磁盘的次设备号数量,即磁盘的分区数量 */ 
	char disk_name[DISK_NAME_LEN]; 	/* name of major driver */ 
	char *(*devnode)(struct gendisk *gd, umode_t *mode); 
	unsigned int events; 	/* supported events */ 
	unsigned int async_events; 	/* async events, subset of all */ 
	/* 磁盘对应的分区表 */
	struct disk_part_tbl __rcu *part_tbl; 
	struct hd_struct part0; 
	const struct block_device_operations *fops; /* 块设备操作集 */
	struct request_queue *queue; /* 磁盘对应的请求队列 */
	void *private_data; 
	int flags; 
	struct device *driverfs_dev; // FIXME: remove 
	struct kobject *slave_dir; 
	struct timer_rand_state *random; 
	atomic_t sync_io; /* RAID */ 
	struct disk_events *ev; 
#ifdef CONFIG_BLK_DEV_INTEGRITY 
	struct blk_integrity *integrity; 
#endif 
	int node_id;
};

编写块设备驱动时,需要分配并初始化一个 gendisk,内核提供了一组 gendisk 操作函数

  • 申请 gendisk:使用 gendisk 之前要先申请
struct gendisk *alloc_disk(int minors)
//minors:次设备号数量,即 gendisk 对应的分区数量
//返回值:成功,返回申请到的 gendisk,失败,返回 NULL
  • 删除 gendisk:
void del_gendisk(struct gendisk *gp)
//gp:要删除的 gendisk
  • 将 gendisk 添加到内核:申请到 gendisk 后,还要将 gendisk 添加到内核中
void add_disk(struct gendisk *disk)
//disk:要添加到内核的 gendisk
  • 设置 gendisk 容量:初始化 gendisk 时,需要设置其容量
void set_capacity(struct gendisk *disk, sector_t size)
//disk:要设置容量的 gendisk
//size:磁盘容量大小,注意这里是扇区数量
//块设备中最小的可寻址单元是扇区,扇区一般是512字节
//因此该函数设置的大小就是实际容量除以521得到的扇区数量
  • 调整 gendisk 引用计数:内核通过以下两个函数来调整 gendisk 的引用计数
//增加 gendisk 的引用计数
struct kobject *get_disk(struct gendisk *disk)
//减少 gendisk 的引用计数
void put_disk(struct gendisk *disk)
2.3 block_device_operations 结构体

块设备操作集,为结构体 block_device_operations,定义在 include/linux/blkdev.h 中

struct block_device_operations {
	int (*open) (struct block_device *, fmode_t);
	void (*release) (struct gendisk *, fmode_t);
	//rw_page 函数用于读写指定的页
	int (*rw_page)(struct block_device *, sector_t, struct page *, int rw);
	//ioctl 函数用于块设备的 I/O 控制
	int (*ioctl) (struct block_device *, fmode_t, unsigned, unsigned long);
	int (*compat_ioctl) (struct block_device *, fmode_t, unsigned, unsigned long);
	long (*direct_access)(struct block_device *, sector_t,
	void **, unsigned long *pfn, long size);
	unsigned int (*check_events) (struct gendisk *disk,
	unsigned int clearing);
	/* ->media_changed() is DEPRECATED, use ->check_events() instead */
	int (*media_changed) (struct gendisk *);
	void (*unlock_native_capacity) (struct gendisk *);
	int (*revalidate_disk) (struct gendisk *);
	//getgeo 函数用于获取磁盘信息,包括磁头、柱面和扇区等信息
	int (*getgeo)(struct block_device *, struct hd_geometry *);
	/* this callback is with swap_lock and sometimes page table lock held */
	void (*swap_slot_free_notify) (struct block_device *, unsigned long);
	struct module *owner;
};
2.4 块设备I/O请求过程

block_device_operations 结构体中并没有找到 read 和 write 这样的读写函数,那么块设备是怎么从物理块设备中读写数据?这里就引出了块设备驱动中非常重要的 request_queue、 request 和 bio。三者之间的关系如下图示:
在这里插入图片描述

内核将对块设备的读写都发送到请求队列 request_queue 中, request_queue 中是大量的request(请求结构体),而 request 又包含了 bio, bio 保存了读写相关数据,比如从块设备的哪个地址开始读取、读取的数据长度,读取到哪里,如果是写的话还包括要写入的数据等

  • 请求队列 request_queue:每个磁盘(gendisk)都要分配一个 request_queue

初始化请求队列:申请并初始化一个 request_queue,然后在初始化 gendisk 的时候将这个request_queue 地址赋值给 gendisk 的 queue 成员变量

request_queue *blk_init_queue(request_fn_proc *rfn, spinlock_t *lock)
//返回值:如果为 NULL 的话表示失败,成功的话就返回申请到的 request_queue 地址
//lock:自旋锁指针,需要驱动编写人员定义一个自旋锁传递进来,请求队列会使用该锁
//rfn:请求处理函数指针,每个request_queue都要有一个请求处理函数,其原型如下
void (request_fn_proc) (struct request_queue *q)

删除请求队列:卸载块设备驱动时,需要删除掉前面申请到的 request_queue

void blk_cleanup_queue(struct request_queue *q)
//q:需要删除的请求队列

分配请求队列并绑定制造请求函数:针对于非机械设备(EMMC、SD卡等);对于机械设备,在申请请求队列时已经绑定了,并分配一个 I/O 调度器来优化数据读写过程。非机械设备可进行完全随机访问,无需复杂的 I/O 调度器

//下面两个函数搭配使用:
/* request_queue 申请函数 */
struct request_queue *blk_alloc_queue(gfp_t gfp_mask)
//gfp_mask:内存分配掩码,一般为GFP_KERNEL
//返回值:申请到的无I/O调度的 request_queue
/* “制造请求”函数 */
void blk_queue_make_request(struct request_queue *q, make_request_fn *mfn)
//q:需要绑定的请求队列,也就是 blk_alloc_queue 申请到的请求队列
//mfn:需要绑定的“制造”请求函数,函数原型如下:
void (make_request_fn) (struct request_queue *q, struct bio *bio)
// “制造请求”函数需要驱动编写人员实现
  • 请求 request:请求队列里面包含的就是一系列的请求(request)

获取请求:从request_queue中依次获取每个request

request *blk_peek_request(struct request_queue *q)
//q:指定 request_queue
//返回值:request_queue中下一个要处理的request,若没有要处理的请求就返回 NULL

开启请求:获取到下一个要处理的请求以后就要开始处理这个请求

void blk_start_request(struct request *req)
//req:要开始处理的请求

一步到位处理请求:也可以使用如下函数来一次性完成请求的获取和开启

struct request *blk_fetch_request(struct request_queue *q) {
	struct request *rq;
	rq = blk_peek_request(q);
		if (rq)
			blk_start_request(rq);
	return rq;
}

其他和请求有关的函数

blk_end_request() 		//请求中指定字节数据被处理完成
blk_end_request_all() 	//请求中所有数据全部处理完成
blk_end_request_cur() 	//当前请求中的 chunk
blk_end_request_err() 	//处理完请求,直到下一个错误产生
__blk_end_request() 	//和 blk_end_request 函数一样,但是需要持有队列锁
__blk_end_request_all() //和 blk_end_request_all 函数一样,但是需要持有队列锁
__blk_end_request_cur() //和 blk_end_request_cur 函数一样,但是需要持有队列锁
__blk_end_request_err() //和 blk_end_request_err 函数一样,但是需要持有队列锁
  • bio 结构:每个 request 里面会有多个 bio,保存着最终要读写的数据、地址等信息。

bio 是个结构体,定义在 include/linux/blk_types.h 中

struct bio {
	struct bio *bi_next; 			/* 请求队列的下一个 bio */
	struct block_device *bi_bdev; 	/* 指向块设备 */
	unsigned long bi_flags; 		/* bio 状态等信息 */
	unsigned long bi_rw; 			/* I/O 操作,读或写 */
	struct bvec_iter bi_iter; 		/* I/O 操作,读或写 */
	unsigned int bi_phys_segments;
	unsigned int bi_seg_front_size;
	unsigned int bi_seg_back_size;
	atomic_t bi_remaining;
	bio_end_io_t *bi_end_io;
	void *bi_private;
#ifdef CONFIG_BLK_CGROUP
	struct io_context *bi_ioc;
	struct cgroup_subsys_state *bi_css;
#endif
	union {
#if defined(CONFIG_BLK_DEV_INTEGRITY)
	struct bio_integrity_payload *bi_integrity;
#endif
	};
	unsigned short bi_vcnt; 		/* bio_vec 列表中元素数量 */
	unsigned short bi_max_vecs; 	/* bio_vec 列表长度 */
	atomic_t bi_cnt; 				/* pin count */
	struct bio_vec *bi_io_vec;  	/* bio_vec 列表 */
	struct bio_set *bi_pool;
	struct bio_vec bi_inline_vecs[0];
};
/*******************************************************************/
/***** bvec_iter 结构体描述了要操作的设备扇区等信息,结构体内容如下 *****/
struct bvec_iter {
	sector_t bi_sector; 		/* I/O 请求的设备起始扇区(512 字节) */
	unsigned int bi_size; 		/* 剩余的 I/O 数量 */
	unsigned int bi_idx; 		/* blv_vec 中当前索引 */
	unsigned int bi_bvec_done; 	/* 当前 bvec 中已经处理完成的字节数 */
};
/***********************************************************/
/********* bio_vec 结构体描述了内容如下 **********************/
struct bio_vec {
	struct page *bv_page; 		/* 页 */
	unsigned int bv_len; 		/* 长度 */
	unsigned int bv_offset; 	/* 偏移 */
};
/***********************************************************/
/** bio、bvec_iter 以及 bio_vec 这三个结构体之间的关系如下图 **/
/***********************************************************/

在这里插入图片描述

遍历请求中的 bio:请求中包含有大量的 bio,因此就涉及到遍历请求中所有 bio 并进行处理

#define __rq_for_each_bio(_bio, rq)\
	if ((rq->bio))\ 
		for (_bio = (rq)->bio; _bio; _bio = _bio->bi_next)
//_bio 是遍历出来的每个 bio,为 bio 结构体指针类
//rq 是要进行遍历操作的请求,为 request 结构体指针类型

遍历 bio 中的所有段:bio 包含了最终要操作的数据,因此还需要遍历 bio 中的所有段

#define bio_for_each_segment(bvl, bio, iter) \
__bio_for_each_segment(bvl, bio, iter, (bio)->bi_iter)
//bvl 是遍历出来的每个 bio_vec
//bio 是要遍历的 bio,类型为bio 结构体指针
//iter 参数保存要遍历的 bio 中 bi_iter 成员变量

通知 bio 处理结束:如果使用“制造请求”,也就是抛开 I/O 调度器直接处理 bio 的话,在 bio 处理完成以后要通知内核 bio 处理完成

bvoid bio_endio(struct bio *bio, int error)
//bio:要结束的 bio
//error:若bio处理成功就直接填0,若失败就填个负值,比如-EIO
三、使用请求队列实验

使用开发板上的 RAM 模拟一段块设备ramdisk,然后编写块设备驱动

3.1 驱动程序编写

传统的使用请求队列,针对机械硬盘的驱动编写。新建驱动文件 ramdisk.c,并编写如下代码

#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内存空间,用于模拟块设备 */
	spinlock_t lock;			/* 自旋锁 */
	struct gendisk *gendisk; 	/* gendisk */
	struct request_queue *queue;/* 请求队列 */
};
struct ramdisk_dev ramdisk;		/* ramdisk设备 */
/* 打开块设备 */
int ramdisk_open(struct block_device *dev, fmode_t mode){
	printk("ramdisk open\r\n");
	return 0;
}
/* 释放块设备 */
void ramdisk_release(struct gendisk *disk, fmode_t mode){
	printk("ramdisk release\r\n");
}
/* 获取磁盘信息 */
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,
};
/* 处理传输过程 */
static void ramdisk_transfer(struct request *req){	
	/* blk_rq_pos获取到的是扇区地址,左移9位转换为字节地址 */
	unsigned long start = blk_rq_pos(req) << 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);
}
/* 请求处理函数 */
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 ramdisk_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、注册块设备 */
	ramdisk.major = register_blkdev(0, RAMDISK_NAME); /* 由系统自动分配主设备号 */
	if(ramdisk.major < 0) {
		goto register_blkdev_fail;
	}  
	printk("ramdisk major = %d\r\n", ramdisk.major);
	/* 4、分配并初始化gendisk */
	ramdisk.gendisk = alloc_disk(RADMISK_MINOR);
	if(!ramdisk.gendisk) {
		ret = -EINVAL;
		goto gendisk_alloc_fail;
	}
	/* 5、分配并初始化请求队列 */
	ramdisk.queue = blk_init_queue(ramdisk_request_fn, &ramdisk.lock);
	if(!ramdisk.queue) {
		ret = EINVAL;
		goto blk_init_fail;
	}
	/* 6、添加(注册)disk */
	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_NAME); /* 名字 */
	set_capacity(ramdisk.gendisk, RAMDISK_SIZE/512);	/* 设备容量(单位为扇区) */
	add_disk(ramdisk.gendisk);

	return 0;

blk_init_fail:
	put_disk(ramdisk.gendisk);
	//del_gendisk(ramdisk.gendisk);
gendisk_alloc_fail:
	unregister_blkdev(ramdisk.major, RAMDISK_NAME);
register_blkdev_fail:
	kfree(ramdisk.ramdiskbuf); /* 释放内存 */
ram_fail:
	return ret;
}
/* 驱动出口函数 */
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); 
}

module_init(ramdisk_init);
module_exit(ramdisk_exit);
MODULE_LICENSE("GPL");
3.2 运行测试

使用make -j32命令编译驱动文件,得到 ramdisk.ko 驱动模块,拷贝到 modules/4.1.15 目录中,重启开发板,加载驱动模块

depmod 				//第一次加载驱动的时候需要运行此命令
modprobe ramdisk.ko //加载驱动模块
  • 查看 ramdisk 磁盘:加载成功后会在/dev/目录下生成一个名为“ramdisk”的设备
fdisk -l 		//查看磁盘信息

在这里插入图片描述

  • 格式化/dev/ramdisk:上图中提示没有分区表,是因为还没有格式化 ramdisk,使用mkfs.vfat命令
mkfs.vfat /dev/ramdisk
  • 挂载访问:格式化完成后就可以挂载访问了,挂载点可自定义,使用mount命令
mount /dev/ramdisk /tmp		//挂载到'/tmp'目录下
四、不使用请求队列实验

请求队列会用到 I/O 调度器,适合机械硬盘这种存储设备。对于 EMMC、SD、ramdisk 这样没有机械结构的存储设备,可以直接访问任意一个扇区,因此可以不需要 I/O 调度器,也就不需要请求队列了。下面介绍如何使用“制造请求”方法编写驱动。

新建驱动文件 ramdisk.c,并编写如下代码

#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内存空间,用于模拟块设备 */
	spinlock_t lock;			/* 自旋锁 */
	struct gendisk *gendisk; 	/* gendisk */
	struct request_queue *queue;/* 请求队列 */

};
struct ramdisk_dev ramdisk;		/* ramdisk设备 */
/* 打开块设备 */
int ramdisk_open(struct block_device *dev, fmode_t mode){
	printk("ramdisk open\r\n");
	return 0;
}
/* 释放块设备 */
void ramdisk_release(struct gendisk *disk, fmode_t mode){
	printk("ramdisk release\r\n");
}
/* 获取磁盘信息 */
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,
};
/* “制造请求”函数 */
void 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;

	offset = (bio->bi_iter.bi_sector) << 9;	/* 获取要操作的设备的偏移地址 */

	/* 处理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, ramdisk.ramdiskbuf + offset, len);
		else if(bio_data_dir(bio) == WRITE)	/* 写数据 */
			memcpy(ramdisk.ramdiskbuf + offset, ptr, len);
		offset += len;
	}
	set_bit(BIO_UPTODATE, &bio->bi_flags);
	bio_endio(bio, 0);
}
/* 驱动出口函数 */
static int __init ramdisk_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、注册块设备 */
	ramdisk.major = register_blkdev(0, RAMDISK_NAME); /* 由系统自动分配主设备号 */
	if(ramdisk.major < 0) {
		goto register_blkdev_fail;
	}  
	printk("ramdisk major = %d\r\n", ramdisk.major);

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

	/* 5、分配请求队列 */
	ramdisk.queue = blk_alloc_queue(GFP_KERNEL);
	if(!ramdisk.queue){
		ret = -EINVAL;
		goto blk_allo_fail;
	}

	/* 6、设置“制造请求”函数 */
	blk_queue_make_request(ramdisk.queue, ramdisk_make_request_fn);

	/* 7、添加(注册)disk */
	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_NAME); /* 名字 */
	set_capacity(ramdisk.gendisk, RAMDISK_SIZE/512);	/* 设备容量(单位为扇区) */
	add_disk(ramdisk.gendisk);

	return 0;

blk_allo_fail:
	put_disk(ramdisk.gendisk);
	//del_gendisk(ramdisk.gendisk);
gendisk_alloc_fail:
	unregister_blkdev(ramdisk.major, RAMDISK_NAME);
register_blkdev_fail:
	kfree(ramdisk.ramdiskbuf); /* 释放内存 */
ram_fail:
	return ret;
}
/* 驱动出口函数 */
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); 
}

module_init(ramdisk_init);
module_exit(ramdisk_exit);
MODULE_LICENSE("GPL");

测试方法和上一节的实验一模一样,参考上一小节即可

Logo

更多推荐