Linux块设备驱动的实现

以内存作为存储介质,在无队列模式下实现的一个简单块设备驱动程序,在最新的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);


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值