块设备驱动程序分析之内存模拟磁盘

来源:互联网 发布:java连接池用线程写 编辑:程序博客网 时间:2024/05/17 08:58
#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);

static struct block_device_operations ramblock_fops = {
.owner= THIS_MODULE,
};

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

static void do_ramblock_request(request_queue_t * q)
{
static int cnt = 0;
struct request *req;

        / *这个循环里面从队列里一个一个地取出请求,并处理* /
while ((req = elv_next_request(q)) != NULL) {
/* 数据传输三要素: 源,目的,长度 */
/* 源/目的: */
unsigned long offset = req->sector * 512;

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

/* 长度: */
unsigned long len = req->current_nr_sectors * 512;

if (rq_data_dir(req) == READ)
{
memcpy(req->buffer, ramblock_buf+offset, len);//读的时候从内存读取数据放入req->buffer
}
else
{
memcpy(ramblock_buf+offset, req->buffer, len);//写的时候从req->buffer读取数据到内存
}
end_request(req, 1);//一个请求处理完毕后要结束请求,才能处理下一个请求
}
}

static int ramblock_init(void)
{
ramblock_disk = alloc_disk(16); 

ramblock_queue = blk_init_queue(do_ramblock_request, &ramblock_lock);
ramblock_disk->queue = ramblock_queue;
major = register_blkdev(0, "ramblock");  /* 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);

ramblock_buf = kzalloc(RAMBLOCK_SIZE, GFP_KERNEL);
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");

总结:
在这个程序里面,我们开辟了一块内存区模拟这个磁盘。也就是说,我们对块设备的操作实际上是对这块内存的操作。对于这块内存的操作我们有必要来唠叨一下:首先数据传送肯定需要三个要素,一是源,二是目的,三是长度。我们需要对这些参数进行设置,所有的这些参数信息都在req这个结构体里面,其中req->sector表示下一个要提交的扇区,那么ramblock_buf+req->sector*512就表示要下一次要处理的内存的首地址。req->current_nr_sectors表示当前要处理的扇区的个数,那么req->current_nr_sectors就表示要处理的内存的长度。而读数据时,读出的数据放在req->buffer中,写数据时,要写入的数据也在req->buffer中,所以req->buffer既是源也是目的。

其实说道这里,我已经乱了。我已经完全不知道程序是如何执行的,所以我们还是从应用空间出来,在来啰嗦一遍:
我们以读取为例,当应用空间读块设备的时候,会调用ll_rw_block函数,然后会将请求放入到我们在驱动程序里面申请的队列里面,但是此时不会处理该请求,当请求结束后,才开始处理所有的请求。这时就会调用我们注册的队列处理函数来处理队列里面的请求,处理请求的时候我们会根据电梯调度算法一个一个取出请求来处理,当然取出请求的时候,需要获得请求结构体,里面记载了该请求的相关信息,我们处理该请求需要依赖于这些信息。而且每处理完一个请求需要结束该请求,否则无法处理下一个请求。但是我们需要知道,我们构造请求时候有合并请求,这就告诉我们,处理每个请求并不是处理一个512字节的块,而是处理多个块的组合。

测试:
(1)insmod block.ko
(2)格式化: mkdosfs /dev/ramblock
(3)挂接: mount /dev/ramblock /tmp/
(4)读写文件: cd /tmp, 在里面vi文件,如vi text.c,在里面写入hello,ls命令可以看到text.c文件,但是看不见tmp目录下原来的文件了
(5)卸载:cd /; umount /tmp/  我们发现tmp目录下面已经没有text.c这文件了,有的只是tmp目录下原来的文件。
以上我们将块设备挂接到tmp目录下之后,tmp目录就相当于块设备了。
原创粉丝点击