本节我们利用前两节所总结的内容设计一个简单的块设备驱动程序,分配一块内存作为磁盘实现块设备的功能。
首先是一些宏定义和全局变量
#define RAMDISK_SIZE (1024*1024)
#define SECTOR_SIZE 512
static int major;
struct ramdisk_dev {
unsigned char *buffer; /* The data array */
spinlock_t lock; /* For mutual exclusion */
struct gendisk *gd; /* The gendisk structure */
};
struct ramdisk_dev *ramdisk;
我们定义了一个名为ramdisk_dev的结构描述该块设备,buffer指向存取数据内存,lock是控制访问队列的自旋锁,gd就是核心的gendisk结构体。宏定义RAMDISK_SIZE为整个磁盘的容量,SECTOR_SIZE为每个扇区的大小。
入口和出口函数
static struct block_device_operations ramdisk_fops = {
.getgeo = ramdisk_getgeo,
};
static int __init ramdisk_init(void)
{
major = register_blkdev(major, "ramdisk");
if (major <= 0) {
printk("ramdisk: unable to get major number\n");
return -EBUSY;
}
ramdisk = kmalloc(sizeof(struct ramdisk_dev), GFP_KERNEL);
if (!ramdisk)
goto unregister;
memset(ramdisk, 0, sizeof(struct ramdisk_dev));
ramdisk->buffer = vmalloc(RAMDISK_SIZE);
if (ramdisk->buffer == NULL)
goto unregister;
spin_lock_init(&ramdisk->lock);
ramdisk->gd = alloc_disk(16);
if (ramdisk->gd == NULL)
goto vfree;
ramdisk->gd->major = major;
ramdisk->gd->first_minor = 0;
ramdisk->gd->fops = &ramdisk_fops;
ramdisk->gd->queue = blk_init_queue(ramdisk_request, &ramdisk->lock);
if (ramdisk->gd->queue == NULL)
goto vfree;
sprintf(ramdisk->gd->disk_name, "ramdisk0");
set_capacity(ramdisk->gd, RAMDISK_SIZE / 512);
add_disk(ramdisk->gd);
return 0;
vfree:
vfree(ramdisk->buffer);
unregister:
unregister_blkdev(major, "sbd");
return -ENOMEM;
}
module_init(ramdisk_init);
static void vmem_disk_exit(void)
{
if (ramdisk->gd->queue)
blk_cleanup_queue(ramdisk->gd->queue);
if (ramdisk->gd) {
del_gendisk(ramdisk->gd);
put_disk(ramdisk->gd);
}
if (ramdisk->buffer)
vfree(ramdisk->buffer);
unregister_blkdev(major, "ramdisk");
kfree(ramdisk);
}
module_exit(vmem_disk_exit);
入口函数初始化一个ramdisk_dev,并初始化其中的gendisk结构体,出口函数释放分配的资源。
block_device_operations中成员函数的实现,这里只实现getgeo函数,设置磁头,磁柱,扇区信息
static int ramdisk_getgeo(struct block_device *bdev, struct hd_geometry *geo)
{
/* 容量=heads*cylinders*sectors*512 */
geo->heads = 2;
geo->cylinders = 32;
geo->sectors = RAMDISK_SIZE/2/32/512;
return 0;
}
最后就是处理请求函数的实现了
static void ramdisk_request(struct request_queue *q)
{
struct request *req;
struct bio *bio;
struct bio_vec bvec;
struct bvec_iter iter;
sector_t sector;
unsigned long offset;
unsigned long nbytes;
while ((req = blk_peek_request(q)) != NULL) { //each request
blk_start_request(req);
__rq_for_each_bio(bio, req) { //each bio
sector = bio->bi_iter.bi_sector;
bio_for_each_segment(bvec, bio, iter) { //each bio_vec
char *buffer = __bio_kmap_atomic(bio, iter);
offset = sector*SECTOR_SIZE;
nbytes = bio_cur_bytes(bio);
if (bio_data_dir(bio)) //write
memcpy(ramdisk->buffer + offset, buffer, nbytes);
else //read
memcpy(buffer, ramdisk->buffer + offset, nbytes);
sector += bio_cur_bytes(bio) >> 9;
__bio_kunmap_atomic(buffer);
}
}
__blk_end_request_all(req, 0);
}
}
请求处理函数提取出每个请求,再遍历每个请求的每个bio,再遍历每个bio的bio_vec。
处理每个bio的过程:
通过bio->bvec-iter->bi_sector得到要操作哪个扇区;
提取每个bio_vec;
处理每个bio_vec: 需要三个要素,请求的缓存地址,块设备的读写起始地址,传输数据的字节数
请求的缓存地址:用__bio_kmap_atomic()得到请求的缓存,如果是写操作,将该缓存内容复制到块设备,如果是读,则将块设备内容复制到该缓存。处理结束后要使用__bio_kunmap_atomic()解除对该缓存的映射。
块设备的读写起始地址:用扇区编号乘上扇区容量获得;
传输数据的字节数:用bio_cur_bytes()得到。
接下来就是复制数据了,最后算出下一个bio_vec在哪个扇区,用传输数据的字节数除以扇区容量,表示这次操作占了几个扇区,再累加到当前扇区编号就得到下次操作所在的扇区。
编译通过后加载驱动,可以在/dev目录下看到这个块设备:
再将其格式化为ext2文件系统:mkfs.ext2 /dev/ramdisk0
接下来就可以挂载使用了,我们先建立一个ramdisk目录,将其挂载到该目录上:
使用df指令查看:
可以看到这个磁盘已经成功挂载可以使用了。