块设备体系结构分析

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

1.1从字面上理解,块设备和字符设备最大的区别在于读写数据的基本单元不同。块设备读写数据的基本单元为块,例如磁盘通常为一个sector(扇区),而字符设备的基本单元为字节。所以Linux中块设备驱动往往为磁盘设备的驱动,但是由于磁盘设备的 IO性能与CPU相比很差,因此,块设备的数据流往往会引入文件系统的Cache机制。

1.2从实现角度来看,Linux为块设备和字符设备提供了两套机制。字符设备实现的比较简 单,内核例程和用户态API一一对应,用户层的Read函数直接对应了内核中的Read例程,这种映射关系由字符设备的file_operations维 护。块设备接口相对于字符设备复杂,read、write API没有直接到块设备层,而是直接到文件系统层,然后再由文件系统层发起读写请求。

2相关结构体

2.1 block_device_operations

      与字符设备驱动程序一样,块设备驱动程序也包含一个在<linux/fs.h>中定义的block_device_operations结构,其定义如下所示。

struct block_device_operations

{

    int (*open) (struct inode *, struct file *);

    int (*release) (struct inode *, struct file *);

    int (*ioctl) (struct inode *, struct file *, unsigned, unsigned long);

    long (*unlocked_ioctl) (struct file *, unsigned, unsigned long);

    long (*compat_ioctl) (struct file *, unsigned, unsigned long);

    int (*direct_access) (struct block_device *, sector_t, unsigned long *);

    int (*media_changed) (struct gendisk *);

    int (*revalidate_disk) (struct gendisk *);

    int (*getgeo)(struct block_device *, struct hd_geometry *);

    struct module *owner;

};

      从该结构的定义中,可以看出块设备并不提供read()、write()等函数接口。对块设备的读写请求都是以异步方式发送到设备相关的request 队列之中。

      关于block_device_operations,walfred曾在[快速上手Linux设备驱动]之一切皆是文件思想一文中有详细叙述。

2.2 gendisk

      一个块设备物理实体由一个gendisk结构体来表示(在</linux/genhd.h>中定义),每个gendisk可以支持多个分区。

      每个gendisk中包含了本物理实体的全部信息以及操作函数接口。整个块设备的注册过程是围绕gendisk来展开的。在驱动程序中需要初始化的gendisk的一些成员如下所示。

struct gendisk

{

    int major;            /* 主设备号 */

    int first_minor;    /* 第一个次设备号 */

    int minors;          /* 次设备号个数,一个块设备至少需要使用一个次设备号,而且块设

                        备的每个分区都需要一个次设备号,因此这个成员等于1,则表明该块

                        设备是不可被分区的,否则可以包含minors – 1    个分区。*/

    char disk_name[32];        /* 块设备名称,在/proc/partions中显示 */

    struct hd_struct **part;    /* 分区表 */

    struct block_device_operations *fops;        /* 块设备操作接口,与字符设备的

                                                 file_operations结构对应*/

    struct request_queue *queue;    /* I/O请求队列 */

    void *private_data;        /* 指向驱动程序私有数据 */

    sector_t capacity;    /* 块设备可包含的扇区数 */

    …… /* 其他省略 */

};

2.3 request_queue 和 request

      request和request_queue结构体:Linux块设备驱动中,使用request结构体来表征等待进行的IO请求;并用request_queue来表征一个块IO请求队列.两个结构体的定义如下:

request结构体

    struct request{

        struct list_head queuelist;

        unsigned long flags;

        sector_t sector;/*要传输的下一个扇区*/

        unsigned long nr_sectors;/*要传送的扇区数目*/

        unsigned int current_nr_sector;/*当前要传送的扇区*/

        sector_t hard_sector;/*要完成的下一个扇区*/

        unsigned long hard_nr_sectors;/*要被完成的扇区数目*/

        unsigned int hard_cur_sectors;/*当前要被完成的扇区数目*/

        struct bio* bio;/*请求的bio结构体的链表*/

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

        

        /*请求在屋里内存中占据的不连续的段的数目*/

        unsigned short nr_phys_segments;

        unsigned short nr_hw_segments;

        int tag;

        char* buffer;/*传送的缓冲区,内核的虚拟地址*/

        int ref_count;/*引用计数*/

        ...

    };

说明:

      request结构体的主要成员包括:

        sector_t hard_sector;/*要完成的下一个扇区*/

        unsigned long hard_nr_sectors;/*要被完成的扇区数目*/

        unsigned int hard_cur_sectors;/*当前要被完成的扇区数目*/

        /*

         * 上述三个成员依次是第一个尚未传输的扇区,尚待完成的扇区数,当前IO操作中待完成的扇区数

         * 但驱动中一般不会用到他们.而是下面的一组成员.

         */

        sector_t sector;/*要传输的下一个扇区*/

        unsigned long nr_sectors;/*要传送的扇区数目*/

        unsigned int current_nr_sector;/*当前要传送的扇区*/

        /* 

         * 这三个成员,以字节为单位.如果硬件的扇区大小不是512字节.如字节,则在开始对硬件进行操作之

         * 前,应先用4来除起始扇区号.前三个成员,与后三个成员的关系可以理解为"副本".

         */

 

 关于unsigned short nr_phys_segments:该成员表示相邻的页被合并后,这个请求在物理内存中的段的数目.如果该设备支持SG(分散/聚合,scatter/gather),可根据该字段申请sizeof(scatterlist*) nr_phys_segments的内存,并使用下面的函数进行DMA映射:

int blk_rq_map_sg(request_queue_t* q, struct request* rq, struct scatterlist *sg);

该函数与dma_map_sg()类似,返回scatterlist列表入口的数量.

      关于struct list_head queuelist:该成员用于链接这个请求到请求队列的链表结构,函数blkdev_ dequeue_request()可用于从队列中移除请求.宏rq_data_dir(struct request* req)可获得数据传送方向.返回0表示从设备读取,否则表示写向设备.

2.4 request_queue请求队列

    struct request_queue{

        ...

        /*自旋锁,保护队列结构体*/

        spinlock_t __queue_lock;

        spinlock_t* queue_lock;

        struct kobject kobj;/*队列kobject*/

        /*队列设置*/

        unsigned long nr_requests;/*最大的请求数量*/

        unsigned int  nr_congestion_on;

        unsigned int  nr_congestion_off;

        unsigned int  nr_batching;

        unsigned short max_sectors;/*最大扇区数*/

        unsigned short max_hw_sectors;

        unsigned short max_phys_sectors;/*最大的段数*/

        unsigned short max_hw_segments;

        unsigned short hardsect_size;/*硬件扇区尺寸*/

        unsigned int max_segment_size;/*最大的段尺寸*/

        unsigned long seg_boundary_mask;/*段边界掩码*/

        unsigned int dma_alignment;/*DMA传送内存对齐限制*/

        struct blk_queue_tag* queue_tags;

        atomic_t refcnt;/*引用计数*/

        unsigned int in_flight;

        unsigned int sg_timeout;

        unsigned int sg_reserved_size;

        int node;

        struct list_head drain_list;

        struct request* flush_rq;

        unsigned char ordered;

    };

      说明:请求队列跟踪等候的块IO请求,它存储用于描述这个设备能够支持的请求的类型信息,他们的最大大小,多少不同的段可以进入一个请求,硬件扇区大小,对齐要求等参数.其结果是:如果请求队列被配置正确了,它不会交给该设备一个不能处理的请求.

      请求队列还要实现一个插入接口,这个接口允许使用多个IO调度器,IO调度器以最优性能的方式向驱动提交IO请求.大部分IO调度器是积累批量的IO请求,并将其排列为递增/递减的块索引顺序后,提交给驱动.另外,IO调度器还负责合并邻近的请求,当一个新的IO请求被提交给调度器后,它会在队列里搜寻包含邻近的扇区的请求.如果找到一个,并且请求合理,调度器会将这两个请求合并.

2.5块I/O

      通常一个bio对应一个IO请求.IO调度算法可将连续的bio合并成一个请求.所以一个请求包含多个bio.

    struct bio{

        sector_t bi_sector;/*要传送的第一个扇区*/

        struct bio* bi_next;/*下一个bio*/

        struct block_device* bi_bdev;

        unsigned long bi_flags;

        /*如果是一个写请求,最低有效位被置位,可使用bio_data_dir(bio)宏来获取读写方向*/

        unsigned long bi_rw;/*地位表示R/W方向,高位表示优先级*/

        unsigned short bi_vcnt;/*bio_vec数量*/

        unsigned short bi_idx; /*当前bvl_vec索引*/

        unsigned short bi_phys_segments;/*不相邻的物理段的数目*/

        unsigned short bi_hw_segments;/*物理合并和DMA remap合并后不相邻的物理扇区*/

        unsigned int bi_size;

        /*被传送的数据大小(byte),用bio_sector(bio)获取扇区为单位的大小*/

        /*为了明了最大的hw尺寸,考虑bio中第一个和最后一个虚拟的可合并的段的尺寸*/

        unsigned int bi_hw_front_size;

        unsigned int bi_hw_back_size;

        unsigned int bi_max_vecs;/*能持有的最大bvl_vecs数*/

        struct bio_vec* bio_io_vec;/*实际的vec列表*/

        bio_end_io_t* bio_end_io;

        atomic_t bi_cnt;

        void* bi_private;

        bio_destructor_t* bi_destructor;

    };

      //结构体包含三个成员

    struct bio_vec{

        struct page* bv_page;//页指针

        unsigned int bv_len;//传送的字节数

        unsigned int bv_offset;//偏移位置

    };

 

/*一般不直接访问bio的bio_vec成员,而使用bio_for_each_segment()宏进行操作.

 *该宏循环遍历整个bio中的每个段.

 */

    #define __bio_for_each_segment(bvl, bio, i, start_idx)\

             for(

                bvl = bio_iovec_idx((bio),(start_idx)),i = (start_idx);\

                i <(bio)->bi_vcnt;\

                bvl++, i++\

             )

    #define bio_for_each_segment(bvl, bio, i)\

              __bio_for_each_segment(bvl, bio, i, (bio)->bi_idx)

      在内核中,提供了一组函数(宏)用于操作bio:

    int bio_data_dir(struct bio* bio);

    该函数用于获得数据传送方向.

    struct page* bio_page(struct bio* bio);

    该函数用于获得目前的页指针.

    int bio_offset(struct bio* bio);

      该函数返回操作对应的当前页的页内偏移,通常块IO操作本身就是页对齐的.

    int bio_cur_sectors(struct bio* bio);

      该函数返回当前bio_vec要传输的扇区数.

    char* bio_data(struct bio* bio);

      该函数返回数据缓冲区的内核虚拟地址.

    char* bvec_kmap_irq(struct bio_vec* bvec, unsigned long* offset);

    该函数也返回一个内核虚拟地址此地址可用于存取被给定的bio_vec入口指向的数据缓冲区.同时会屏蔽中断并返回一个原子kmap,因此,在此函数调用之前,驱动不应该是睡眠状态.

    void bvec_kunmap_irq(char* buffer, unsigned long flags);

    该函数撤销函数bvec_kmap_irq()创建的内存映射.

    char* bio_kmap_irq(struct bio* bio, unsigned long* flags);

      该函数是对bvec_kmap_irq函数的封装,它返回给定的比偶的当前bio_vec入口的映射.

    char* __bio_kmap_atomic(struct bio* bio, int i, enum km_type type);

    该函数是通过kmap_atomic()获得返回给定bio的第i个缓冲区的虚拟地址.

    void __bio_kunmap_atomic(char* addr, enum km_type type);

      该函数返还由函数__bio_kmap_atomic()获得的内核虚拟地址给系统.

    void bio_get(struct bio* bio);

    void bio_put(struct bio* bio);

      上面两个函数分别完成对bio的引用和引用释放.

      下图可以体现出bio/request/request_queue/bio_vec四个结构体之间的关系.

你可能感兴趣的:(linux,struct,IO,File,each,destructor)