Linux块设备驱动实例

在上一篇文章中详细讲解了块设备驱动的相关知识,并有一些参考代码,但是由于linux系统版本的原因,在2.6.35.6版本中,编译有错误,故在这篇文章中,我们贴出了2.6.35.6版本下的块设备驱动的一个简单例子,代码如下所示。

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <asm/uaccess.h>
#include <linux/spinlock.h>
#include <linux/sched.h>
#include <linux/types.h>
#include <linux/fcntl.h>
#include <linux/hdreg.h>
#include <linux/genhd.h>
#include <linux/blkdev.h>
#include <linux/slab.h>

#define BLK_MAJOR 250
#define BLK_NAME "myblkdev"
#define DISK_SECTOR_SIZE 512
#define DISK_BYTES (1024*1024*64)

int blk_major=0;

struct myblk_dev{
	int size;						/*以字节为单位,设备大小*/
	unsigned char *data;			/*数据数组*/
	short users;					/*用户数目*/
	struct request_queue *queue;	/*设备请求队列*/
	struct gendisk *gd;				/*gendisk结构*/
	spinlock_t lock;				/*用于互斥*/
};

struct myblk_dev *myblkdev;

/*块设备请求处理函数*/
static int myblk_make_request(struct request_queue *q,struct bio *bio)
{
	int i;
	char *mem_pbuf;
	char *disk_pbuf;
	struct myblk_dev *pmyblkdev;
	struct bio_vec *pbvec;
	if((bio->bi_sector*DISK_SECTOR_SIZE + bio->bi_size) > DISK_BYTES){
		bio_io_error(bio);
		return 0;
	}
	pmyblkdev = (struct myblk_dev*)bio->bi_bdev->bd_disk->private_data; /*得到设备结构体*/
	//printk("myblk_make_request\n");
	disk_pbuf = pmyblkdev->data+bio->bi_sector*DISK_SECTOR_SIZE;	/*得到要读写的起始位置*/
	
	/*开始遍历这个bio中的每个bio_vec*/
	bio_for_each_segment(pbvec,bio,i){	/*循环分散的内存segment*/
		mem_pbuf = kmap(pbvec->bv_page)+pbvec->bv_offset;/*获得实际内存地址*/
		switch(bio_data_dir(bio)){	/*读写*/
			case READA:
			case READ:
				memcpy(mem_pbuf, disk_pbuf, pbvec->bv_len);
				break;
			case WRITE:
				memcpy(disk_pbuf, mem_pbuf, pbvec->bv_len);
				break;
			default:
				kunmap(pbvec->bv_page);
				bio_io_error(bio);
				return 0;
		}
		kunmap(pbvec->bv_page);/*清除映射*/
		disk_pbuf += pbvec->bv_len;
	}
	bio_endio(bio,0);
	return 0;
}

static int blk_ioctl(struct block_device *dev, fmode_t no, unsigned cmd, unsigned long arg)
{
	printk("#############\n");
	return -ENOTTY;
}

static int blk_open(struct block_device *dev, fmode_t no)
{
	struct myblk_dev *device = dev->bd_inode->i_bdev->bd_disk->private_data;
	spin_lock(&device->lock);
	device->users ++;
	spin_unlock(&device->lock);
	
	printk("blk mount succeed\n");
	return 0;
}

static int blk_release(struct gendisk *gd, fmode_t no)
{
	struct myblk_dev *device = gd->private_data;
	spin_lock(&device->lock);
	device->users --;
	spin_unlock(&device->lock);
	
	printk("blk umount succeed\n");
	return 0;
}

struct block_device_operations blk_ops={
	.owner = THIS_MODULE,
	.open = blk_open,
	.release = blk_release,
	.ioctl = blk_ioctl,
};

static int __init myblkdev_init(void)
{
	blk_major = register_blkdev(blk_major,BLK_NAME);	/*注册驱动*/
	/* register_blkdev注册函数注册失败时,返回一个负值,如果主设备号参数为0,表示动态分配,分配成功返回主设备号,
		如果主设备号参数非0,表示静态分配,分配成功返回0*/
	if(blk_major < 0){
		printk("unable to get major number\n");
		return -EBUSY;
	}else if(blk_major == 0){
		blk_major = BLK_MAJOR;
		printk("regiser blk dev succeed\n");
	}
	
	myblkdev = kmalloc (sizeof(struct myblk_dev), GFP_KERNEL);
	if(myblkdev == NULL){
		unregister_blkdev(blk_major,BLK_NAME);
		return -ENOMEM;
	}
	
	memset(myblkdev, 0, sizeof(struct myblk_dev));
	myblkdev->size = DISK_BYTES;			/*设备的大小*/
	myblkdev->data = vmalloc(DISK_BYTES);	/*设备的数据空间*/
	if(myblkdev->data == NULL){
		printk(KERN_NOTICE "vmalloc failure.\n");
		return -EBUSY;
	}
	spin_lock_init(&myblkdev->lock);
	
	myblkdev->queue = blk_alloc_queue(GFP_KERNEL);/*生成队列*/
	if(myblkdev->queue == NULL){
		printk("blk_alloc_queue failure\n");
		goto out_vfree;
	}
	blk_queue_make_request(myblkdev->queue,myblk_make_request);/*注册make_request,绑定请求制造函数*/
	blk_queue_logical_block_size(myblkdev->queue, DISK_SECTOR_SIZE);/*告知内核块设备硬件扇区的大小*/
	myblkdev->queue->queuedata = myblkdev;/*queuedata是void *指针,用来指向所需要的数据*/
	
	/*分配gendisk,参数是这个磁盘使用的次设备号的数量,一般也就是磁盘分区的数量,此后minors不能被修改,数量1表示不包含分区*/
	myblkdev->gd = alloc_disk(1);	
	if(! myblkdev->gd){
		printk("alloc_disk failure\n");
		goto out_vfree;
	}
	myblkdev->gd->major = blk_major;/*主设备号*/
	myblkdev->gd->first_minor = 0;/*第一个设备号*/
	myblkdev->gd->fops = & blk_ops;/*块文件结构变量*/
	myblkdev->gd->queue = myblkdev->queue;/*请求队列*/
	myblkdev->gd->private_data = myblkdev;/*私有数据指针*/
	snprintf(myblkdev->gd->disk_name,32,"myblkdev%c",'a');/*名字*/
	set_capacity(myblkdev->gd,DISK_BYTES>>9);/*设置gendisk容量*/
	/*增加gendisk,gendisk结构体被分配之后,系统还不能使用这个磁盘,需要调用如下函数来注册这个磁盘设备*/
	add_disk(myblkdev->gd);
	
	printk("gendisk init success\n");
	return 0;
	
out_vfree:
	if(myblkdev->data){
		vfree(myblkdev->data);
	}
	return 0;
	
}

static void __exit myblkdev_exit(void)
{
	/*清除请求队列*/
	blk_cleanup_queue(myblkdev->gd->queue);
	/*释放gendisk,当不再需要一个磁盘时,应当使用如下函数释放gendisk*/
	del_gendisk(myblkdev->gd);
	put_disk(myblkdev->gd);
	if(myblkdev->data){
		vfree(myblkdev->data);
	}
	unregister_blkdev(blk_major,BLK_NAME);
	kfree(myblkdev);
	printk("blk dev exit success!\n");
}

module_init(myblkdev_init);
module_exit(myblkdev_exit);

MODULE_AUTHOR("Fang Xieyun");
MODULE_LICENSE("GPL");

在代码中,各函数的基本功能在代码中已经基本说明了。其中需要注意的是,alloc_disk函数,其函数原型如下:

struct gendisk *alloc_disk(int minors)
其功能是分配gendisk,参数minors是这个磁盘使用的次设备号的数量,一般也就是磁盘分区的数量,此后minors不能被修改,当这个参数为1时,表示不包含分区,也就是不能对这个磁盘进行分区。如下图所示,参数为1时,进行分区就会报错。

Linux块设备驱动实例_第1张图片

而当我们修改参数为2时,就表示有两个分区,可以对这个磁盘进行分区,分区结果如下所示。

Linux块设备驱动实例_第2张图片

其中我们的Makefile文件内容如下所示:

obj-m := myblkdev.o
KERNELDIR := /lib/modules/$(shell uname -r)/build/
PWD:=$(shell pwd)
default:	
	$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
clean:
	$(MAKE) -C $(KERNELDIR) SUBDIRS=$(PWD) clean

install:
	insmod myblkdev.ko

uninstall:
	rmmod myblkdev


你可能感兴趣的:(linux,块设备驱动)