良许Linux教程网 干货合集 Linux 块设备驱动 (二)

Linux 块设备驱动 (二)

1 Ramdisk的概念

Ramdisk是一种虚拟磁盘,它利用一部分内存空间来模拟一个磁盘驱动器,并以块设备的形式来管理和访问这片内存。用户可以像使用普通的硬盘分区一样使用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=0; i if(register_blkdev(GAO_RD_DEV_MAJOR, GAO_RD_DEV_NAME))/*对此块设备进行注册*/
       {
              err = -EIO;
           goto out;
       }

       for(i = 0; i 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 0;
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 = 0; i for(i=0;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 0;
    }
    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 0;            
            }
            kunmap(bvec->bv_page);//取消内存页地址映射
            pVHDDData += bvec->bv_len;
        }
        /*结束处理,并终止gao_rd_make_request函数*/
        bio_endio(bio,0);
        return 0;
    }    
}

4、编译驱动模块

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

KERNELDIR = /usr/src/kernels/2.6.27.10-1-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-1-i686/ M=/root/Desktop/work modules

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

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

  Building modules, stage 2.

  MODPOST 1 modules

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

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

make[1]: Leaving directory `/usr/src/kernels/2.6.27.10-1-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 
#include 
#include //定义了一些常用的函数原型
#include //
#include //一些出错的常量符号的宏
#include //定义了一些基本的数据类型。所有类型均定义为适当的数字类型长度。
#include //文件控制选项头文件,
#include 
#include //定义了一些对硬盘控制器进行编程的一些命令常量符号。
#include 
#include 
#include 

/*设备名称,段大小,设备大小等信息的定义*/
#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 0;
    }
    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 0;            
            }
            
            kunmap(bvec->bv_page);
            pVHDDData += bvec->bv_len;
        }
        /*结束处理,并终止gao_rd_make_request函数*/
        bio_endio(bio, /*bio->bi_size, */0);
        return 0;
    }    
}

int gao_rd_open(struct inode *inode, struct file *filp)
{
    return 0;
}

int gao_rd_release (struct inode *inode, struct file *filp)
{
    return 0;
}

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 bd_inode->i_mapping,0);
        error = 0;
    }
    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=0; i if(register_blkdev(GAO_RD_DEV_MAJOR, GAO_RD_DEV_NAME))//对此块设备进行注册
    {
        err = -EIO;
            goto out;
    }

    /**/
    for(i = 0; i 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 0;
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 = 0; i for(i=0;i "Dual BSD/GPL");

Makefile代码

KERNELDIR = /usr/src/kernels/2.6.27.10-1-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

以上就是良许教程网为各位朋友分享的Linu系统相关内容。想要了解更多Linux相关知识记得关注公众号“良许Linux”,或扫描下方二维码进行关注,更多干货等着你 !

137e00002230ad9f26e78-265x300
本文由 良许Linux教程网 发布,可自由转载、引用,但需署名作者且注明文章出处。如转载至微信公众号,请在文末添加作者公众号二维码。
良许

作者: 良许

良许,世界500强企业Linux开发工程师,公众号【良许Linux】的作者,全网拥有超30W粉丝。个人标签:创业者,CSDN学院讲师,副业达人,流量玩家,摄影爱好者。
上一篇
下一篇

发表评论

联系我们

联系我们

公众号:良许Linux

在线咨询: QQ交谈

邮箱: yychuyu@163.com

关注微信
微信扫一扫关注我们

微信扫一扫关注我们

关注微博
返回顶部