• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

Linux驱动_块设备驱动

武飞扬头像
anieoo
帮助1

        块设备是Linux驱动三大设备之一。与字符设备有很大的区别。块设备是针对存储设备的,比如 SD 卡、 EMMC、 NAND Flash、 Nor Flash、 SPI Flash、机械硬盘、固态硬盘等。因此块设备驱动其实就是这些存储设备驱动。

一、块设备和字符设备的区别

摘自Linux驱动设备开发详解(宋宝华)

学新通

二、块设备驱动结构

1、block_device_operations

        block_device_operations是和file_operations字符设备操作集类似的块设备操作集。它的具体定义如下:

  1.  
    struct block_device_operations {
  2.  
    int (*open) (struct block_device *, fmode_t);
  3.  
    void (*release) (struct gendisk *, fmode_t);
  4.  
    int (*rw_page)(struct block_device *, sector_t, struct page *, int rw);
  5.  
    int (*ioctl) (struct block_device *, fmode_t, unsigned, unsigned long);
  6.  
    int (*compat_ioctl) (struct block_device *, fmode_t, unsigned, unsigned long);
  7.  
    long (*direct_access)(struct block_device *, sector_t,
  8.  
    void **, unsigned long *pfn, long size);
  9.  
    unsigned int (*check_events) (struct gendisk *disk,
  10.  
    unsigned int clearing);
  11.  
    /* ->media_changed() is DEPRECATED, use ->check_events() instead */
  12.  
    int (*media_changed) (struct gendisk *);
  13.  
    void (*unlock_native_capacity) (struct gendisk *);
  14.  
    int (*revalidate_disk) (struct gendisk *);
  15.  
    int (*getgeo)(struct block_device *, struct hd_geometry *);
  16.  
    /* this callback is with swap_lock and sometimes page table lock held */
  17.  
    void (*swap_slot_free_notify) (struct block_device *, unsigned long);
  18.  
    struct module *owner;
  19.  
    };
学新通

 主要用到的的函数: 

        open:当块设备打开的时候会调用此函数
        release:当块设备关闭的时候会调用此函数
        getgeo:此函数用来根据驱动器的几何信息填充一个hd_geometry结构体,该结构体包含磁头、扇区、柱面等信息。 

2、gendisk结构体

        Linux内核使用gendisk结构体描述一个独立的磁盘,该结构体的定义如下:

  1.  
    struct gendisk {
  2.  
    /* major, first_minor and minors are input parameters only,
  3.  
    * don't use directly. Use disk_devt() and disk_max_parts().
  4.  
    */
  5.  
    int major; /* major number of driver */
  6.  
    int first_minor;
  7.  
    int minors; /* maximum number of minors, =1 for
  8.  
    * disks that can't be partitioned. */
  9.  
     
  10.  
    char disk_name[DISK_NAME_LEN]; /* name of major driver */
  11.  
    char *(*devnode)(struct gendisk *gd, umode_t *mode);
  12.  
     
  13.  
    unsigned int events; /* supported events */
  14.  
    unsigned int async_events; /* async events, subset of all */
  15.  
     
  16.  
    /* Array of pointers to partitions indexed by partno.
  17.  
    * Protected with matching bdev lock but stat and other
  18.  
    * non-critical accesses use RCU. Always access through
  19.  
    * helpers.
  20.  
    */
  21.  
    struct disk_part_tbl __rcu *part_tbl;
  22.  
    struct hd_struct part0;
  23.  
     
  24.  
    const struct block_device_operations *fops;
  25.  
    struct request_queue *queue;
  26.  
    void *private_data;
  27.  
     
  28.  
    int flags;
  29.  
    struct device *driverfs_dev; // FIXME: remove
  30.  
    struct kobject *slave_dir;
  31.  
     
  32.  
    struct timer_rand_state *random;
  33.  
    atomic_t sync_io; /* RAID */
  34.  
    struct disk_events *ev;
  35.  
    #ifdef CONFIG_BLK_DEV_INTEGRITY
  36.  
    struct blk_integrity *integrity;
  37.  
    #endif
  38.  
    int node_id;
  39.  
    };
学新通

        major:为磁盘设备的主设备号。
        first_minor:为磁盘的第一个次设备号。
        minors:为磁盘的次设备号数量,也就是磁盘的分区数量,这些分区的主设备号一
样, 次设备号不同。
        part_tbl:为磁盘对应的分区表,为结构体 disk_part_tbl 类型, disk_part_tbl 的核心是一个 hd_struct 结构体指针数组,此数组每一项都对应一个分区信息。
        part0:disk->part_tbl->part[0] = &part0;
        fops:块设备操作集,为 block_device_operations 结构体类型。
        queue:磁盘对应的请求队列,所以针对该磁盘设备的请求都放到此队列中,驱动程序需要处理此队列中的所有请求

3、request_queue、request、bio

     request_queue直译为请求队列,定义在gendisk中,因此每个块设备都对应于有一个请求队列。而请求队列中包含很多request直译为请求队列项。请求队列项又由一个或者多个bio结构体组成。

        上层应用程序对于块设备的读写会被构造成一个或多个 bio 结构, bio 结构描述了要读写的起始扇区、要读写的扇区数量、是读取还是写入、页偏移、数据长度等等信息。上层会将 bio 提交给 I/O 调度器, I/O 调度器会将这些 bio 构造成 request 结构,而一个物理存储设备对应一个request_queue,request_queue 里面顺序存放着一系列的 request。新产生的 bio 可能被合并到 request_queue 里现有的 request 中,也可能产生新的 request,然后插入到 request_queue 中合适的位置,这一切都是由 I/O 调度器来完成的。 request_queue、 request 和 bio 之间的关系如图:

学新通

         bio结构体的定义如下:

  1.  
    struct bio {
  2.  
    struct bio *bi_next; /* request queue link */
  3.  
    struct block_device *bi_bdev;
  4.  
    unsigned long bi_flags; /* status, command, etc */
  5.  
    unsigned long bi_rw; /* bottom bits READ/WRITE,
  6.  
    * top bits priority
  7.  
    */
  8.  
     
  9.  
    struct bvec_iter bi_iter;
  10.  
     
  11.  
    /* Number of segments in this BIO after
  12.  
    * physical address coalescing is performed.
  13.  
    */
  14.  
    unsigned int bi_phys_segments;
  15.  
     
  16.  
    /*
  17.  
    * To keep track of the max segment size, we account for the
  18.  
    * sizes of the first and last mergeable segments in this bio.
  19.  
    */
  20.  
    unsigned int bi_seg_front_size;
  21.  
    unsigned int bi_seg_back_size;
  22.  
     
  23.  
    atomic_t bi_remaining;
  24.  
     
  25.  
    bio_end_io_t *bi_end_io;
  26.  
     
  27.  
    void *bi_private;
  28.  
    #ifdef CONFIG_BLK_CGROUP
  29.  
    /*
  30.  
    * Optional ioc and css associated with this bio. Put on bio
  31.  
    * release. Read comment on top of bio_associate_current().
  32.  
    */
  33.  
    struct io_context *bi_ioc;
  34.  
    struct cgroup_subsys_state *bi_css;
  35.  
    #endif
  36.  
    union {
  37.  
    #if defined(CONFIG_BLK_DEV_INTEGRITY)
  38.  
    struct bio_integrity_payload *bi_integrity; /* data integrity */
  39.  
    #endif
  40.  
    };
  41.  
     
  42.  
    unsigned short bi_vcnt; /* how many bio_vec's */
  43.  
     
  44.  
    /*
  45.  
    * Everything starting with bi_max_vecs will be preserved by bio_reset()
  46.  
    */
  47.  
     
  48.  
    unsigned short bi_max_vecs; /* max bvl_vecs we can hold */
  49.  
     
  50.  
    atomic_t bi_cnt; /* pin count */
  51.  
     
  52.  
    struct bio_vec *bi_io_vec; /* the actual vec list */
  53.  
     
  54.  
    struct bio_set *bi_pool;
  55.  
     
  56.  
    /*
  57.  
    * We can inline a number of vecs at the end of the bio, to avoid
  58.  
    * double allocations for a small number of bio_vecs. This member
  59.  
    * MUST obviously be kept at the very end of the bio.
  60.  
    */
  61.  
    struct bio_vec bi_inline_vecs[0];
  62.  
    };
学新通

        主要的成员变量如下:

bvec_iter:描述了要操作的设备扇区等信息,其结构体定义如下:

  1.  
    struct bvec_iter {
  2.  
    sector_t bi_sector; /* I/O 请求的设备起始扇区(512 字节) */
  3.  
    unsigned int bi_size; /* 剩余的 I/O 数量 */
  4.  
    unsigned int bi_idx; /* blv_vec 中当前索引 */
  5.  
    unsigned int bi_bvec_done; /* 当前 bvec 中已经处理完成的字节数 */
  6.  
    };

bio_vec:page 指定了所在的物理页, offset 表示所处页的偏移地址, len 就是数据长度。

  1.  
    struct bio_vec {
  2.  
    struct page *bv_page; /* 页 */
  3.  
    unsigned int bv_len; /* 长度 */
  4.  
    unsigned int bv_offset; /* 偏移 */
  5.  
    };

        其中 bi_iter 这个结构体成员变量就用于描述物理存储设备地址信息,比如要操作的扇
区地址。 bi_io_vec 指向 bio_vec 数组首地址, bio_vec 数组就是 RAM 信息,比如页地址、页偏
移以及长度。

三、块设备驱动编写函数

        1、注册和注销块设备

int register_blkdev(unsigned int major, const char *name)
void unregister_blkdev(unsigned int major, const char *name)

        major: 主设备号。
        name: 块设备名字。

        2、gendisk

struct gendisk *alloc_disk(int minors)    //        注册gendisk 
void del_gendisk(struct gendisk *gp)    //注销gendisk
void add_disk(struct gendisk *disk)    //向内核添加gendisk
void set_capacity(struct gendisk *disk, sector_t size)    //设置gendisk容量

        3、请求队列 request_queue

申请并初始化:

  1.  
    request_queue *blk_init_queue(request_fn_proc *rfn, spinlock_t *lock)
  2.  
    //初始化请求队列,并绑定rfn请求处理函数

        rfn: 请求处理函数指针,每个 request_queue 都要有一个请求处理函数,请求处理函数
request_fn_proc 原型如下:

void (request_fn_proc) (struct request_queue *q)

        请求处理函数需要驱动编写人员自行实现。
        lock: 自旋锁指针,需要驱动编写人员定义一个自旋锁,然后传递进来。,请求队列会使用
这个自旋锁。

删除请求队列:

void blk_cleanup_queue(struct request_queue *q)

分配请求队列并绑定制造请求函数:

        blk_init_queue 函数完成了请求队列的申请已经请求处理函数的绑定,这个一般用于像机械
硬盘这样的存储设备,需要 I/O 调度器来优化数据读写过程。但是对于 EMMC、 SD 卡这样的
非机械设备,可以进行完全随机访问,所以就不需要复杂的 I/O 调度器了。对于非机械设备可以先申请 request_queue,然后将申请到的 request_queue 与“制造请求”函数绑定在一起。

        分配一个request_queue

struct request_queue *blk_alloc_queue(gfp_t gfp_mask)

        将request_queue和制造请求函数绑定 、

void blk_queue_make_request(struct request_queue *q, make_request_fn *mfn)

        mfn:制造请求函数,该函数需要自己编写,函数原型如下:

void (make_request_fn) (struct request_queue *q, struct bio *bio)

四、 使用blk_init_queue进行试验

        本实验利用开发板上的RAM模拟一段内存,编写块设备驱动,代码如下:

  1.  
    #include <linux/types.h>
  2.  
    #include <linux/kernel.h>
  3.  
    #include <linux/delay.h>
  4.  
    #include <linux/ide.h>
  5.  
    #include <linux/init.h>
  6.  
    #include <linux/module.h>
  7.  
    #include <linux/errno.h>
  8.  
    #include <linux/gpio.h>
  9.  
    #include <linux/cdev.h>
  10.  
    #include <linux/device.h>
  11.  
    #include <linux/of_gpio.h>
  12.  
    #include <linux/semaphore.h>
  13.  
    #include <linux/timer.h>
  14.  
    #include <linux/i2c.h>
  15.  
    #include <linux/genhd.h>
  16.  
    #include <linux/blkdev.h>
  17.  
    #include <linux/hdreg.h>
  18.  
    #include <asm/mach/map.h>
  19.  
    #include <asm/uaccess.h>
  20.  
    #include <asm/io.h>
  21.  
     
  22.  
     
  23.  
    /*定义磁盘大小*/
  24.  
    #define RAMDISK_SIZE (2 * 1024 * 1024) /*大小2MB*/
  25.  
    #define RAMDISK_NAME "ramdisk"
  26.  
    #define RAMDISK_MINOR 3 /*磁盘分区*/
  27.  
     
  28.  
    struct ramdisk_dev {
  29.  
    int major; /* major主设备号*/
  30.  
    unsigned char *ramdiskbuffer; /*ramdisk内存空间,用来模拟块设备*/
  31.  
    spinlock_t spinlock; /*spinlock自旋锁*/
  32.  
    struct gendisk *gendisk; /*gendisk*/
  33.  
    struct request_queue *queue; /*request_queue请求队列*/
  34.  
    };
  35.  
     
  36.  
    struct ramdisk_dev ramdisk; /*定义一个ramdisk设备*/
  37.  
     
  38.  
     
  39.  
    /*块设备操作函数集合*/
  40.  
    int ramdisk_open(struct block_device *dev, fmode_t mode)
  41.  
    {
  42.  
    printk("ramdisk open\r\n");
  43.  
    return 0;
  44.  
    }
  45.  
     
  46.  
    void ramdisk_release(struct gendisk *disk, fmode_t mode)
  47.  
    {
  48.  
    printk("ramdisk release\r\n");
  49.  
    }
  50.  
     
  51.  
     
  52.  
    int ramdisk_getgeo(struct block_device *dev, struct hd_geometry *geo)
  53.  
    {
  54.  
    /* 这是相对于机械硬盘的概念 */
  55.  
    geo->heads = 2; /* 磁头 */
  56.  
    geo->cylinders = 32; /* 柱面 */
  57.  
    geo->sectors = RAMDISK_SIZE / (2 * 32 *512); /* 一个磁道上的扇区数量 */
  58.  
    return 0;
  59.  
    }
  60.  
     
  61.  
     
  62.  
    static struct block_device_operations ramdisk_fops =
  63.  
    {
  64.  
    .owner = THIS_MODULE,
  65.  
    .open = ramdisk_open,
  66.  
    .release = ramdisk_release,
  67.  
    .getgeo = ramdisk_getgeo,
  68.  
    };
  69.  
     
  70.  
    /*
  71.  
    * @description : 处理传输过程
  72.  
    * @param-req : 请求
  73.  
    * @return : 无
  74.  
    */
  75.  
    static void ramdisk_transfer(struct request *req)
  76.  
    {
  77.  
    unsigned long start = blk_rq_pos(req) << 9; /* blk_rq_pos获取到的是扇区地址,左移9位转换为字节地址 */
  78.  
    unsigned long len = blk_rq_cur_bytes(req); /* 大小 */
  79.  
     
  80.  
    /* bio中的数据缓冲区
  81.  
    * 读:从磁盘读取到的数据存放到buffer中
  82.  
    * 写:buffer保存这要写入磁盘的数据
  83.  
    */
  84.  
    void *buffer = bio_data(req->bio);
  85.  
     
  86.  
    if(rq_data_dir(req) == READ) /* 读数据 */
  87.  
    memcpy(buffer, ramdisk.ramdiskbuffer start, len);
  88.  
    else if(rq_data_dir(req) == WRITE) /* 写数据 */
  89.  
    memcpy(ramdisk.ramdiskbuffer start, buffer, len);
  90.  
     
  91.  
    }
  92.  
     
  93.  
    /*
  94.  
    * @description : 请求处理函数
  95.  
    * @param-q : 请求队列
  96.  
    * @return : 无
  97.  
    */
  98.  
    void ramdisk_request_fn(struct request_queue *q)
  99.  
    {
  100.  
    int err = 0;
  101.  
    struct request *req;
  102.  
     
  103.  
    /* 循环处理请求队列中的每个请求 */
  104.  
    req = blk_fetch_request(q);
  105.  
    while(req != NULL) {
  106.  
     
  107.  
    /* 针对请求做具体的传输处理 */
  108.  
    ramdisk_transfer(req);
  109.  
     
  110.  
    /* 判断是否为最后一个请求,如果不是的话就获取下一个请求
  111.  
    * 循环处理完请求队列中的所有请求。
  112.  
    */
  113.  
    if (!__blk_end_request_cur(req, err))
  114.  
    req = blk_fetch_request(q);
  115.  
    }
  116.  
    }
  117.  
     
  118.  
     
  119.  
    static int __init ramdisk_init(void)
  120.  
    {
  121.  
    int ret = 0;
  122.  
    printk("ramdisk init\r\n");
  123.  
     
  124.  
    /*1、给准备模拟块设备的内存申请空间*/
  125.  
    ramdisk.ramdiskbuffer = kzalloc(RAMDISK_SIZE, GFP_KERNEL);
  126.  
    if(ramdisk.ramdiskbuffer == NULL) { /*内存申请失败*/
  127.  
    ret = -EINVAL;
  128.  
    goto failed_ram;
  129.  
    }
  130.  
     
  131.  
    /*2、初始化自旋锁*/
  132.  
    spin_lock_init(&ramdisk.spinlock);
  133.  
     
  134.  
    /*3、注册块设备*/
  135.  
    ramdisk.major = register_blkdev(0, RAMDISK_NAME); /*自动分配设备号*/
  136.  
    if(ramdisk.major < 0) { /*块设备注册失败*/
  137.  
    goto failed_register;
  138.  
    }
  139.  
     
  140.  
    /*4、分配初始化gendisk*/
  141.  
    ramdisk.gendisk = alloc_disk(RAMDISK_MINOR); /*初始化3个分区*/
  142.  
    if(!ramdisk.gendisk) { /*初始化gendisk失败*/
  143.  
    ret = -EINVAL;
  144.  
    goto failed_gendisk;
  145.  
    }
  146.  
     
  147.  
    /*5、分配并初始化请求队列*/
  148.  
    ramdisk.queue = blk_init_queue(&ramdisk_request_fn, &ramdisk.spinlock);
  149.  
    if(!ramdisk.queue) {
  150.  
    ret = -EINVAL;
  151.  
    goto failed_queue;
  152.  
    }
  153.  
     
  154.  
    /*6、注册添加gendisk*/
  155.  
    ramdisk.gendisk->major = ramdisk.major; /*主设备号*/
  156.  
    ramdisk.gendisk->first_minor = 0; /*起始次设备号*/
  157.  
    ramdisk.gendisk->fops = &ramdisk_fops; /*块设备操作函数*/
  158.  
    ramdisk.gendisk->private_data = &ramdisk; /* 私有数据 */
  159.  
    ramdisk.gendisk->queue = ramdisk.queue; /* 请求队列 */
  160.  
    sprintf(ramdisk.gendisk->disk_name, RAMDISK_NAME);/* 名字 */
  161.  
    set_capacity(ramdisk.gendisk, RAMDISK_SIZE/512); /* 设备容量(单位为扇区)*/
  162.  
     
  163.  
    add_disk(ramdisk.gendisk);
  164.  
     
  165.  
    return 0;
  166.  
    failed_queue:
  167.  
    put_disk(ramdisk.gendisk);
  168.  
    failed_gendisk:
  169.  
    unregister_blkdev(ramdisk.major, RAMDISK_NAME);
  170.  
    failed_register:
  171.  
    kfree(ramdisk.ramdiskbuffer);
  172.  
    failed_ram:
  173.  
    return ret;
  174.  
    }
  175.  
     
  176.  
     
  177.  
    static void __exit ramdisk_exit(void)
  178.  
    {
  179.  
    printk("ramdisk exit\r\n");
  180.  
     
  181.  
    }
  182.  
     
  183.  
    module_init(ramdisk_init);
  184.  
    module_exit(ramdisk_exit);
  185.  
    MODULE_LICENSE("GPL");
  186.  
    MODULE_AUTHOR("ZYC");
  187.  
     
学新通

        实验验证: 

学新通

        可以看出, ramdisk 已经识别出来了,大小为 2MB,但是同时也提示/dev/ramdisk
没有分区表,因为我们还没有格式化/dev/ramdisk。

     格式化/dev/ramdisk

mkfs.vfat /dev/ramdisk

     格式化完成后如下图所示: 

学新通

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhfhchbb
系列文章
更多 icon
同类精品
更多 icon
继续加载