一、块设备
- 块设备驱动是Linux 三大驱动类型之一。
- 块设备驱动要远比字符设备驱动复杂得多,不同类型的存储设备又对应不同的驱动子系统。
- 块设备是针对存储设备的,比如 SD 卡、EMMC、NAND Flash、Nor Flash、SPI Flash、机械硬盘、固态硬盘等。
块设备和字符设备的区别?
- 块设备以块(是VFS基本数据传输单位)为单位进行读写,字符设备以字节的形式进行读写,
- 块设备在结构上是可以进行随机访问的,对于这些设备的读写都是按块进行的块设备使用缓冲区来暂时存放数据,等到条件成熟以后在一次性将缓冲区中的数据写入块设备中。(这样就减少了对块设备的擦除次数,提高了块设备寿命。)
- 字符设备是顺序的数据流设备,字符设备是按照字节进行读写访问的。字符设备不需要缓冲区,对于字符设备的访问都是实时的,而且也不需要按照固定的块大小进行访问
- 总结一下:
字符设备:实时、按照字节访问,无缓冲。
块设备:带有缓冲区,按照块大小进行访问。(一种是随机访问。一种是按顺序访问)
块设备的类别
- 针对不同的存储设备实现了不同的I/O调度算法。
- 块设备结构的不同其 I/O算法也会不同,比如对于EMMC、SD卡、NAND Flash这类没有任何机械设备的存储设备就可以任意读写任何的扇区(块设备物理存储单元)。
- 但是对于机械硬盘这样带有磁头的设备,读取不同的盘面或者磁道里面的数据,磁头都需要进行移动,因此对于机械硬盘而言,将那些杂乱的访问按照一定的顺序进行排列可以有效提高磁盘性能。
- ===这个后面的框架你会发现是有所不同的。
二、块设备驱动框架简述
- 在了解具体的块设备驱动框架的大概,我们现需要了解一下其中的结构体和一些函数
/*
@ 定义在include/linux/fs.h文件中
@ block_device 结构体
@ linux 内核使用 block_device 表示块设备
*/
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;
struct gendisk *bd_disk; //比如一个硬盘或者分区,如果是硬盘的话 bd_disk就指向通用磁盘结构 gendisk
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;
};
/*
@ 向内核注册新的块设备、申请设备号
@ major:主设备号。
@ name:块设备名字。
@ 返回值:如果参数 major 在 1~255 之间的话表示自定义主设备号,那么返回 0 表示注册成功,如果返回负值的话表示注册失败。如果 major 为 0 的话表示由系统自动分配主设备号,那么返回值就是系统分配的主设备号(1~255),如果返回负值那就表示注册失败
*/
int register_blkdev(unsigned int major, const char *name)
/*
@ 如果不使用某个块设备了,那么就需要注销掉
@ major:要注销的块设备主设备号
@ name:要注销的块设备名字。
@ 返回值:无
*/
void unregister_blkdev(unsigned int major, const char *name)
/*
@ 定义在 include/linux/genhd.h中
@ gendisk 结构体
@ linux内核使用gendisk来描述一个磁盘设备
*/
struct gendisk {
/* major, first_minor and minors are input parameters only,
* don't use directly. Use disk_devt() and disk_max_parts().
*/
int major; /* major number of driver */
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 */
/* Array of pointers to partitions indexed by partno.
* Protected with matching bdev lock but stat and other
* non-critical accesses use RCU. Always access through
* helpers.
*/
struct disk_part_tbl __rcu *part_tbl; /*此数组每一项都对应一个分区信息*/
struct hd_struct part0;
const struct block_device_operations *fops; /*字符设备操作集 file_operations一样*/
struct request_queue *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之前要先申请
@ minors:次设备号数量,也就是gendisk对应的分区数量。
@ 返回值:成功:返回申请到的 gendisk,失败:NULL。
*/
struct gendisk *alloc_disk(int minors)
/*
@ 使用 add_disk 函数将申请到的gendisk添加到内核中
@ disk:要添加到内核的 gendisk。
@ 返回值:无。
*/
void add_disk(struct gendisk *disk)
/*
@ 在初始化 gendisk 的时候也需要设置其容量
@ disk:要设置容量的 gendisk。
@ size:磁盘容量大小,注意这里是扇区数量。块设备中最小的可寻址单元是扇区,一个扇区一般是 512字节比如一个 2MB 的磁盘,其扇区数量就是(2*1024*1024)/512=4096。
@ 返回值:无。
*/
void set_capacity(struct gendisk *disk, sector_t size)
/*
@ 内核会通过 get_disk 和 put_disk 这两个函数来调整 gendisk 的引用计数
@ get_disk是增加 gendisk的引用计数
@ put_disk是减少gendisk的引用计数
*/
truct kobject *get_disk(struct gendisk *disk)
void put_disk(struct gendisk *disk)
/*
@ 删除gendisk的话可以使用函数del_gendisk
@ gp:要删除的gendisk。
@ 返回值:无。
*/
void del_gendisk(struct gendisk *gp)
/*
@ 定义在include/linux/blkdev.h中
@ block_device_operations 结构体
@ 和字符设备的file _operations一样,块设备也有操作集
*/
struct block_device_operations {
int (*open) (struct block_device *, fmode_t);
void (*release) (struct gendisk *, fmode_t);
int (*rw_page)(struct block_device *, sector_t, struct page *, int rw); /*读写指定页*/
int (*ioctl) (struct block_device *, fmode_t, unsigned, unsigned long); /*32-32块设备的IO控制*/
int (*compat_ioctl) (struct block_device *, fmode_t, unsigned, unsigned long); /*64-32块设备IO控制*/
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 *);
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; /*表示此结构属于哪个模块*/
};
- 我们上面的介绍的fops操作集里面不像字符设备一样里面有read和write函数,但是我们呢,是肯定要进行读写操作的。
- 要想了解整个流程,首先就要引入块设备驱动中非常重要的三个角色。
request_queue、
request
bio - 内核将对块设备的读写都发送到请求队列 request_queue 中,request_queue 中是大量的request(请求结构体),而 request 又包含了 bio,bio 保存了读写相关数据。比如从块设备的哪个地址开始读取、读取的数据长度,读取到哪里,如果是写的话还包括要写入的数据等。
- 下面我们继续了解一下其中的结构体和一些函数目的就是可以了解requestqueue和quest。
/*
@ 上面谈到过block_device 里面含有requet_queue.
@ 因为在编写块设备驱动的时候,每个磁盘(gendisk)都要分配一个 request_queue
*/
/*============================================================机械磁头硬盘
@ 这个一般用于像机械硬盘这样的存储设备,需要 I/O 调度器来优化数据读写过程
@ 申请并初始化一个 request_queue
@ rfn:请求处理函数指针,每个 request_queue 都要有一个请求处理函数,
@ lock:自旋锁指针,需要驱动编写人员定义一个自旋锁,然后传递进来。请求队列会使用这个自旋锁。
@ 返回值:如果为NULL的话表示失败,成功的话就返回申请到的request_queue 地址。
*/
request_queue *blk_init_queue(request_fn_proc *rfn, spinlock_t *lock)
/*
@ 请求处理函数需要驱动编写人员自行实现
@ 与上面依赖
@ q:请求队列
*/
void (request_fn_proc) (struct request_queue *q) /*我们只传入了request+_queue*/
/*===========================================================EMMC、SD卡非机械设备
@ 但是对于 EMMC、SD 卡这样的非机械设备,可以进行完全随机访问,所以就不需要复杂的 I/O 调度器了(分配请求队列+绑定制造请求函数)
@ gfp_mask: 内存分配掩码
@ 返回值:申请到的无 I/O调度的request_queue。
*/
struct request_queue *blk_alloc_queue(gfp_t gfp_mask)
/*
@ 上面blk_alloc_queue函数申请到的请求队列绑定一个“制造请求”函数
@ q:需要绑定的请求队列,也就是 blk_alloc_queue申请到的请求队列。
@ mfn:需要绑定的“制造”请求函数
@ 返回值:无
*/
void blk_queue_make_request(struct request_queue *q, make_request_fn *mfn)
/*
@ 与上面依赖
@ “制造请求”函数需要驱动编写人员实现
@ 返回值:无。
*/
void (make_request_fn) (struct request_queue *q, struct bio *bio)
/*
@ 当卸载块设备驱动的时候我们还需要删除掉前面申请到的 request_queue
@ q:需要删除的请求队列
@ 返回值:无。
*/
void blk_cleanup_queue(struct request_queue *q)
/*
@ 请求队列(request_queue)里面包含的就是一系列的请求(request)
*/
/*
@ 从request_queue中依次获取每个request
@ q:指定request_queue。
@ 返回值:request_queue 中下一个要处理的请求(request),如果没有要处理的请求就返回NULL
@
*/
request *blk_peek_request(struct request_queue *q)
/*
@ 获取到下一个要处理的请求以后就要开始处理这个请求
@ req:要开始处理的请求
@ 返回值:无。
*/
void blk_start_request(struct request *req)
/*
@ 此函数功能等是上面的两个函数的合体==一步到位处理请求
@ q:指定request_queue。
*/
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;
}
- 每个request里面里面会有多个bio,bio保存着最终要读写的数据、地址等信息上层应用程序对于块设备的读写会被构造成一个或多个bio结构,bio结构描述了要读写的起始扇区、要读写的扇区数量、是读取还是写入、页偏移、数据长度等等信息。上层会将 bio提交给I/O调度器, I/O 调度器会将这些bio构造成request结构,而一个物理存储设备对应一个 request_queue,request_queue里面顺序存放着一系列的 request。新产生的 bio可能被合并到 request_queue里现有的 request 中,也可能产生新的 request,然后插入到 request_queue 中合适的位置,这一切都是由I/O 调度器来完成的
- 下面我们继续了解一下其中的结构体和一些函数目的就是可以了解quest中的bio。
/*
@ 定义在 include/linux/blk_types.h中
@ bio 结构体
*/
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
/*
* Optional ioc and css associated with this bio. Put on bio
* release. Read comment on top of bio_associate_current().
*/
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];
};
/*
@ 属于bio中
@ 定义在 include/linux/blk_types.h中
@ bio 结构体
*/
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中
@ 定义在 include/linux/blk_types.h中
@ bio 结构体
*/
struct bio_vec {
struct page *bv_page; /* 页 */
unsigned int bv_len; /* 长度 */
unsigned int bv_offset; /* 偏移 */
};
- 上面的bio和bio_vec和bvec_iter的关系
- 我们对于物理存储设备的操作不外乎就是将 RAM 中的数据写入到物理存储设备中,或者、将物理设备中的数据读取到 RAM 中去处理。数据传输三个要求:数据源、数据长度以及数据目的地,也就是你要从物理存储设备的哪个地址开始读取、读取到 RAM 中的哪个地址处、读取的数据长度是多少。
bi_iter这个结构体成员变量就用于描述物理存储设备地址信息,比如要操作的扇区地址
bi_io_vec指向 bio_vec数组首地址,bio_vec数组就是RAM信息,比如页地址、页偏移以及长度
三、简单实例
- 接下来我们使用开发板上的 RAM 模拟一段块设备,也就是ramdisk,然后编写块设备驱动
- 我们上面也提到过我们在上面请求队列的时候分为机械硬盘和比如SD卡非机械硬盘这两种不同的
- 一般 blk_alloc_queue 和blk_queue_make_request 是搭配在一起使用的,用于那么非机械的存储设备、无需 I/O 调度器,比如 EMMC、SD 卡等。blk_init_queue 函数会给请求队列分配一个 I/O调度器,用于机械存储设备,比如机械硬盘等。
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/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 */
#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设备 */
/*
* @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);
}
}
/*
* @description : 驱动出口函数
* @param : 无
* @return : 无
*/
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;
}
/*
* @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);
}
module_init(ramdisk_init);
module_exit(ramdisk_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zuozhongkai");
- 当我们把上面细心阅读后,会发现块设备驱动框架还是有些字符设备的影子的,只是在具体实现上要复杂一下。
2、对于EMMC、SD、ramdisk这样没有机械结构的存储设备,我们可以直接访问任意一个扇区,因此可以不需要 I/O 调度器,也就不需要请求队列了
- 这里主要贴出区别的地方
/*
@ 驱动函数入口
*/
static int __init ramdisk_init(void)
{
....
/* 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 ret;
}
/*
* @description : “制造请求”函数
* @param-q : 请求队列
* @return : 无
*/
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);
}
- (分配请求队列+绑定制造请求函数) 这部分代替了原有的初始化请求队列
整体例子代码
#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 */
#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设备 */
/*
* @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 : 无
*/
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);
}
/*
* @description : 驱动出口函数
* @param : 无
* @return : 无
*/
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;
}
/*
* @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);
}
module_init(ramdisk_init);
module_exit(ramdisk_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zuozhongkai");