在上一篇文章中详细讲解了块设备驱动的相关知识,并有一些参考代码,但是由于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时,进行分区就会报错。
而当我们修改参数为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