zoukankan      html  css  js  c++  java
  • Linux内核设计与实现 总结笔记(第十四章)块I/O层

    一、剖析一个块设备

    块设备最小的可寻址单元是扇区。 扇区大小一般是2的整数倍,最常见的是512字节。

    因为各种软件的用途不同,所以他们都会用到自己的最小逻辑可寻址单元----块。块只能基于文件系统,是一种抽象。

    而扇区是设备的最小可寻址单元,所以块不能比扇区还小,只能数倍于扇区大小。此外内核要求块是2的整数倍。

    至此,块最终要求是:必须是扇区大小的2的整数倍,并且要小于页面大小。所以通常块是512字节、1KB或4KB。

    扇区:设备最小寻址单元。别名:硬扇区、设备块

    块:文件系统的最小寻址单元。别名:文件块、I/O块

    块包含一个或多个扇区,但大小不能超过一个页。所以页可以容纳一个或多个内存中的块。

    二、缓冲区和缓冲区头

    当一个块被调入内存时,它要存储在一个缓冲区中。每个缓冲区与一个块对应,相当于磁盘块在内存中的表示。

    每个缓冲区都有一个对应的描述符。该描述符用buffer_head结构体表示,称作缓冲区头,在文件<linux/buffer_head.h>中定义。

    struct buffer_head {
        unsigned long b_state; /* buffer state bitmap (see above)缓冲区状态标志位 */
        struct buffer_head *b_this_page;/* circular list of page's buffers页面中的缓冲区 */
        struct page *b_page; /* the page this bh is mapped to 存储缓冲区的页面*/
        sector_t b_blocknr; /* start block number 其实块号*/
        size_t b_size; /* size of mapping 映像的大小*/
        char *b_data; /* pointer to data within the page 页面内的数据指针*/
        struct block_device *b_bdev; /* 相关联的块设备 */
        bh_end_io_t *b_end_io; /* I/O completion I/O完成方法*/
        void *b_private; /* reserved for b_end_io io完成方法*/
        struct list_head b_assoc_buffers; /* associated with another     mapping 相关的映射链表*/
        struct address_space *b_assoc_map; /* mapping this buffer is associated with 相关的地址空间*/
        atomic_t b_count; /* users using this buffer_head 缓冲区使用计数*/
    };
    struct buffer_head

    b_count域表示缓冲区的使用记数,可通过两个定义在文件<linux/buffer_head.h>中内联函数对此域进行增减。

    static inline void get_bh(struct buffer_head *bh)
    {
        atomic_inc(&bh->b_count);
    }
    get_bh
    static inline void put_bh(struct buffer_head *bh)
    {
        atomic_dec(&bh->b_count);
    }
    put_bh

    合法的标志存放在bh_state_bits枚举中,该枚举在<linux/buffer_head.h>中定义:

    enum bh_state_bits {
        BH_Uptodate, /* Contains valid data 包含可用数据*/
        BH_Dirty, /* Is dirty 脏数据*/
        BH_Lock, /* Is locked */
        BH_Req, /* Has been submitted for I/O 有IO操作请求*/
        BH_Uptodate_Lock,/* Used by the first bh in a page, to serialise
      * IO completion of other buffers in the page 被IO操作占用
      */
        BH_Mapped, /* Has a disk mapping 映射了磁盘块*/
        BH_New, /* Disk mapping was newly created by get_block 缓冲区用get_block刚刚获取的,还不能使用*/
        BH_Async_Read, /* Is under end_buffer_async_read I/O 正在被异步读*/
        BH_Async_Write, /* Is under end_buffer_async_write I/O 正在被异步写*/
        BH_Delay, /* Buffer is not yet allocated on disk 尚未与磁盘关联*/
        BH_Boundary, /* Block is followed by a discontiguity 缓冲区在连续块边缘*/
        BH_Write_EIO, /* I/O error on write 写入时遇到错误*/
        BH_Unwritten, /* Buffer is allocated on disk but not written */
        BH_Quiet, /* Buffer Error Prinks to be quiet 禁止错误*/
        BH_Meta, /* Buffer contains metadata */
        BH_Prio, /* Buffer should be submitted with REQ_PRIO */
        BH_Defer_Completion, /* Defer AIO completion to workqueue */
        BH_PrivateStart,/* not a state bit, but the first bit available
     * for private allocation by other entities
     */
    
    };
    bh_state_bits

    bh_state_bits还有一个特殊标志,BH_PrivateStart,该标志不是可用状态标志。 

    三、bio结构体

    内核中块I/O操作基本容器由bio结构表示,定义在文件<linux/bio.h>中。 

    该结构体代表了正在现场的(活动的)以片段(segment)链表形式组织的块I/O操作。

    /*
     * main unit of I/O for the block layer and lower layers (ie drivers and
     * stacking drivers)
     */
    struct bio {
        struct bio        *bi_next;    /* request queue link 请求链表*/
        struct block_device    *bi_bdev;        /* 相关的块设备 */
        unsigned int        bi_flags;    /* status, command, etc 状态和命令标志 */
        int            bi_error;
        unsigned long        bi_rw;        /* bottom bits READ/WRITE,
                             * top bits priority 读还是写
                             */
    
        struct bvec_iter    bi_iter;
    
        /* Number of segments in this BIO after
         * physical address coalescing is performed. 结合后的片段数目
         */
        unsigned int        bi_phys_segments;
    
        /*
         * To keep track of the max segment size, we account for the
         * sizes of the first and last mergeable segments in this bio. 第一个可合并的段大小,最后一个可合并的段大小
         */
        unsigned int        bi_seg_front_size;
        unsigned int        bi_seg_back_size;
    
        atomic_t        __bi_remaining;
    
        bio_end_io_t        *bi_end_io;        /* bi io完成方法 */
    
        void            *bi_private;        /* 拥有者的私有方法 */
    #ifdef CONFIG_BLK_CGROUP
        /*
         * Optional ioc and css associated with this bio.  Put on bio
         * release.  Read comment on top of bio_associate_current().
         */
        struct io_context    *bi_ioc;
        struct cgroup_subsys_state *bi_css;
    #endif
        union {
    #if defined(CONFIG_BLK_DEV_INTEGRITY)
            struct bio_integrity_payload *bi_integrity; /* data integrity */
    #endif
        };
    
        unsigned short        bi_vcnt;    /* how many bio_vec's bio链表个数*/
    
        /*
         * Everything starting with bi_max_vecs will be preserved by bio_reset()
         */
    
        unsigned short        bi_max_vecs;    /* max bvl_vecs we can hold */
    
        atomic_t        __bi_cnt;    /* pin count */
    
        struct bio_vec        *bi_io_vec;    /* the actual vec list bio链表*/
    
        struct bio_set        *bi_pool;
    
        /*
         * We can inline a number of vecs at the end of the bio, to avoid
         * double allocations for a small number of bio_vecs. This member
         * MUST obviously be kept at the very end of the bio.内嵌bio向量
         */
        struct bio_vec        bi_inline_vecs[0];
    };
    struct bio

    bi_io_vec是bio_vec的链表指针,共有bi_vcnt个,当前指向bi_idx位置。bi_vcnt域用来描述bi_io_vec所指向的vio_vec数组中的向量数目。

    3.1 I/O向量

    bi_io_vec域指向一个bio_vec结构体数组,该结构体链表包含一个特定I/O操作所需要使用的所有片段。

    每个bio_vec结构都是一个形式为<page, offset, len>的向量,它描述的是一个特定的片段。片段所在的物理页、块在物理页中的偏移位置、从给定偏移量开始的块长度。

    /*
     * was unsigned short, but we might as well be ready for > 64kB I/O pages
     */
    struct bio_vec {
    /* 指向这个缓冲区所驻留的物理页 */
        struct page    *bv_page;
    /* 这个缓冲区以字节为单位的大小 */
        unsigned int    bv_len;
    /* 缓冲区所驻留的页中以字节为单位的偏移量 */
        unsigned int    bv_offset;
    };
    struct bio_vec

     总而言之,每一个I/O请求都通过一个bio结构体表示。每个请求包含一个或多个块,这些块存储在bio_vec结构体数组中。

    bi_cnt域记录bio结构体的使用计数,如果该值减为0,就撤销该bio结构体。并释放占用的内存。

    void bio_get(struct bio *bio)
    void bio_put(struct bio *bio)
    计数操作

    bi_private域,是一个私有域。只有创建了bio结构的拥有者可以读写。

    利用bio结构体代替buffer_head结构体有以下好处:

    • bio结构体很容易处理高端内存,因为它处理的是物理页而不是直接指针。
    • bio结构体既可以代表普通页I/O,同时也可以代表直接I/O
    • bio结构体便于执行分散--集中块I/O操作,操作中的数据可取自多个物理页面。
    • bio结构体相比缓冲区头属于轻量级的结构体。因为它只需要包含块I/O操作所需的信息就行了,不用包含与缓冲区本身相关的不必要信息。

    四、请求队列

    块设备将他们挂起在块I/O请求保存在请求队列中,该队列有reques_queue结构体表示,定义在文件<linux/blkdev.h>中,包含要给双向请求链表以及相关控制信息。

    队列中的请求由request表示,定义在<linux/blkdev.h>中。

    bio使用例程:

    #include <linux/module.h>
    #include <linux/init.h>
    #include <linux/genhd.h>
    #include <linux/string.h>
    #include <uapi/linux/major.h>
    #include <linux/blkdev.h>
    
    #define SIMP_BLKDEV_DISK_NAME "simp_blkdev"
    #define SIMP_BLKDEV_DEVICEMAJOR COMPAQ_SMART2_MAJOR7;
    #define SIMP_BLKDEV_BYTES (16*1024*1024)
    
    static struct gendisk *simp_blkdev_disk;
    static struct request_queue *simp_blkdev_queue;
    unsigned char simp_blkdev_data[SIMP_BLKDEV_BYTES];
    
    static void simp_blkdev_do_request(struct request_queue *q);
    
    struct block_device_operations simp_blkdev_fops = {
        .owner  = THIS_MODULE,
    };
    
    static void simp_blkdev_do_request(struct request_queue *q)
    {
        struct request *req;
        struct bio *req_bio;
        struct bio_vec *bvec;
        char *disk_mem;
        char *buffer;
        int i = 0;
        //get request from queue
        while((req = blk_fetch_request(q))!=NULL) {
            //request ok?
            if((blk_rq_pos(req) << 9) + blk_rq_cur_bytes(req) > SIMP_BLKDEV_BYTES) {
                printk(KERN_ERR SIMP_BLKDEV_DISK_NAME
                        ":bad request:block=%llu,count=%u
    ",
                        (unsigned long long)blk_rq_pos(req),
                         blk_rq_cur_bytes(req));
                blk_end_request_all(req, -EIO);
                continue;
            }
            //get memory position
            disk_mem = simp_blkdev_data + (blk_rq_pos(req) << 9);
            req_bio = req->bio;
            
            switch(rq_data_dir(req)) {
                case READ:
                    while(req_bio != NULL) {
                        for(i=0;i<req_bio->bi_vcnt;i++) {
                            bvec = &(req_bio->bi_io_vec[i]);
                            buffer = kmap(bvec->bv_page) + bvec->bv_offset;
                            memcpy(buffer, disk_mem, bvec->bv_len);
                            kunmap(bvec->bv_page);
                            disk_mem += bvec->bv_len;
                        }
                        req_bio = req_bio->bi_next;
                    }
                    __blk_end_request_all(req, 0);
                    break;
                case WRITE:
                    while(req_bio != NULL) {
                        for(i=0;i<req_bio->bi_vcnt;i++) {
                            bvec = &(req_bio->bi_io_vec[i]);
                            buffer = kmap(bvec->bv_page) + bvec->bv_offset;
                            memcpy(disk_mem, buffer, bvec->bv_len);
                            kunmap(bvec->bv_page);
                            disk_mem += bvec->bv_len;
                        }
                        req_bio = req_bio->bi_next;
                    }
                    __blk_end_request_all(req, 0);
                    break;
                default:
                    break;
            }
        }
    }
    
    static int __init init_base(void)
    {
        int ret;
        printk("----Hello World----
    ");
        /* init queue */
        simp_blkdev_queue = blk_init_queue(simp_blkdev_do_request, NULL);
        if(!simp_blkdev_queue) {
            ret = -ENOMEM;
            goto err_init_queue;
        }
    
        /* init gendisk */
        simp_blkdev_disk = alloc_disk(1);
        if(!simp_blkdev_disk) {
            ret = -ENOMEM;
            goto err_alloc_disk;
        }
        strcpy(simp_blkdev_disk->disk_name, SIMP_BLKDEV_DISK_NAME);
        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 exit_base(void)
    {
        blk_cleanup_queue(simp_blkdev_queue);
        put_disk(simp_blkdev_disk);
        del_gendisk(simp_blkdev_disk);
        printk("----exit ----
    ");
    }
    
    module_init(init_base);
    module_exit(exit_base);
    
    MODULE_LICENSE("GPL");
    MODULE_AUTHOR("chen");
    MODULE_DESCRIPTION("bio test");
    mybio

    参考资料:https://lwn.net/Articles/736534/

    https://www.xuebuyuan.com/3227084.html

    https://blog.csdn.net/cxy_chen/article/details/80998510

    http://bbs.chinaunix.net/thread-2017377-1-1.html

  • 相关阅读:
    Java规约之方法设计
    JVM第一篇 JVM与Java体系结构
    初学者学习Java方法集
    初学者学习Java方法集
    1.Spring-Boot 静态文件和页默认放置位置
    2.Spring-Boot常用配置解释
    3.Spring-Boot核心@SpringBootApplication介绍
    4.Spring-Boot中基于Junit的单元测试
    vue学习笔记(一) ---- vue指令(v-for 和 key 属性)
    vue学习笔记(一) ----- vue指令(菜单列表案例)
  • 原文地址:https://www.cnblogs.com/ch122633/p/11725660.html
Copyright © 2011-2022 走看看