首页 > 代码库 > 块设备驱动之内存模拟硬盘

块设备驱动之内存模拟硬盘

转载请注明出处:http://blog.csdn.net/ruoyunliufeng/article/details/25240899

一.块设备驱动框架

app:      open,read,write "hello.txt"
---------------------------------------------  文件的读写
文件系统: vfat, ext2, ext3, yaffs2, jffs2      (把文件的读写转换为扇区的读写)
-----------------ll_rw_block-----------------  扇区的读写(临时先不分析)
                       1. 把"读写"放入队列
                       2. 调用队列的处理函数(优化/调顺序/合并)
            块设备驱动程序     
---------------------------------------------
硬件:        硬盘,flash

与字符设备相比什么差别:

          1.块设备仅仅能以块为单位接受输入和返回输出。而字符设备则以字节为单位。大多数设备是字符设备,由于它们不须要缓冲并且不以固定块大小进行操作。

          2.块设备对于I/O请求有相应的缓冲区。因此它们能够选择以什么顺序进行响应,字符设备无需缓冲且被直接读写。对于存储设备而言调整读写的顺序作用巨大,由于在读写连续的扇区比分离的扇区更快。

          3.符设备仅仅能被顺序读写,而块设备能够随机訪问。尽管块设备可随机訪问,可是对于磁盘这类机械设备而言。顺序地组织块设备的訪问能够提高性能。

二.驱动代码

/* 參考:
 * drivers\block\xd.c
 * drivers\block\z2ram.c
 */

#include <linux/module.h>
#include <linux/errno.h>
#include <linux/interrupt.h>
#include <linux/mm.h>
#include <linux/fs.h>
#include <linux/kernel.h>
#include <linux/timer.h>
#include <linux/genhd.h>
#include <linux/hdreg.h>
#include <linux/ioport.h>
#include <linux/init.h>
#include <linux/wait.h>
#include <linux/blkdev.h>
#include <linux/blkpg.h>
#include <linux/delay.h>
#include <linux/io.h>

#include <asm/system.h>
#include <asm/uaccess.h>
#include <asm/dma.h>

static struct gendisk *ramblock_disk;
static request_queue_t *ramblock_queue;

static int major;

static DEFINE_SPINLOCK(ramblock_lock);

#define RAMBLOCK_SIZE (1024*1024)
static unsigned char *ramblock_buf;

/*几何属性*/
static int ramblock_getgeo(struct block_device *bdev, struct hd_geometry *geo)
{
	/* 容量=heads*cylinders*sectors*512 */
	geo->heads     = 2;                       //多少面
	geo->cylinders = 32;                      //多少环
	geo->sectors   = RAMBLOCK_SIZE/2/32/512;  //一环有多少扇区
	return 0;
}


static struct block_device_operations ramblock_fops = {
	.owner	= THIS_MODULE,                     //一个指向拥有该结构的模块指针。通常设为 THIS_MODULE
	.getgeo	= ramblock_getgeo,                 //几何属性
};

static void do_ramblock_request(request_queue_t * q)
{
	static int r_cnt = 0;
	static int w_cnt = 0;
	struct request *req;
	
	//printk("do_ramblock_request %d\n", ++cnt);


	while ((req = elv_next_request(q)) != NULL)    //一旦有请求req就不为null
	{
		/* 传输数据三要素: 源,目的,长度 */
		/* 源/目的: */
		unsigned long offset = req->sector * 512;    //从哪開始(偏移值)

		/* 目的/源: */
		// req->buffer

		/* 长度: */		
		unsigned long len = req->current_nr_sectors * 512;  //须要传送的扇区数

		if (rq_data_dir(req) == READ)  //假设是读
		{
			//printk("do_ramblock_request read %d\n", ++r_cnt);
			memcpy(req->buffer, ramblock_buf+offset, len);  //将内存中数据复制到buffer中
		}
		else                           //假设是写
		{
			//printk("do_ramblock_request write %d\n", ++w_cnt);
			memcpy(ramblock_buf+offset, req->buffer, len);   //将buffer中的数据拷到内存中
		}		
		
		end_request(req, 1);
	}
}

static int ramblock_init(void)        //入口函数
{
	/* 1. 分配一个gendisk结构体 */
	ramblock_disk = alloc_disk(16); /* 次设备号个数: 分区个数+1 */

	/* 2. 设置 */
	/* 2.1 分配/设置队列: 提供读写能力 */
	ramblock_queue = blk_init_queue(do_ramblock_request, &ramblock_lock);
	ramblock_disk->queue = ramblock_queue;    //把队列放入结构体
	
	/* 2.2 设置其它属性: 比方容量 */
	major = register_blkdev(0, "ramblock");  /* 向内核注冊块设备。假设是0,则分配一个新的主设备号给设备。并将设备号返回给调用者cat /proc/devices */	
	ramblock_disk->major       = major;                 //主设备号
	ramblock_disk->first_minor = 0;                    //次设备号
	sprintf(ramblock_disk->disk_name, "ramblock");      //名字
	ramblock_disk->fops        = &ramblock_fops;        //操作函数
	set_capacity(ramblock_disk, RAMBLOCK_SIZE / 512);  //容量

	/* 3. 硬件相关操作 */
	ramblock_buf = kzalloc(RAMBLOCK_SIZE, GFP_KERNEL); //分配一块内存

	/* 4. 注冊 */
	add_disk(ramblock_disk);        //一点调用它,磁盘设备将被“激活”,并随时会调用它提供方法

	return 0;
}

static void ramblock_exit(void)         //出口函数
{
	unregister_blkdev(major, "ramblock"); //注销块设备驱动程序
	del_gendisk(ramblock_disk);           //卸载磁盘
	put_disk(ramblock_disk);
	blk_cleanup_queue(ramblock_queue);

	kfree(ramblock_buf);
}

module_init(ramblock_init);
module_exit(ramblock_exit);

MODULE_LICENSE("GPL");


三.驱动分析

           我们的驱动程序要实现的功能就是将内存模拟硬盘进行操作。
    1. 分配gendisk结构体
    2. 设置
        2.1 分配/设置队列,并将它放入结构体中,用来提供读写能力
        2.2 设置gendisk其它信息(主设备号、次设备号、名字、操作函数、容量)

                  操作函数:这里面主要调用了ramblock_getgeo函数用来设置硬盘的属性(尽管是RAM模拟的,为了使用 老的工具还是要假装设置一下)

    3.硬件相关的操作(就是分配一块内存。比較简单)         
    4. 注冊: add_disk

    4.请求处理函数(块设备驱动的核心)

              当内核须要驱动程序处理读取、写入以及其它的操作时。就会调用改函数。

static void do_ramblock_request(request_queue_t * q)
{
	static int r_cnt = 0;
	static int w_cnt = 0;
	struct request *req;
	
	//printk("do_ramblock_request %d\n", ++cnt);


	while ((req = elv_next_request(q)) != NULL)    //一旦有请求req就不为null
	{
		/* 传输数据三要素: 源,目的,长度 */
		/* 源/目的: */
		unsigned long offset = req->sector * 512;    //从哪開始(偏移值)

		/* 目的/源: */
		// req->buffer

		/* 长度: */		
		unsigned long len = req->current_nr_sectors * 512;  //须要传送的扇区数

		if (rq_data_dir(req) == READ)  //假设是读
		{
			//printk("do_ramblock_request read %d\n", ++r_cnt);
			memcpy(req->buffer, ramblock_buf+offset, len);  //将内存中数据复制到buffer中
		}
		else                           //假设是写
		{
			//printk("do_ramblock_request write %d\n", ++w_cnt);
			memcpy(ramblock_buf+offset, req->buffer, len);   //将buffer中的数据拷到内存中
		}		
		
		end_request(req, 1);
	}
}

这段代码首先推断是否有请求,假设有依据传输数据三要素进行设置,然后推断读写,进行对应的操作。

 

參考:韦东山视频第二期

           LDD3



块设备驱动之内存模拟硬盘