Chinaunix首页 | 论坛 | 博客
  • 博客访问: 192463
  • 博文数量: 41
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 131
  • 用 户 组: 普通用户
  • 注册时间: 2013-09-03 20:39
文章分类

全部博文(41)

文章存档

2019年(2)

2018年(1)

2014年(19)

2013年(19)

分类: LINUX

2013-09-12 09:45:58

原文地址:块设备驱动程序设计 作者:特殊借口



一、块设备简介

1>块设备
块设备将数据存储在固定大小的块中,每个块的大小通常为512字节到32768字节之间。磁盘、SD卡都是常见的块设备。
2>块设备&字符设备
块设备和字符设备最大的区别在于读写数据的基本单元不同。块设备读写数据的基本单元为块,例如磁盘通常为一个sector,而字符设备的基本单元为字节。
块设备能够随机访问,而字符设备则只能顺序访问。
3>体系架构
VFS:对各种具体文件系统的一种封装,为用户程序访问文件提供统一的接口。

Disk Cache
当用户发起文件访问请求的时候,首先会到Disk Cache中寻找文件是否被缓存了,如果在cache中,则直
接从cache中读取。如果数据不在缓存中,就必须要到具体的文件系统中读取数据了。
mapping layer:首先确定文件系统的block size,然后计算所请求的数据包含多少个block。调用具体文件

系统的函数来访问文件的inode,确定所请求的数据再磁盘上的逻辑块地址。
generic block layer:Linux内核为块设备抽象了统一的模型,把块设备看作是由若干个扇区组成的数据 

空间。上层的读写请求在通用块层(generic block layer)被构造成一个或多个bio结构。
I/O scheduler layer:I/O调度层负责将I/O操作进行排序,采用某种算法(如:电梯调度算法)来高效

地处理操作。
电梯调度算法原则:如果电梯现在朝上运动,如果当前楼层的上下方都有请求,则先响应所有上方的请求,然后向

下响应下方的请求;如果电梯向下运动,则相反。
block device driver:块设备驱动程序通过发送命令给磁盘控制器实现真正的数据传输。

二、块设备驱动程序的设计

1>设备描述

    Linux内核使用struct gendisk(定义于)来描述块设备。

struct gendisk{
    int major;
    int first_minor;
    int minor;
    char disk_name[DISK_NAME_LEN];
    struct block_device_operation *fops;
    struct request_queue *queue;
    ..........................
    int node_id;
};

2>设备注册

    Linux内核使用add_disk函数想内核注册块设备。
    void add_disk(struct gendisk *gd)

3>设备操作

   字符设备通过file_operations结构来定义使它所支持的操作,块设备使用一个类似的结构:

struct_device_operations{
    int(*open)(struct block_device*,fmode_t);
    int(*release)(struct gendisk*,fmode_t);
    int(*ioctl)(struct block_device*,fmode_t,unsigned,unsigned long);
    .....................................
} ;

4>IO请求

    在Linux内核中,使用struct request来表示等待处理的块设备IO请求

struct request
{
    struct list_head queuelist;//链表结构
    sector_t sector;//要操作的首个扇区
    unsigned long nr_sector;//要操作的扇区数目

    struct bio *bio;//请求的bio结构体的链表
    struct bio *biotail;//请求的bio结构体的链表尾
..............................................
};

5 >请求队列

    简单的讲,请求队列就是IO请求request所形成的队列,在Linux内核中struct request_queue描述。

内核提供了一系列函数用来操作请求队列:

struct request_queue *blk_init_queue(request_fn_proc *rfn,spinlock_t *lock)
初始化请求队列,一般在块设备驱动的模块加载函数中调用。

void blk_clean_queue(request_queue_t* q)
清除请求队列,这个函数完成将请求队列返回给系统的任务,一般在块设备驱动模块卸载函数中调用。

struct request *elv_next_request(request_queue_t *queue)
返回下一个要处理的请求(由IO调度器决定),如果没有请求则返回NULL。elv_next_request()不会清除请求,它任然将这个请求保留在队列上,因此连续调用它2次,2次会返回同一个请求结构体。

void blkdev_dequeue_request(struct request *req)
从队列中删除一个请求。


实例代码:


点击(此处)折叠或打开

  1. #include <linux/module.h>
  2. #include <linux/moduleparam.h>
  3. #include <linux/init.h>
  4. #include <linux/sched.h>
  5. #include <linux/kernel.h>    /* printk() */
  6. #include <linux/slab.h>        /* kmalloc() */
  7. #include <linux/fs.h>        /* everything... */
  8. #include <linux/errno.h>    /* error codes */
  9. #include <linux/timer.h>
  10. #include <linux/types.h>    /* size_t */
  11. #include <linux/fcntl.h>    /* O_ACCMODE */
  12. #include <linux/hdreg.h>    /* HDIO_GETGEO */
  13. #include <linux/kdev_t.h>
  14. #include <linux/vmalloc.h>
  15. #include <linux/genhd.h>
  16. #include <linux/blkdev.h>
  17. #include <linux/buffer_head.h>    /* invalidate_bdev */
  18. #include <linux/bio.h>
  19. #include <linux/major.h>



  20. #define SIMP_BLKDEV_DEVICEMAJOR COMPAQ_SMART2_MAJOR
  21. #define SIMP_BLKDEV_DISKNAME "simp_blkdev"
  22. #define SIMP_BLKDEV_BYTES (16*1024*1024)

  23. static struct request_queue *simp_blkdev_queue;
  24. static struct gendisk *simp_blkdev_disk;
  25. unsigned char simp_blkdev_data[SIMP_BLKDEV_BYTES];

  26. static int simp_blkdev_make_request(struct request_queue *q, struct bio *bio)
  27. {
  28.         struct bio_vec *bvec;
  29.         int i;
  30.         void *dsk_mem;

  31.         if ((bio->bi_sector << 9) + bio->bi_size > SIMP_BLKDEV_BYTES) {
  32.                 printk(KERN_ERR SIMP_BLKDEV_DISKNAME
  33.                         ": bad request: block=%llu, count=%u\n",
  34.                         (unsigned long long)bio->bi_sector, bio->bi_size);

  35.                 bio_endio(bio, -EIO);
  36.                 return 0;
  37.         }

  38.         dsk_mem = simp_blkdev_data + (bio->bi_sector << 9);

  39.         bio_for_each_segment(bvec, bio, i) {
  40.                 void *iovec_mem;

  41.                 switch (bio_rw(bio)) {
  42.                 case READ:
  43.                 case READA:
  44.                         iovec_mem = kmap(bvec->bv_page) + bvec->bv_offset;
  45.                         memcpy(iovec_mem, dsk_mem, bvec->bv_len);
  46.                         kunmap(bvec->bv_page);
  47.                         break;
  48.                 case WRITE:
  49.                         iovec_mem = kmap(bvec->bv_page) + bvec->bv_offset;
  50.                         memcpy(dsk_mem, iovec_mem, bvec->bv_len);
  51.                         kunmap(bvec->bv_page);
  52.                         break;
  53.                 default:
  54.                         printk(KERN_ERR SIMP_BLKDEV_DISKNAME
  55.                                 ": unknown value of bio_rw: %lu\n",
  56.                                 bio_rw(bio));

  57.                         bio_endio(bio, -EIO);
  58.                         return 0;
  59.                 }
  60.                 dsk_mem += bvec->bv_len;
  61.         }


  62.         bio_endio(bio, 0);


  63.         return 0;
  64. }


  65. struct block_device_operations simp_blkdev_fops = {
  66.         .owner = THIS_MODULE,
  67. };

  68. static int __init simp_blkdev_init(void)
  69. {
  70.         int ret;

  71.         simp_blkdev_queue = blk_alloc_queue(GFP_KERNEL);
  72.         if (!simp_blkdev_queue) {
  73.                 ret = -ENOMEM;
  74.                 goto err_alloc_queue;
  75.         }
  76.         blk_queue_make_request(simp_blkdev_queue, simp_blkdev_make_request);

  77.         simp_blkdev_disk = alloc_disk(1);
  78.         if (!simp_blkdev_disk) {
  79.                 ret = -ENOMEM;
  80.                 goto err_alloc_disk;
  81.         }

  82.         strcpy(simp_blkdev_disk->disk_name, SIMP_BLKDEV_DISKNAME);
  83.         simp_blkdev_disk->major = SIMP_BLKDEV_DEVICEMAJOR;
  84.         simp_blkdev_disk->first_minor = 0;
  85.         simp_blkdev_disk->fops = &simp_blkdev_fops;
  86.         simp_blkdev_disk->queue = simp_blkdev_queue;
  87.         set_capacity(simp_blkdev_disk, SIMP_BLKDEV_BYTES>>9);
  88.         add_disk(simp_blkdev_disk);

  89.         return 0;

  90. err_alloc_disk:
  91.         blk_cleanup_queue(simp_blkdev_queue);
  92. err_alloc_queue:
  93.         return ret;
  94. }


  95. static void __exit simp_blkdev_exit(void)
  96. {
  97.         del_gendisk(simp_blkdev_disk);
  98.         put_disk(simp_blkdev_disk);
  99.         blk_cleanup_queue(simp_blkdev_queue);
  100. }

  101. module_init(simp_blkdev_init);
  102. module_exit(simp_blkdev_exit);




三、不使用IO调度器的程序实现


数据访问流程

generic_make_request()

__generic_make_request(bio)

q->make_request_fn(q,bio)

struct request_queue
{
    ...
    make_request_fn *make_request_fn;
    ...
}

struct request_queue *blk_init_queue(request_fn_proc *rfn,spinlock_t *lock)
{
    
    return blk_init_queue_node(rfn,lock,-1);
}

blk_init_queue_node(request_fn_proc *rfn,spinlock_t *lock,int node_id)
{
    ....................................................
    blk_queue_make_request(q,__make_request);
    ......
}

void blk_queue_make_request(struct_queue *q,make_request_fn *mfn)
{
    ......
q->make_request_fn = mfn;

    ........
}

BIO

1个struct bio代表1次块设备IO请求,IO调度器可将连续的bio合并成1个请求struct request。

struct bio
{
    sector_t bi_sector;//要访问的第一个扇区
    unsigned int bi_size;//以字节为单位所需传输的数据大小
    struct bio_vec *bi_io_vec;//实际的vec列表
............................
}


bi_io_vec

struct bio_vec{
    struct page *bv_page;//页指针
    unsigned int bv_len;//传输的数据长度
    unsigned int bv_offset;//偏移量
};

__make_request

在__make_request函数中,使用了IO调度器(elecator)将多个bio的访问顺序进行优化,调整,合并为一个

request,然后提交给用户指定的函数处理。但是对于ramdisk,U盘,记忆棒之类的设备,并不存在磁盘所面临的

寻道时间。因此对这样的“块设备”而言,一个IO调度器不但发挥不了作用,反而其本身将白白耗掉不少内存和

CPU。


解决办法:驱动程序自己实现request_queue所需要的make_request_fn函数,不使用__make_request.

1、请求队列

request_queue_t *blk_alloc_queue(int gfp_mask)

    分配“请求队列”,对于FLASH、RAM盘等完全随机访问的非机械设备,并不需要进行复杂的IO调度,这个时候,应该使用上述函数分配1个“请求队列”

void blk_queue_make_request(request_queue_t* q,make_request_fn *mfn)
绑定“请求队列”和"制造请求"函数

块设备驱动测试

1>insmod simple - blk.ko

2>ls /dev/simp_blkdev

3>mkfs.ext3 /dev/simp_blkdev

4>mkdir -p /mnt/blk

5> mount

6>cp /etc/init.d*/*    /mnt/blk

7>ls /mnt/blk

8>umount /mnt/blk

9>ls /mnt/blk

阅读(1658) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~