您的位置:首页 > 运维架构 > Linux

linux设备驱动之块设备驱动程序的设计

2019-05-07 21:21 176 查看

文章目录

  • 块设备完整驱动程序
  • 块设备驱动测试
  • 数据访问流程
  • BIO
  • __make_request
  • 块设备

    块设备将数据存储在固定的块中,每个块的大小通常在512字节到32768字节之间磁盘、SD卡都是常见的块设备。

    块设备与字符设备的区别:

    • 在与读写数据的基本单元不同。块设备读写数据的基本单元为块,例如磁盘通常为一个 sector ,而字符设备的基本单元为字节。
    • 块设备能够随机访问,而字符设备则只能顺序访问。

    块设备体系结构

    体系结构层次

    1. 虚拟文件系统VFS
      VFS是对各种具体文件系统的一种封装,为用户程序访问文件提供统一的接口。

    2. Disk Cache
      当用户发起文件访问请求的时候,首先会到Disk Cache 中寻找文件是否被缓存了,如果在cache中,则直接从 cache 中读取。如果数据不在缓存中,就必须到具体的文件系统中读取数据了。

    3. Mapping layer
      (1)首先确定文件系统的 block size ,然后计算所请求的数据包含多少个block
      (2)调用具体文件系统的函数来访问文件的 inode,确定所请求的数据在磁盘上的逻辑块地址。

    4. Generic Block Layer
      Linux内核为块设备抽象了统一的模型,把块设备看作是由若干个扇区组成的数据空间。
      上层的读写请求在通用块层(Generic Block Layer)被构造成一个或多个bio结构。

    5. I/O Secheduler Layer
      I/O调度层负责将I/O操作排序,采用某种算法(如:电梯调度算法)来高效地处理操作。
      电梯调度算法:

    6. Block Device Driver
      块设备驱动程序通过发送命令给磁盘控制器实现真正的数据传输

    块设备驱动程序设计

    块设备描述

    Linux内核使用 struct gendisk (定义于 <linux/genhd.h>)来描述块设备。

    struct gendisk {
    int major;			/* 主设备号 */
    int first_minor;     /* 次设备号*/
    int minors;                     /* maximum number of minors, =1 for
    * disks that can't be partitioned. */
    
    char disk_name[DISK_NAME_LEN];	/* name of major driver */
    char *(*devnode)(struct gendisk *gd, mode_t *mode);
    
    unsigned int events;		/* supported events */
    unsigned int async_events;	/* async events, subset of all */
    
    /* Array of pointers to partitions indexed by partno.
    * Protected with matching bdev lock but stat and other
    * non-critical accesses use RCU.  Always access through
    * helpers.
    */
    struct disk_part_tbl __rcu *part_tbl;
    struct hd_struct part0;
    
    const struct block_device_operations *fops;
    struct request_queue *queue;   //请求队列
    void *private_data;
    
    int flags;
    struct device *driverfs_dev;  // FIXME: remove
    struct kobject *slave_dir;
    
    struct timer_rand_state *random;
    atomic_t sync_io;		/* RAID */
    struct disk_events *ev;
    #ifdef  CONFIG_BLK_DEV_INTEGRITY
    struct blk_integrity *integrity;
    #endif
    int node_id;
    };

    设备注册

    Linux 内核使用 add_disk 函数向内核注册块设备驱动。

    void add_disk(struct gendisk *gd)

    设备操作

    字符 设备通过file_operations 结构来定义使它所支持的操作,块设备使用一个类似的结构:struct block_device_operations.(<linux/blkdev.h>)

    struct block_device_operations {
    int (*open) (struct block_device *, fmode_t);
    int (*release) (struct gendisk *, fmode_t);
    int (*locked_ioctl) (struct block_device *, fmode_t, unsigned, unsigned long);
    int (*ioctl) (struct block_device *, fmode_t, unsigned, unsigned long);
    int (*compat_ioctl) (struct block_device *, fmode_t, unsigned, unsigned long);
    int (*direct_access) (struct block_device *, sector_t,
    void **, unsigned long *);
    int (*media_changed) (struct gendisk *);
    unsigned long long (*set_capacity) (struct gendisk *,
    unsigned long long);
    int (*revalidate_disk) (struct gendisk *);
    int (*getgeo)(struct block_device *, struct hd_geometry *);
    struct module *owner;
    };

    I/O请求

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

    struct request
    {
    struct list_head queuelist;   //链表结构
    sector_t sector;   //要操作的首个扇区
    unsigned long nr_sectors;  //要操作的扇区数目
    struct bio *bio;  //请求的bio结构体的链表
    struct bio *biotail;  //请求的bio结构体的链表尾
    ......
    };

    请求队列

    请求队列就是 I/O请求 request 所形成的队列,在Linux内核中 struct request_queue 描述。

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

    • 初始化请求队列,一般在块设备驱动的模块加载函数中调用。
    struct request_queue *blk_init_queue(request_fn_proc *rfn,spinlock_t *lock)
    • 清除请求队列,这个函数完成将请求队列返回给系统的任务,一般在块设备驱动模块卸载函数中调用。
    void blk_cleanup_queue(request_queue_t  *q)
    • 返回下一个要处理的请求(由I/O调度决定),如果没有请求则返回NULL。elv_next_request() 不会清除请求,它仍然将这个请求保留在队列上,因此连续调用它2次,2次返回同一个请求结构体。
    struct request *elv_next_request (request_queue_t *queue)
    • 从队列中删除1个请求
    void blkdev_dequeue_request (struct request *req)

    块设备完整驱动程序

    #include <linux/module.h>
    #include <linux/moduleparam.h>
    #include <linux/init.h>
    #include <linux/fs.h>
    #include <linux/sched.h>
    #include <linux/kernel.h>
    #include <linux/slab.h>
    #include <linux/errno.h>
    #include <linux/timer.h>
    #include <linux/types.h>
    #include <linux/fcntl.h>
    #include <linux/hdreg.h>
    //#include <linux/kdev.h>
    #include <linux/genhd.h>
    #include <linux/vmalloc.h>
    #include <linux/blkdev.h>
    #include <linux/buffer_head.h>
    #include <linux/bio.h>
    
    #define SIMP_BLKDEV_DEVICEMAJOR  COMPAQ_SMART2_MAJOR
    #define SIMP_BLKDEV_DISKNAME  "simp_blkdev"
    #define SIMP_BLKDEV_BYTES (16*1024*1024)
    
    static struct request_queue *simp_blkdev_queue;
    static struct gendisk *simp_blkdev_disk;
    unsigned char simp_blkdev_data[SIMP_BLKDEV_BYTES];
    
    static void simp_blk_dev_do_request(struct request_queue *q)
    {
    struct request *req;
    while ((req = blk_fetch_request(q)) != NULL)
    {
    if((blk_rq_pos(req) +blk_rq_cur_sectors(req)) << 9
    > SIMP_BLKDEV_BYTES)
    {
    printk(KERN_ERR SIMP_BLKDEV_DISKNAME ":bad reques
    20000
    t:block=%llu,count=%u\n",(unsigned long long)blk_rq_pos(req),blk_rq_cur_sectors(req));
    blk_end_request_all(req, 0);
    continue;
    }
    
    switch(rq_data_dir(req))
    {
    case READ:
    memcpy(req->buffer,simp_blkdev_data + (blk_rq_pos(req) << 9),blk_rq_cur_sectors(req) << 9);
    blk_end_request_all(req, 1);
    break;
    case WRITE:
    memcpy(simp_blkdev_data + (blk_rq_pos(req) << 9),req->buffer,blk_rq_cur_sectors(req) << 9);
    blk_end_request_all(req, 1);
    break;
    default:
    break;
    }
    }
    }
    
    struct block_device_operations simp_blkdev_fops =
    {
    .owner = THIS_MODULE,
    };
    
    static int __init simp_blkdev_init(void)
    {
    int ret;
    
    simp_blkdev_queue = blk_init_queue(simp_blk_dev_do_request,NULL);
    if(!simp_blkdev_queue)
    {
    ret = -ENOMEM;
    goto err_init_queue;
    }
    
    simp_blkdev_disk = alloc_disk(1);
    if(!simp_blkdev_disk)
    {
    ret = -ENOMEM;
    goto err_alloc_disk;
    }
    
    strcpy(simp_blkdev_disk->disk_name,SIMP_BLKDEV_DISKNAME);
    simp_blkdev_disk->major = SIMP_BLKDEV_DEVICEMAJOR;
    simp_blkdev_disk->first_minor = 0;
    simp_blkdev_disk->fops = &simp_blkdev_fops;
    simp_blkdev_disk->queue = simp_blkdev_queue;
    set_capacity(simp_blkdev_disk, SIMP_BLKDEV_BYTES>>9);
    add_disk(simp_blkdev_disk);
    
    return 0;
    
    err_alloc_disk:
    blk_cleanup_queue(simp_blkdev_queue);
    
    err_init_queue:
    return ret;
    }
    
    static void __exit simp_blkdev_exit(void)
    {
    del_gendisk(simp_blkdev_disk);
    put_disk(simp_blkdev_disk);
    blk_cleanup_queue(simp_blkdev_queue);
    }
    
    module_init(simp_blkdev_init);
    module_exit(simp_blkdev_exit);

    linux2.6.38 内核struct request成员中少了好多成员(或者是改成其他名字了),比如

    request -> sectors 变为 blk_rq_pos(request)

    request -> current_nr_sectors 变为 blk_rq_nr_sectors(request)

    __elev_next_request(request) 变为 blk_fetch_request(request_queue)

    end_request(request, error) 变为 blk_end_request_all(request, error)

    块设备驱动测试

    数据访问流程

    BIO

    一个struct bio 代表一个块设备I/O请求,I/O调度器可将连续的bio合并成一个请求 struct request

    struct bio
    {
    sector_t bi_sector;  //要访问的第一个扇区
    unsigned int bi_size;  //以字节为单位所需传输的数据大小
    struct bio_vec *bi_io_vec;  //实际的vec列表
    };
    struct bio_vec
    {
    struct page *bv_page;  //页指针
    unsigned int bv_len;  //传输的数据长度
    unsigned int bv_offset;  //偏移量
    };

    __make_request


    根据以上陈述,在不使用IO调度情况下的块设备驱动程序设计代码如下:

    #include <linux/module.h>
    #include <linux/moduleparam.h>
    #include <linux/init.h>
    #include <linux/fs.h>
    #include <linux/sched.h>
    #include <linux/kernel.h>
    #include <linux/slab.h>
    #include <linux/errno.h>
    #include <linux/timer.h>
    #include <linux/types.h>
    #include <linux/fcntl.h>
    #include <linux/hdreg.h>
    //#include <linux/kdev.h>
    #include <linux/genhd.h>
    #include <linux/vmalloc.h>
    #include <linux/blkdev.h>
    #include <linux/buffer_head.h>
    #include <linux/bio.h>
    #include <linux/version.h>
    
    #define SIMP_BLKDEV_DEVICEMAJOR  COMPAQ_SMART2_MAJOR
    #define SIMP_BLKDEV_DISKNAME  "simp_blkdev"
    #define SIMP_BLKDEV_BYTES (16*1024*1024)
    
    MODULE_AUTHOR("silence");
    MODULE_LICENSE("GPL");
    
    static struct request_queue *simp_blkdev_queue;
    static struct gendisk *simp_blkdev_disk;
    unsigned char simp_blkdev_data[SIMP_BLKDEV_BYTES];
    
    static int simp_blkdev_make_request(struct request_queue *q,struct bio *bio)
    {
    struct bio_vec *bvec;
    int i;
    void *dsk_mem;
    
    if((bio->bi_sector << 9) + bio->bi_size > SIMP_BLKDEV_BYTES)
    {
    printk(KERN_ERR SIMP_BLKDEV_DISKNAME ":bad request:block=%llu,count=%u\n",
    (unsigned long long)bio->bi_sector,bio->bi_size);
    
    #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,24)
    bio_endio(bio,0,-EIO);
    #else
    bio_endio(bio,-EIO);
    #endif
    return 0;
    }
    
    dsk_mem = simp_blkdev_data + (bio->bi_sector << 9);
    
    bio_for_each_segment(bvec, bio, i)
    {
    void *iovec_mem;
    
    switch(bio_rw(bio))
    {
    case READ:
    case READA:
    iovec_mem = kmap(bvec->bv_page) + bvec->bv_offset;
    memcpy(iovec_mem, dsk_mem,bvec->bv_len);
    kunmap(bvec->bv_page);
    break;
    case WRITE:
    iovec_mem = kmap(bvec->bv_page) + bvec->bv_offset;
    memcpy(dsk_mem,iovec_mem,bvec->bv_len);
    kunmap(bvec->bv_page);
    break;
    default:
    printk(KERN_ERR SIMP_BLKDEV_DISKNAME ": unknown value of bio_rw:%lu\n",bio_rw(bio));
    
    #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,24)
    bio_endio(bio,0,-EIO);
    #else
    bio_endio(bio,-EIO);
    #endif
    return 0;
    
    }
    dsk_mem += bvec->bv_len;
    }
    
    #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,24)
    bio_endio(bio,bio->bi_size,0);
    #else
    bio_endio(bio,0);
    #endif
    return 0;
    
    }
    
    struct block_device_operations simp_blkdev_fops =
    {
    .owner = THIS_MODULE,
    };
    
    static int __init simp_blkdev_init(void)
    {
    int ret;
    
    simp_blkdev_queue = blk_alloc_queue(GFP_KERNEL);
    if(!simp_blkdev_queue)
    {
    ret = -ENOMEM;
    goto err_alloc_queue;
    }
    
    blk_queue_make_request(simp_blkdev_queue,simp_blkdev_make_request);
    
    simp_blkdev_disk = alloc_disk(1);
    simp_blkdev_disk->major = SIMP_BLKDEV_DEVICEMAJOR;
    simp_blkdev_disk->first_minor = 0;
    simp_blkdev_disk->fops = &simp_blkdev_fops;
    simp_blkdev_disk->queue = simp_blkdev_queue;
    set_capacity(simp_blkdev_disk, SIMP_BLKDEV_BYTES>>9);
    add_disk(simp_blkdev_disk);
    
    return 0;
    
    err_alloc_disk:
    blk_cleanup_queue(simp_blkdev_queue);
    
    err_alloc_queue:
    return ret;
    }
    
    static void __exit simp_blkdev_exit(void)
    {
    del_gendisk(simp_blkdev_disk);
    put_disk(simp_blkdev_disk);
    blk_cleanup_queue(simp_blkdev_queue);
    }
    
    module_init(simp_blkdev_init);
    module_exit(simp_blkdev_exit);
    内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
    标签: 
    相关文章推荐