一个失败的块设备驱动案例----用内存模拟磁盘

作者分享了学习Linux块设备驱动过程中,尝试创建一个内存模拟磁盘驱动程序的过程,遇到的问题和解决方案,包括内存分配限制、文件系统交互难题和内存空间显示不足。通过实际操作,读者可了解块设备驱动的基础构造和常见挑战。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一个失败的块设备驱动程序----用内存模拟磁盘

文末附源码

这两天简单学习了一下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");


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值