一个失败的块设备驱动程序----用内存模拟磁盘
文末附源码
这两天简单学习了一下linux的块设备驱动,在观看视频、阅读数据、查找网页的帮助下,终于完成了一个失败的块设备驱动。有什么不对的地方还请读者在评论区指正。
开始
块设备驱动程序和我们的字符设备驱动程序一样,不外乎也是分配、设置、注册结构体,不过这个结构体和我们的字符设备驱动程序的结构体大不一样,不过先跳过这些细节,本文实现的是一个mydisk的驱动程序,正如标题所言,这是一个失败的块设备驱动程序。视频参考韦东山老师老一期的驱动大全。
首先来看驱动的入口函数
static int __init mydisk_init(void)
{
int ret;
/* 1. 申请设备号 */
ret = -EBUSY;
g_iMyDiskMajor = register_blkdev(g_iMyDiskMajor, DEVICE_NAME);
if(g_iMyDiskMajor <= 0)
{
goto err;
}
/* 2. 分配gendisk结构体 */
ret = -EBUSY;
g_ptMyGendisk = alloc_disk(16);
if(!g_ptMyGendisk)
goto out_disk;
/* 3. 分配请求队列 */
g_ptMyGenDiskQueue = blk_mq_init_sq_queue(&g_tTagSet, &g_tMyGenDiskMqOps, 16,
BLK_MQ_F_SHOULD_MERGE);
if (IS_ERR(g_ptMyGenDiskQueue))
{
ret = PTR_ERR(g_ptMyGenDiskQueue);
g_ptMyGenDiskQueue = NULL;
goto out_queue;
}
/* 4. 设置gendisk */
g_ptMyGendisk->major = g_iMyDiskMajor;
g_ptMyGendisk->first_minor = 0;
g_ptMyGendisk->fops = &g_tMyBLDFOps;
sprintf(g_ptMyGendisk->disk_name, "mydisk");
g_ptMyGendisk->queue = g_ptMyGenDiskQueue;
/* 5. 硬件操作(这里是内存分配) */
ret = -EBUSY;
g_pucMyDiskBuff = vmalloc(MYDISK_SIZE);
if(!g_pucMyDiskBuff)
goto out_kzalloc;
memset(g_pucMyDiskBuff, 0, sizeof(char) * MYDISK_SIZE);
mutex_lock(&g_tMyDiskMutex);
set_capacity(g_ptMyGendisk, MYDISK_SIZE / 512);
mutex_unlock(&g_tMyDiskMutex);
/* 6. 注册gendisk */
add_disk(g_ptMyGendisk);
blk_register_region(MKDEV(g_iMyDiskMajor, 0), g_iMyDiskMinorsCount, THIS_MODULE,
MyDiskFind, NULL, NULL);
return 0;
out_kzalloc:
out_queue:
put_disk(g_ptMyGendisk);
out_disk:
unregister_blkdev(g_iMyDiskMajor, DEVICE_NAME);
err:
return ret;
}
入口函数主要分为:申请设备号、分配gendisk结构体、分配请求队列、设置gendisk结构体、硬件操作(在这个示例中是分配内存)、注册gendisk结构体 六个操作
这是驱动程序大体的框架。
然后注意到这里面有两个operations结构体
g_ptMyGenDiskQueue = blk_mq_init_sq_queue(&g_tTagSet, &g_tMyGenDiskMqOps, 16,
BLK_MQ_F_SHOULD_MERGE);
g_ptMyGendisk->fops = &g_tMyBLDFOps;
和字符设备驱动程序类似,这个Ops是我们要重点关照的对象,显然,这个queue的ops是请求队列相关的,这个gendisk的ops是和我们的磁盘相关的。
这个g_tMyBLDFOps里的open和release函数似乎每一次操作都会调用,在使用fdisk创建分区,和使用mkfs.ext4创建文件系统时,这两个函数出现的次数很频繁。而g_tMyGenDiskMqOps里的queue_rq函数,主要负责处理用户对磁盘的读写功能,一会看到它的代码,就会发现里面有一个大大的READ的字符。
这几个结构体和函数,可以参考z2ram.c里面的,在这就不再赘述了
高潮
在带着“懵懂”的眼神写完这些代码之后,我已经迫不及待要上机调试了,这里发现了许多问题:
1、驱动申请的内存不宜太大,这里我分配10MB的内存就会报错,不知道为什么
2、在insmod成功之后,要使用fdisk来进行分区,分区之后使用mkfs.ext4来创建文件系统,这里创建成功了,但是无法进行读写,google了一下,资料很多,但是一语中的的暂时没有找到,不过这里已经涉及到文件系统的知识了,待日后更加了解文件系统了再来研究
3、这个例程建议使用下面这个命令来创建文件系统,否则甚至无法挂载
mke2fs -t ext4 -O ^has_journal /dev/mydisk1
4、在成功挂载后还是无法读写目录,明明有内存,但是提示没有空间
结尾
经过这么一次折腾,大体了解了一下块设备驱动的轮廓,但是还仅仅是用内存模拟,如果涉及真实的硬件设备,还得熟悉他们使用的协议,比如I2C、SPI等等,eMMC的资料好像很少。
源码
#define DEVICE_NAME "mydisk"
#include <linux/major.h>
#include <linux/vmalloc.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/blk-mq.h>
#include <linux/bitops.h>
#include <linux/mutex.h>
#include <linux/slab.h>
#include <linux/hdreg.h>
const unsigned int MYDISK_SIZE = 1024 * 1024;
static struct mutex g_tMyDiskMutex;
static int g_iMyDiskMajor = 0;
static struct gendisk *g_ptMyGendisk;
static struct request_queue *g_ptMyGenDiskQueue;
static struct blk_mq_tag_set g_tTagSet;
static int g_iMyDiskMinorsCount = 8;
static unsigned char *g_pucMyDiskBuff;
static blk_status_t MyDiskQueueRQ(struct blk_mq_hw_ctx *hctx,
const struct blk_mq_queue_data *bd)
{
struct request *req;
unsigned long start;
unsigned long len;
req = bd->rq;
start = blk_rq_pos(req) << 9; /* 获取请求的扇区开始地址 */
len = blk_rq_cur_bytes(req); /* 请求的字节数量? */
if (start + len > MYDISK_SIZE) {
pr_err(DEVICE_NAME ": bad access: block=%llu, "
"count=%u\n",
(unsigned long long)blk_rq_pos(req),
blk_rq_cur_sectors(req));
return BLK_STS_IOERR;
}
mutex_lock(&g_tMyDiskMutex);
while(len)
{
int size = MYDISK_SIZE - start;
void *buffer = bio_data(req->bio);
if (start + len < MYDISK_SIZE)
size = len;
if (rq_data_dir(req) == READ)
{
memcpy(buffer, (char *)g_pucMyDiskBuff + start, size);
}
else
{
memcpy((char *)g_pucMyDiskBuff + start, buffer, size);
}
start += size;
len -= size;
}
mutex_unlock(&g_tMyDiskMutex);
blk_mq_end_request(req, BLK_STS_OK);
return BLK_STS_OK;
}
static const struct blk_mq_ops g_tMyGenDiskMqOps = {
.queue_rq = MyDiskQueueRQ,
};
static int MyDiskOpen(struct block_device *bdev, fmode_t mode)
{
printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
return 0;
}
static void MyDiskRelease(struct gendisk *disk, fmode_t mode)
{
printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
}
static int MyDiskGetgeo(struct block_device *bdev, struct hd_geometry *geo)
{
geo->heads = 2;
geo->cylinders = 32;
geo->sectors = MYDISK_SIZE /2 / 32 / 512;
geo->start = 0;
return 0;
}
static const struct block_device_operations g_tMyBLDFOps =
{
.owner = THIS_MODULE,
.open = MyDiskOpen,
.release = MyDiskRelease,
.getgeo = MyDiskGetgeo,
};
static struct kobject *MyDiskFind(dev_t dev, int *part, void *data)
{
printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
*part = 0;
return get_disk_and_module(g_ptMyGendisk);
}
static int __init mydisk_init(void)
{
int ret;
ret = -EBUSY;
g_iMyDiskMajor = register_blkdev(g_iMyDiskMajor, DEVICE_NAME);
if(g_iMyDiskMajor <= 0)
{
goto err;
}
ret = -EBUSY;
g_ptMyGendisk = alloc_disk(16);
if(!g_ptMyGendisk)
goto out_disk;
g_ptMyGenDiskQueue = blk_mq_init_sq_queue(&g_tTagSet, &g_tMyGenDiskMqOps, 16,
BLK_MQ_F_SHOULD_MERGE);
if (IS_ERR(g_ptMyGenDiskQueue))
{
ret = PTR_ERR(g_ptMyGenDiskQueue);
g_ptMyGenDiskQueue = NULL;
goto out_queue;
}
g_ptMyGendisk->major = g_iMyDiskMajor;
g_ptMyGendisk->first_minor = 0;
g_ptMyGendisk->fops = &g_tMyBLDFOps;
sprintf(g_ptMyGendisk->disk_name, "mydisk");
g_ptMyGendisk->queue = g_ptMyGenDiskQueue;
ret = -EBUSY;
g_pucMyDiskBuff = vmalloc(MYDISK_SIZE);
if(!g_pucMyDiskBuff)
goto out_kzalloc;
memset(g_pucMyDiskBuff, 0, sizeof(char) * MYDISK_SIZE);
mutex_lock(&g_tMyDiskMutex);
set_capacity(g_ptMyGendisk, MYDISK_SIZE / 512);
mutex_unlock(&g_tMyDiskMutex);
add_disk(g_ptMyGendisk);
blk_register_region(MKDEV(g_iMyDiskMajor, 0), g_iMyDiskMinorsCount, THIS_MODULE,
MyDiskFind, NULL, NULL);
return 0;
out_kzalloc:
out_queue:
put_disk(g_ptMyGendisk);
out_disk:
unregister_blkdev(g_iMyDiskMajor, DEVICE_NAME);
err:
return ret;
}
static void __exit mydisk_exit(void)
{
blk_unregister_region(MKDEV(g_iMyDiskMajor, 0), g_iMyDiskMinorsCount);
unregister_blkdev(g_iMyDiskMajor, DEVICE_NAME);
del_gendisk(g_ptMyGendisk);
put_disk(g_ptMyGendisk);
blk_cleanup_queue(g_ptMyGenDiskQueue);
blk_mq_free_tag_set(&g_tTagSet);
vfree(g_pucMyDiskBuff);
return;
}
module_init(mydisk_init);
module_exit(mydisk_exit);
MODULE_LICENSE("GPL");