Chinaunix首页 | 论坛 | 博客
  • 博客访问: 111821
  • 博文数量: 19
  • 博客积分: 1716
  • 博客等级: 上尉
  • 技术积分: 275
  • 用 户 组: 普通用户
  • 注册时间: 2010-02-25 14:03
文章分类

全部博文(19)

文章存档

2011年(8)

2010年(11)

我的朋友

分类: LINUX

2010-04-27 20:47:52

4.requestbio结构体

1)请求request

requestrequest_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)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请求被提交给调度器后,它会在队列里搜寻包含邻近的扇区的请求.如果找到一个,并且请求合理,调度器会将这两个请求合并.

Linux2.6的四个IO调度器,他们分别是No-op/Anticipatory/Deadline/CFQ IO scheduler.

关于request_queu结构体的操作:

    //初始化请求队列

    kernel elevator = deadline;/*kernel添加启动参数*/

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

        /*

         * 两个参数分别是请求处理函数指针和控制队列访问权限的自旋锁.

         * 此函数会发生内存分配的行为,需要检查其返回值.一般在加载函数中调用.

         */

    //清除请求队列

    void blk_cleanup_queue(request_queue_t* q);

        /* 

         * 此函数完成将请求队列返回给系统的任务,一般在卸载函数中调用.

         * 此函数即bld_put_queue()的宏定义#define blk_put_queue(q) blk_cleanup_queue((q))

         */

    //分配"请求队列"

    request_queue_t* blk_alloc_queue(int gfp_mask);

    void blk_queue_make_request(request_queue_t* q, make_request_fn* mfn);

        /*

         * 前一个函数用于分配一个请求队列,后一个函数是将请求队列和"制造函数"进行绑定

         * 但函数blk_alloc_queue实际上并不包含任何请求.

         */

    //提取请求

    struct request* elv_next_request(request_queue_t* q);

    //去除请求

    void blkdev_dequeue_request(struct request* req);

    void elv_requeue_request(request_queue_t* queue, struct request* req);

    //启停请求

    void blk_stop_queue(request_queue_t* queue);

    void blk_start_queue(request_queue_t* queue);

    //参数设置

    void blk_queue_max_sectors(request_queue_t* q, unsigned short max);

        /*请求可包含的最大扇区数.默认255*/

    void blk_queue_max_phys_segments(request_queue_t* q, unsigned short max);

    void blk_queue_max_hw_segments(request_queue_t* q, unsigned short max);

        /*这两个函数设置一个请求可包含的最大物理段数(系统内存中不相邻的区),缺省是128*/

    void blk_queue_max_segment_size(request_queue_t* q, unsigned int max);

        /*告知内核请求短的最大字节数,默认2^16 = 65536*/

    //通告内核

    void blk_queue_bounce_limit(request_queue_t* queue, u64 dma_addr);

        /*

         * 此函数告知内核设备执行DMA,可使用的最高物理地址dma_addr,常用的宏如下:

         * BLK_BOUNCE_HIGH:对高端内存页使用反弹缓冲(缺省)

         * BLK_BOUNCE_ISA:驱动只可以在MBISA区执行DMA

         * BLK_BOUNCE_ANY:驱动可在任何地方执行DMA

         */

    blk_queue_segment_boundary(request_queue_t* queue, unsigned long mask);

        /*这个函数在设备无法处理跨越一个特殊大小内存边界的请求时,告知内核这个边界.*/

    void blk_queue_dma_alignment(request_queue_t* q, int mask);

        /*告知内核设备加于DMA传送的内存对齐限制*/

    viod blk_queue_hardsect_size(request_queue_t* q, unsigned short max);

       /*此函数告知内核块设备硬件扇区大小*/

3)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;//偏移位置

    };

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

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

 */

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

             for(

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

                <(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四个结构体之间的关系.

 

 

 

5.块设备驱动注册于注销

块设备驱动的第一个任务就是将他们自己注册到内核中,其函数原型如下:

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

major参数是块设备要使用的主设备号,name为设备名,它会在/proc/devices中被现实.如果major0,内核会自动分配一个新的主设备号,并由该函数返回.如果返回值为负值,则说明设备号分派失败.

register_blkdev对应的注销函数是unregister_blkdev(),原型如下:

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

这里unreister_blkdevregister_blkdev的参数必须匹配,否则这个函数会返回-EINVAL.

Linux2.6,register_blkdev的调用是可选的.register_blkdev这个调用在Linux2.6中只完成了两件事情:①如果需要,分派一个主设备号;②在/proc/devices中创建一个入口.

下一篇:Linux块设备驱动(3)--块设备驱动相关模块模板

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