Linux 块设备驱动 (二)

时间:2022-09-10 17:18:45

linux下Ramdisk驱动

1 什么是Ramdisk

Ramdisk是一种模拟磁盘,其数据实际上是存储在RAM中,它使用一部分内存空间来模拟出一个磁盘设备,并以块设备的方式来组织和访问这片内存。对于用户来说可以把Ramdisk与通常的硬盘分区同等对待来使用。那些经常被访问、并且不会被更改的文件,可以通过Ramdisk被存放在内存中,这样能够明显地提高系统的响应性能。

2  Ramdisk的产生过程

近几年来,计算机的CPU、内存和显卡等主要配件的性能都提升得很快,而与之相对应的磁盘系统性能正越来越严重地成为整个电脑系统性能提升的瓶颈。虽然磁盘外部接口也从以前的ATA33发展到今天的SATA 6Gbit/s。但是,这还是不能彻底解决磁盘瓶颈的问题,特别是在运行一些对数据存取速度要求很高的程序,如数字影像处理或玩3D游戏装入纹理数据时,受磁盘存取速度的影响,屏幕画面时常会出现延迟和停顿。于是,虚拟磁盘技术(Ramdisk)应运而生,它可解上述问题的“燃眉之急”。

3  Ramdisk的特点

Ramdisk是基于内存的块设备,以内存作为实际的存储介质,但以块设备的方式组织,所以它具有比实际磁盘更快的存取速度。但这也带来了另一个问题,当系统重启,内存掉电后,Ramdisk中存储的数据也将会随之消失。所以Ramdisk不适合作为长期保存文件的介质。[2]

4  Ramdisk的作用

Ramdisk磁盘对于保存加密数据来说是一个福音,因为我们如果将加密的文件解密到普通的磁盘的话,即使我们随后删除了解密文件,数据仍然会留在磁盘上。这样是非常不安全的。而对于Ramdiak来说,就不存在这样的问题。另外,假设有几个文件要频繁的使用,你如果将它们加到内存当中,程序运行速度会大幅提高,这是由存储介质的特性决定的(因为内存的读写速度远高于硬盘)。像Web服务器这样的计算机,需要大量的读取和交换特定的文件,因此,在Web服务器上建立Ramdisk会大大提高网络读取的速度。

5  主要代码的解释

在程序的开始,首先定义了几个宏,用于Ramdisk驱动中用到的一些变量的统一赋值:

#define GAO_RD_DEV_NAME "gao_rd" //设备名称

#define GAO_RD_DEV_MAJOR 220  //主设备号

#define GAO_RD_MAX_DEVICE 2    //最大设备数

#define GAO_BLOCKSIZE  1024     //块大小

#define GAO_RD_SECTOR_SIZE 512   //扇区大小

#define GAO_RD_SIZE (4*1024*1024)  //总大小

#define GAO_RD_SECTOR_TOTAL (GAO_RD_SIZE/GAO_RD_SECTOR_SIZE)  //扇区数

这些宏变量描述了驱动程序的一些基本的属性和参数,是Ramdisk的基本信息。

本驱动程序主要完成了如下几个函数:

1)  驱动模块的初始化函数 

int gao_rd_init(void);//初始化

此函数主要完成驱动模块的初始化和必要资源分配的工作。首先,为虚拟磁盘分配必要的内存空间,用作它的存储空间。然后用设备号和设备的名称将此块设备注册到内核中去。接下来要完成的任务就是分配通用磁盘结构体gendisk并赋上相应的值,分配请求队列以及帮定此设备的制造请求函数和请求队列。最后将通用磁盘结构体gendisk添加到内核中的相关队列里。

代码如下:

int  gao_rd_init(void)
{
int i;
int err = -ENOMEM; for(i=; i < GAO_RD_MAX_DEVICE; i++)
{
vdisk[i] = vmalloc(GAO_RD_SIZE);
} /*注册vrd设备驱动程序*/
if(register_blkdev(GAO_RD_DEV_MAJOR, GAO_RD_DEV_NAME))/*对此块设备进行注册*/
{
err = -EIO;
    goto out;
} for(i = ; i < GAO_RD_MAX_DEVICE; i++)
{
device[i].data = vdisk[i]; /*分配gendisk结构题,gendisk结构题是注册会设备的信息结构体*/
device[i].gd = alloc_disk();
    if (!device[i].gd)
    goto out; device[i].queue = blk_alloc_queue(GFP_KERNEL);// 分配正常的内核
if (!device[i].queue)
{
put_disk(device[i].gd); goto out;
    }
    blk_queue_make_request(device[i].queue, &gao_rd_make_request);
    blk_queue_hardsect_size(device[i].queue,GAO_BLOCKSIZE);//盘块大小     device[i].gd->major = GAO_RD_DEV_MAJOR;
    device[i].gd->first_minor = i;
    device[i].gd->fops = &vrd_fops;//块设备操作结构体
    device[i].gd->queue = device[i].queue;
    device[i].gd->private_data = &device[i];
    sprintf(device[i].gd->disk_name, "gao_rd%c" , 'a'+i);//
set_capacity(device[i].gd,GAO_RD_SECTOR_TOTAL);
add_disk(device[i].gd);
} printk("RAMDISK driver initialized!"); return ;
out:
while (i--) {
put_disk(device[i].gd);
blk_cleanup_queue(device[i].queue);
} return err;
}

2 驱动模块的卸载函数 

void  gao_rd_exit(void);//模块卸载函数

此函数完成与初始化函数相反的操作。它会删除此驱动模块被分配的通用磁盘结构体,利用设备号和设备名称删除对此设备的注册并释放此设备曾占用的存储空间。

代码如下:

void  gao_rd_exit(void)
{
int i;
for(i = ; i < GAO_RD_MAX_DEVICE; i++)
{
del_gendisk(device[i].gd);//删除gendisk结构体
put_disk(device[i].gd);//减少gendisk结构体的引用计数
blk_cleanup_queue(device[i].queue);
}
unregister_blkdev(GAO_RD_DEV_MAJOR, GAO_RD_DEV_NAME);
for(i=;i < GAO_RD_MAX_DEVICE; i++)
{
vfree(vdisk[i]);
}
}

驱动模块的制造请求函数 

static  int  gao_rd_make_request(struct request_queue *q, struct bio *bio);//制造请求函数

此函数处理设备使用过程中的实际I/O请求。

它会通过bio结构体获取此次I/O请求的相关信息,例如,存取标志、存取位置、请求队列等必要的I/O信息。之后遍历请求队列中的每一个段,如果是读数据请求,便将指定位置的数据拷贝到缓冲区中;如果是写数据请求,便将缓冲区的数据拷贝到指定的位置上。

代码如下:

static  int  gao_rd_make_request(struct request_queue *q, struct bio *bio)
{
gao_rd_device *pdevice;
char *pVHDDData;
char *pBuffer;
struct bio_vec *bvec;
int i;
if(((bio->bi_sector*GAO_RD_SECTOR_SIZE) + bio-> bi_size) > GAO_RD_SIZE)
{
bio_io_error(bio/*, bio->bi_size*/);
return ;
}
else
{
pdevice = (gao_rd_device *) bio->bi_bdev->bd_disk-> private_data;
pVHDDData = pdevice->data + (bio-> bi_sector*GAO_RD_SECTOR_SIZE);
bio_for_each_segment(bvec, bio, i)/*循环遍历每一个段*/
{
pBuffer = kmap(bvec->bv_page) + bvec-> bv_offset;
switch(bio_data_dir(bio))
{
case READA :
case READ : memcpy(pBuffer, pVHDDData, bvec-> bv_len);
break;
case WRITE : memcpy(pVHDDData, pBuffer, bvec-> bv_len);
break;
default : kunmap(bvec->bv_page);
bio_io_error(bio);
return ;
}
kunmap(bvec->bv_page);//取消内存页地址映射
pVHDDData += bvec->bv_len;
}
/*结束处理,并终止gao_rd_make_request函数*/
bio_endio(bio,);
return ;
}
}

4、编译驱动模块

要编译此驱动模块需要先编写一个Makefile文件:

KERNELDIR = /usr/src/kernels/2.6.27.10--i686/   #指定内核路径

       PWD := $(shell pwd)

       CC  =gcc                                     #指定编译器为gcc

       obj-m := gao_rd.o 

       modules:

              $(MAKE) -C $(KERNELDIR) M=$(PWD) modules

          rm -rf *.o *.mod.c *.mod.o *.o *.order *.symvers   #清除编译后的其它文件

之后在linux的shell终端下执行make命令进行编译:

[root@localhost ramdisk]# make

make -C /usr/src/kernels/2.6.27.10--i686/ M=/root/Desktop/work modules

make[]: Entering directory `/usr/src/kernels/2.6.27.10--i686'

  CC [M]  /root/Desktop/work/gao_rd.o

  Building modules, stage .

  MODPOST  modules

  CC      /root/Desktop/work/gao_rd.mod.o

  LD [M]  /root/Desktop/work/gao_rd.ko

make[]: Leaving directory `/usr/src/kernels/2.6.27.10--i686'

rm -rf *.o *.mod.c *.mod.o *.o *.order *.symvers

现在,在目录下回有一个gao_rd.ko的文件,这个就是编译出来的可以加载的模块文件。

用insmod命令加载此模块后,在/proc/modules文件里就会看到此模块已被加载。

5、测试驱动模块

此驱动模块被加载到内核中后就可以利用此模块创建一个虚拟磁盘设备了。主要步骤如下:

1)#mkdir  /root/Desktop/ramdisk/gao_rd

这个命令的作用是创建一个文件夹,我们用这个文件夹作为虚拟磁盘设备的挂载点。

2)#mknod  /dev/gao_rd0 b 220 0

创建一个块设备,指定块设备的主设备号是220,次设备号自动分配。现在,在/dev目录下就会多出一个块设备,名为gao_rd0。

3)#mke2fs /dev/gao_rd0

用ext2格式对此设备进行格式化。

至此,就完成了一个虚拟磁盘设备的创建。

4)#mount /dev/gao_rd0 /root/Desktop/ramdisk/gao_rd

这个命令的作用是将刚刚创建的虚拟磁盘设备挂载到第一步创建的挂载点上。这样,这个虚拟磁盘就可以使用了。我们可以用ls命令来查看这块虚拟磁盘设备。

以下是完整的代码:

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>//定义了一些常用的函数原型
#include <linux/fs.h>//
#include <linux/errno.h>//一些出错的常量符号的宏
#include <linux/types.h>//定义了一些基本的数据类型。所有类型均定义为适当的数字类型长度。
#include <linux/fcntl.h>//文件控制选项头文件,
#include <linux/vmalloc.h>
#include <linux/hdreg.h>//定义了一些对硬盘控制器进行编程的一些命令常量符号。
#include <linux/blkdev.h>
#include <linux/blkpg.h>
#include <asm/uaccess.h> /*设备名称,段大小,设备大小等信息的定义*/
#define GAO_RD_DEV_NAME "gao_rd" //设备名称
#define GAO_RD_DEV_MAJOR 220 //主设备号
#define GAO_RD_MAX_DEVICE 2 //最大设备数
#define GAO_BLOCKSIZE 1024
#define GAO_RD_SECTOR_SIZE 512 //扇区大小
#define GAO_RD_SIZE (4*1024*1024) //总大小
#define GAO_RD_SECTOR_TOTAL (GAO_RD_SIZE/GAO_RD_SECTOR_SIZE) //总扇区数 typedef struct
{
unsigned char *data;
struct request_queue *queue;
struct gendisk *gd;
}gao_rd_device; static char *vdisk[GAO_RD_MAX_DEVICE]; static gao_rd_device device[GAO_RD_MAX_DEVICE]; static int gao_rd_make_request(struct request_queue *q, struct bio *bio)/*制造请求函数*/
{
gao_rd_device *pdevice;
char *pVHDDData;
char *pBuffer;
struct bio_vec *bvec;
int i; if(((bio->bi_sector*GAO_RD_SECTOR_SIZE) + bio-> bi_size) > GAO_RD_SIZE)
{
bio_io_error(bio/*, bio->bi_size*/);
return ;
}
else
{
pdevice = (gao_rd_device *) bio->bi_bdev->bd_disk-> private_data;
pVHDDData = pdevice->data + (bio-> bi_sector*GAO_RD_SECTOR_SIZE); bio_for_each_segment(bvec, bio, i)/*循环遍历的宏*/
{
pBuffer = kmap(bvec->bv_page) + bvec-> bv_offset;//kmap()函数??? switch(bio_data_dir(bio))//??????????????????????????????
{
case READA :
case READ : memcpy(pBuffer, pVHDDData, bvec-> bv_len);
  break;
case WRITE : memcpy(pVHDDData, pBuffer, bvec-> bv_len);
  break;
default : kunmap(bvec->bv_page);
bio_io_error(bio);
return ;
} kunmap(bvec->bv_page);
pVHDDData += bvec->bv_len;
}
/*结束处理,并终止gao_rd_make_request函数*/
bio_endio(bio, /*bio->bi_size, */);
return ;
}
} int gao_rd_open(struct inode *inode, struct file *filp)
{
return ;
} int gao_rd_release (struct inode *inode, struct file *filp)
{
return ;
} int gao_rd_ioctl(struct inode *inode, struct file *filp, unsigned int cmd,unsigned long arg)
{
//return -ENOTTY;
int error;
struct block_device *bdev = inode->i_bdev;
if(cmd!= BLKFLSBUF)
{
return -ENOTTY;//不适当的I/O控制操作(没有tty终端)
}
error = -EBUSY;//资源正忙
down(&bdev->bd_mount_sem);
if(bdev->bd_openers <= )
{
truncate_inode_pages(bdev->bd_inode->i_mapping,);
error = ;
}
up(&bdev->bd_mount_sem);
return error;
}
//block_device_operations 结构体是对块设备操作的集合
static struct block_device_operations vrd_fops =
{
.owner = THIS_MODULE,
.open = gao_rd_open,
.release = gao_rd_release,
.ioctl = gao_rd_ioctl,
};
int gao_rd_init(void)
{
int i;
int err = -ENOMEM;
for(i=; i < GAO_RD_MAX_DEVICE; i++)
{
vdisk[i] = vmalloc(GAO_RD_SIZE);
}

/*注册vrd设备驱动程序*/
if(register_blkdev(GAO_RD_DEV_MAJOR, GAO_RD_DEV_NAME))//对此块设备进行注册
{
err = -EIO;
goto out;
}
/**/
for(i = ; i < GAO_RD_MAX_DEVICE; i++)
{
device[i].data = vdisk[i];
/*分配gendisk结构题,gendisk结构题是注册会设备的信息结构体*/
device[i].gd = alloc_disk(); if (!device[i].gd)
goto out; device[i].queue = blk_alloc_queue(GFP_KERNEL);//GFP_KERNEL 分配正常的内核
if (!device[i].queue)
{
put_disk(device[i].gd);
goto out;
} blk_queue_make_request(device[i].queue, &gao_rd_make_request);
blk_queue_hardsect_size(device[i].queue,GAO_BLOCKSIZE);//盘块大小 device[i].gd->major = GAO_RD_DEV_MAJOR;
device[i].gd->first_minor = i;
device[i].gd->fops = &vrd_fops;//块设备操作结构体
device[i].gd->queue = device[i].queue;
device[i].gd->private_data = &device[i];
sprintf(device[i].gd->disk_name, "gao_rd%c" , 'a'+i);//
set_capacity(device[i].gd,GAO_RD_SECTOR_TOTAL); add_disk(device[i].gd);
}
printk("RAMDISK driver initialized!");
return ;
out:
while (i--) {
put_disk(device[i].gd);
blk_cleanup_queue(device[i].queue);
}
return err;
} void gao_rd_exit(void)
{
int i;
for(i = ; i < GAO_RD_MAX_DEVICE; i++)
{
del_gendisk(device[i].gd);//删除gendisk结构体
put_disk(device[i].gd);//减少gendisk结构体的引用计数
blk_cleanup_queue(device[i].queue);
}
unregister_blkdev(GAO_RD_DEV_MAJOR, GAO_RD_DEV_NAME);
for(i=;i < GAO_RD_MAX_DEVICE; i++)
{
vfree(vdisk[i]);
}
}
module_init(gao_rd_init);
module_exit(gao_rd_exit); MODULE_LICENSE("Dual BSD/GPL");
Makefile代码
KERNELDIR = /usr/src/kernels/2.6.27.10--i686/

PWD := $(shell pwd)

CC  =gcc
obj-m := gao_rd.o
modules:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules rm -rf *.o *.mod.c *.mod.o *.o *.order *.symvers