23.Linux-块设备驱动(详解)

通过上节的块设备驱动分析,本节便通过内存来模拟块设备驱动 ,方便我们更加熟悉块设备驱动框架

参考内核自带的块设备驱动程序:

drivers/block /xd.c  

drivers/block /z2ram.c  

1.本节需要的结构体如下:

1.1 gendisk磁盘结构体:

 struct gendisk {
        int major;                   //设备主设备号,等于register_blkdev()函数里的major 
        int first_minor;             //起始次设备号,等于0,则表示此设备号从0开始的 
        int minors;              //分区(次设备)数量,当使用alloc_disk()时,就会自动设置该成员
        char disk_name[32];            //块设备名称, 等于register_blkdev()函数里的name
 
        struct hd_struct **part;    /*分区表的信息*/
        int part_uevent_suppress;
        struct block_device_operations *fops; //块设备操作函数
        struct request_queue *queue;  //请求队列,用于管理该设备IO请求队列的指针*
        void *private_data;                    /*私有数据*/
        sector_t capacity;               /*扇区数,512字节为1个扇区,描述设备容量*/
        ....
    };

1.2 request申请结构体:

struct request {  
    //用于挂在请求队列链表的节点,使用函数elv_next_request()访问它,而不能直接访问  
    struct list_head queuelist;   
    struct list_head donelist;  /*用于挂在已完成请求链表的节点*/  
    struct request_queue *q;   /*指向请求队列*/  

    unsigned int cmd_flags;    /*命令标识*/  

    enum rq_cmd_type_bits cmd_type;  //读写命令标志,为 0(READ)表示读, 为1(WRITE)表示写
 
    sector_t sector;                       //要提交的下一个扇区偏移位置(offset)
    ... ...
    unsigned int current_nr_sectors;   //当前需要传送的扇区数(长度) 
    ... ...

    char *buffer;        //当前请求队列链表的申请里面的数据,用来读写扇区数据(源地址)
    ... ...
  };  

2.本节需要的函数如下:

int register_blkdev(unsigned int major, const char *name);

创建一个块设备,当major==0时,表示动态创建,创建成功会返回一个主设备号

unregister_blkdev(unsigned int major, const char *name);

卸载一个块设备, 在出口函数中使用,major:主设备号, name:名称

struct gendisk *alloc_disk(int minors);

分配一个gendisk结构,minors为分区数,填1表示不分区

void del_gendisk(struct gendisk *disk);

释放gendisk结构,在出口函数中使用,也就是不需要这个磁盘了

request_queue *blk_init_queue(request_fn_proc *rfn, spinlock_t *lock);

分配一个request_queue请求队列,分配成功返回一个request_queue结构体

rfn: request_fn_proc结构体,用来执行放置在队列中的请求的处理函数

lock:队列访问权限的自旋锁(spinlock),该锁通过DEFINE_SPINLOCK()来定义

void blk_cleanup_queue(request_queue_t * q);

清除内核中的request_queue请求队列,在出口函数中使用

static DEFINE_SPINLOCK(spinlock_t lock);     

定义一个自旋锁(spinlock)

static inline void set_capacity(struct gendisk *disk, sector_t size);

设置gendisk结构体扇区数(成员copacity), size等于扇区数

该函数内容如下:

disk->capacity = size;

void add_disk(struct gendisk *gd);

向内核中注册gendisk结构体

void put_disk(struct gendisk *disk);

注销内核中的gendisk结构体,在出口函数中使用

struct request *elv_next_request(request_queue_t *q);

通过电梯算法获取申请队列中未完成的申请,获取成功返回一个request结构体,不成功返回NULL

(PS: 不使用获取到的这个申请时,应使用end_request()来结束获取申请)

void end_request(struct request *req, int uptodate);

结束获取申请, 当uptodate==0,表示使用该申请读写扇区失败, uptodate==1,表示成功

static inline void *kzalloc(size_t size, gfp_t flags);

分配一段静态缓存,这里用来当做我们的磁盘扇区用,分配成功返回缓存地址,分配失败会返回0

void kfree(const void *block);

注销一段静态缓存,与kzalloc()成对,在出口函数中使用

rq_data_dir(rq);

获取request申请结构体的命令标志(cmd_flags成员),当返回READ(0)表示读扇区命令,否则为写扇区命令

3.步骤如下:

3.1在入口函数中:

  • 1)使用register_blkdev()创建一个块设备
  • 2) blk_init_queue()使用分配一个申请队列,并赋申请队列处理函数
  • 3)使用alloc_disk()分配一个gendisk结构体
  • 4)设置gendisk结构体的成员
  •   ->4.1)设置成员参数(major、first_minor、disk_name、fops)
  •   ->4.2)设置queue成员,等于之前分配的申请队列
  •   ->4.3)通过set_capacity()设置capacity成员,等于扇区数
  • 5)使用kzalloc()来获取缓存地址,用做扇区
  • 6)使用add_disk()注册gendisk结构体

3.2在申请队列的处理函数中

  • 1) while循环使用elv_next_request()获取申请队列中每个未处理的申请
  • 2)使用rq_data_dir()来获取每个申请的读写命令标志,为 0(READ)表示读, 为1(WRITE)表示写
  • 3)使用memcp()来读或者写扇区(缓存)
  • 4)使用end_request()来结束获取的每个申请

3.3在出口函数中

  • 1)使用put_disk()和del_gendisk()来注销,释放gendisk结构体
  • 2)使用kfree()释放磁盘扇区缓存
  • 3)使用blk_cleanup_queue()清除内存中的申请队列
  • 4)使用unregister_blkdev()卸载块设备

4.代码如下:

#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 DEFINE_SPINLOCK(memblock_lock);                //定义自旋锁
static request_queue_t * memblock_request;                //申请队列
static struct gendisk   *memblock_disk;                  //磁盘结构体
static int memblock_major;

#define BLOCKBUF_SIZE               (1024*1024)              //磁盘大小
#define SECTOR_SIZE                   (512)                    //扇区大小
static unsigned char   *block_buf;                              //磁盘地址


static int memblock_getgeo(struct block_device *bdev, struct hd_geometry *geo)
{    
       geo->heads =2;                                         // 2个磁头分区
       geo->cylinders = 32;                                   //一个磁头有32个柱面
       geo->sectors = BLOCKBUF_SIZE/(2*32*SECTOR_SIZE);      //一个柱面有多少个扇区    
    return 0;
}

static struct block_device_operations memblock_fops = {
       .owner    = THIS_MODULE,
       .getgeo   =  memblock_getgeo,                //几何,保存磁盘的信息(柱头,柱面,扇区)
};

    /*申请队列处理函数*/
static void do_memblock_request (request_queue_t * q)
{
        struct request *req;
        unsigned long offset;
        unsigned long len; 
        static unsigned long r_cnt = 0;
        static unsigned long w_cnt = 0;
              
        while ((req = elv_next_request(q)) != NULL)        //获取每个申请
        {
        offset=req->sector*SECTOR_SIZE;                     //偏移值
        len=req->current_nr_sectors*SECTOR_SIZE;            //长度    
                      
        if(rq_data_dir(req)==READ)
        {            
            memcpy(req->buffer,block_buf+offset,len);       //读出缓存
        }
        else
        {              
            memcpy(block_buf+offset,req->buffer,len);     //写入缓存
        }
        end_request(req, 1);                                            //结束获取的申请
        }    
}

    /*入口函数*/
static int memblock_init(void)
{
     /*1)使用register_blkdev()创建一个块设备*/
     memblock_major=register_blkdev(0, "memblock");     
     
     /*2) blk_init_queue()使用分配一个申请队列,并赋申请队列处理函数*/
     memblock_request=blk_init_queue(do_memblock_request,&memblock_lock);
    
     /*3)使用alloc_disk()分配一个gendisk结构体*/
     memblock_disk=alloc_disk(16);                        //不分区
    
     /*4)设置gendisk结构体的成员*/
     /*->4.1)设置成员参数(major、first_minor、disk_name、fops)*/           
     memblock_disk->major = memblock_major;
     memblock_disk->first_minor = 0;
     sprintf(memblock_disk->disk_name, "memblock");
     memblock_disk->fops = &memblock_fops;
        
     /*->4.2)设置queue成员,等于之前分配的申请队列*/
     memblock_disk->queue = memblock_request;
      
     /*->4.3)通过set_capacity()设置capacity成员,等于扇区数*/
     set_capacity(memblock_disk,BLOCKBUF_SIZE/SECTOR_SIZE);
   
     /*5)使用kzalloc()来获取缓存地址,用做扇区*/
     block_buf=kzalloc(BLOCKBUF_SIZE, GFP_KERNEL);
 
     /*6)使用add_disk()注册gendisk结构体*/
     add_disk(memblock_disk);   
     return  0;
}
static void memblock_exit(void)
{        
      /*1)使用put_disk()和del_gendisk()来注销,释放gendisk结构体*/
      put_disk(memblock_disk);
      del_gendisk(memblock_disk);
      /*2)使用kfree()释放磁盘扇区缓存   */ 
      kfree(block_buf);
      /*3)使用blk_cleanup_queue()清除内存中的申请队列    */
      blk_cleanup_queue(memblock_request);
      
      /*4)使用unregister_blkdev()卸载块设备               */
      unregister_blkdev(memblock_major,"memblock");
}

module_init(memblock_init);
module_exit(memblock_exit);
MODULE_LICENSE("GPL");

5.测试运行

insmod ramblock.ko                                     //挂载memblock块设备

mkdosfs /dev/memblock                               //将memblock块设备格式化为dos磁盘类型

mount /dev/ memblock   /tmp/                    //挂载块设备到/tmp目录下

接下来在/tmp目录下vi 1.txt文件,最终都会保存在/dev/ memblock块设备里面

cd /; umount /tmp/                    //退出/tmp,卸载,同时之前读写的文件也会消失

cat /dev/memblock > /mnt/memblock.bin   //在/mnt目录下创建.bin文件,然后将块设备里面的文件追加到.bin里面

然后进入linux的nfs挂载目录中

sudo mount -o loop ramblock.bin   /mnt      //挂载ramblock.bin, -loop:将文件当做磁盘来挂载

如下图,就可以找到我们之前在开发板上创建的1.txt了

说明这个块设备测试运行无误

6.使用fdisk来对磁盘分区

(fdisk命令使用详解: http://www.cnblogs.com/lifexy/p/7661239.html)

共分了两个分区,如下图所示:

如下图,接下来就可以向上小节那样,分别操作多个分区磁盘了:

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏程序猿DD

Spring Cloud Hystrix的请求合并

通常微服务架构中的依赖通过远程调用实现,而远程调用中最常见的问题就是通信消耗与连接数占用。在高并发的情况之下,因通信次数的增加,总的通信时间消耗将会变的不那么理...

1925
来自专栏一名合格java开发的自我修养

Storm的ack机制在项目应用中的坑

正在学习storm的大兄弟们,我又来传道授业解惑了,是不是觉得自己会用ack了。好吧,那就让我开始啪啪打你们脸吧。

711
来自专栏Golang语言社区

Go语言 Go的网络轮询及IO机制

简介 这篇介绍了Go的运行时系统——网络I/O部分。 阻塞 Go语言中,所有的I/O都是阻塞的,因此我们在写Go系统的时候要秉持一个思想:不要写阻塞的inter...

3827
来自专栏蓝天

redis的一些简介

Redis是Remote Dictionary Server的缩写,他本质上一个Key/Value数据库,与Memcached类似的NoSQL型数据库。

781
来自专栏大内老A

谈谈分布式事务之三: System.Transactions事务详解[下篇]

在前面一篇给出的Transaction的定义中,信息的读者应该看到了一个叫做DepedentClone的方法。该方法对用于创建基于现有Transaction对象...

1928
来自专栏Linux驱动

26.Linux-网卡驱动介绍以及制作虚拟网卡驱动(详解)

1.描述 网卡的驱动其实很简单,它还是与硬件相关,主要是负责收发网络的数据包,它将上层协议传递下来的数据包以特定的媒介访问控制方式进行发送, 并将接收到的数据包...

4647
来自专栏腾讯移动品质中心TMQ的专栏

win32应用程序性能测试-内存篇

本文主要讲述windows平台下应用程序性能测试的内存相关的知识,通过本文了解内存基本原理和分析内存占用问题。 一、内存是什么? 1内存分为物理内存和虚拟内存 ...

2108
来自专栏Golang语言社区

Go语言 Go的网络轮询及IO机制

简介 这篇介绍了Go的运行时系统——网络I/O部分。 阻塞 Go语言中,所有的I/O都是阻塞的,因此我们在写Go系统的时候要秉持一个思想:不要写阻塞的inter...

34112
来自专栏Java进阶干货

MySql常用30种SQL查询语句优化方法

1、应尽量避免在 where 子句中使用!=或<>操作符,否则将引擎放弃使用索引而进行全表扫描。

651
来自专栏乐沙弥的世界

关于 Private strand flush not complete

      网友发来告警日志,原本是关于一个死锁的情形,而另外的一个问题则是从redo log buffer写出到redo log file出现了不能分配新的日...

1033

扫码关注云+社区