以内存作为存储介质,在无队列模式下实现的一个简单块设备驱动程序,在最新的Linux3.3.7测试可用。
块设备驱动编写的主要过程:
1.注册块设备驱动
register_blkdev 可以申请获得主设备号;
2.设计自己设备对应的数据结构
代码里面的mybd_info结构,便于管理设备信息,这个数据机构里面的data,是通过vmalloc申请的一块内存,用于存放块设备的数据,这个数据结构不是标准块设备驱动必须的;
3.初始化请求队列和请求处理函数
请求队列中的请求处理函数的作用是截获块设备上的IO请求,在无队列模式下,每一个IO请求以bio结构的形式作为参数传入请求处理函数,请求队列和请求处理函数的初始化过程可以通过blk_alloc_queue和blk_queue_make_request实现。在无队列模式下,请求队列仅仅是请求处理函数的载体,没实质性作用;
4.申请并初始化gendisk结构
一个gendisk结构对应一个块设备或者块设备上的分区,通过alloc_disk可以申请一个gendisk结构,内部很多数据需要自己初始化;
5.注册块设备
所有初始化工作完成后,向系统注册块设备,通过add_disk实现。至此,块设备驱动的初始化工作已经完成;成功的话该块设备可以在/dev目录下查看到;
6.注销块设备驱动和相关数据结构
根据初始化工作的逆过程进行注销和释放内存。
总结一下:一个块设备驱动先要注册才能获得主设备号,块设备驱动在内核中对应一个gendisk结构,gendisk结构包含块设备几乎所有的信息,另外,每个块设备驱动都要申请一个请求队列(包含一个请求处理函数),请求处理函数以参数的形式自动接受到Bio结构(包含一次IO操作的信息),然后通过分析Bio结构进行相应IO操作。
具体代码如下:
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/genhd.h>
#include <linux/blkdev.h>
#include <linux/bio.h>
MODULE_LICENSE("Dual BSD/GPL");
#define NEED_DEBUG
#ifdef NEED_DEBUG
#define MY_DEBUG(fmt, args...) printk(KERN_DEBUG "From my_bd: " fmt, ##args)
#else
#define MY_DEBUG(fmt, args...)
#endif
#define BDNAME "my_bd"
#define MAX_BD_MINOR 1
#define MAX_BD_SIZE_IN_MB 32
#define MAX_BD_SIZE (2*1024*MAX_BD_SIZE_IN_MB) //单位为扇区
#define SECTOR_SHIFT 9
#define SECTOR_SIZE 512
extern struct request_queue *blk_alloc_queue(gfp_t gfp_mask);
extern void blk_queue_make_request(struct request_queue *q, make_request_fn *mfn);
struct mybd_info
{
int size; //size of block device in sector
void *data; //storage data of blokc device
short users;
short media_change;
spinlock_t lock;
struct request_queue *queue; //块设备对应的请求队列,截获IO请求
struct gendisk *gd; //每个块设备或者分区对应一个gendisk结构
struct timer_list timer;
};
struct mybd_info *mybd = NULL;
struct block_device_operations mybd_fop;
static void free_bd_info()
{
if(mybd)
{
if(mybd->data)
vfree(mybd->data);
kfree(mybd);
}
}
static void mydb_make_request(struct request_queue *q, struct bio *bio)
{
struct bio_vec *bvec;
int i;
void *dsk_offset;
dsk_offset = mybd->data + (bio->bi_sector << SECTOR_SHIFT); //bi_sector为该bio在块设备上偏移,以扇区为单位
bio_for_each_segment(bvec, bio, i) {
void *page_buff; //一个page对应的数据缓冲区
switch (bio_rw(bio))
{
case READ:
case READA:
page_buff = kmap(bvec->bv_page) + bio_offset(bio); //kmap通过page结构得到该page对应的数据缓冲区在内核空间的地址
memcpy(page_buff, dsk_offset, bio_cur_bytes(bio)); //实现读操作,其实就是数据的copy
kunmap(bvec->bv_page); //unmap该page
break;
case WRITE:
page_buff = kmap(bvec->bv_page) + bio_offset(bio);
memcpy(dsk_offset, page_buff, bio_cur_bytes(bio));
kunmap(bvec->bv_page);
break;
default:
MY_DEBUG("strange bio_rw in mydb_make_request!");
bio_endio(bio, -EIO);
return;
}
dsk_offset += bio_cur_bytes(bio); //执行下一个bio_vec对应的IO操作前,重新计算该bio_vec对应的数据在块设备上的偏移
}
bio_endio(bio, 0); //bio对应的IO操作结束时调用bio_endio
return;
}
static int __init mybd_init(void)
{
int major = 0;
printk(KERN_ALERT "Hello, world\n");
//register block device
major = register_blkdev(major, BDNAME);
if(major <= 0)
{
printk("register_blkdev failed!\n");
return -1;
}
//init mybd_info, which contains the information of our block device
mybd = (struct mybd_info *)kmalloc(sizeof(struct mybd_info), GFP_KERNEL);
if(mybd == NULL)
{
MY_DEBUG("kmalloc mybd failed!\n");
goto out;
}
MY_DEBUG("major = %d\n", major);
//init mybd
memset(mybd, 0, sizeof(struct mybd_info));
mybd->size = MAX_BD_SIZE; //size的单位是扇区512byte
mybd->data = (void *)vmalloc(mybd->size<<SECTOR_SHIFT); //申请一块内存存放块设备的数据
if(mybd->data == NULL)
{
MY_DEBUG("vmalloc mybd->data failed!\n");
goto out_malloc_mybd;
}
spin_lock_init(&mybd->lock); //初始化自旋锁
//init request queue
mybd->queue = blk_alloc_queue(GFP_KERNEL); //申请请求队列
if(!mybd->queue)
goto out_blk_alloc_queue;
blk_queue_make_request(mybd->queue, mydb_make_request); //绑定请求队列和请求处理函数
//alloc and init gendisk
mybd->gd = alloc_disk(MAX_BD_MINOR); //申请gendisk结构
if (!mybd->gd) {
goto out_alloc_disk;
}
strcpy(mybd->gd->disk_name, BDNAME); //DBNAME就是/dev/中显示的块设备名称
mybd->gd->major = major; //设备驱动申请的主设备号和gendisk绑定
mybd->gd->first_minor = 0; //第一个此设备号
mybd->gd->fops = &mybd_fop; //通过block_device_operations结构中方法来处理对该块设备的控制和操作
mybd->gd->queue = mybd->queue; //把请求队列和gendisk绑定
set_capacity(mybd->gd, mybd->size); //设置块设备的容量
//register block device driver, tell the OS that there is a new block device
add_disk(mybd->gd);
return 0;
out:
MY_DEBUG("out!\n");
return -1;
out_malloc_mybd:
MY_DEBUG("out_malloc_mybd!\n");
free_bd_info();
unregister_blkdev(major, BDNAME);
return -1;
out_blk_alloc_queue:
MY_DEBUG("out_blk_alloc_queue!\n");
out_alloc_disk:
MY_DEBUG("out_alloc_disk!\n");
blk_cleanup_queue(mybd->queue);
free_bd_info();
unregister_blkdev(major, BDNAME);
return -1;
}
static void __exit mybd_exit(void)
{
printk(KERN_ALERT "Bye, world\n");
put_disk(mybd->gd); //注销块设备
del_gendisk(mybd->gd); //释放gendisk结构
blk_cleanup_queue(mybd->queue); //清除请求队列
free_bd_info();
unregister_blkdev(mybd->gd->major, BDNAME); //注销设备驱动
}
module_init(mybd_init);
module_exit(mybd_exit);