__blk_end_request源码分析


写块设备驱动时要多处用到__blk_end_request这个函数,用这篇博文简单分析一下它,
省得以后用起来还是稀里糊涂的。我们已经这道的是,当对应的 request 包含的IO操作
已经全部完成时,这个函数返回false;否则,如果只是部分完成,则返回true。


__blk_end_request 函数的过程可简化理解如下:(实际的代码并非如此,
这里忽略了双向IO(bidi_io)相关的操作,以使代码简化便于理解,
实际的调用过程参见附录或内核源码,本文所分析的内核源码版本是 3.0.8,

更新版本的内核在这一部分的改动似乎不大)

bool __blk_end_request(struct request *rq, int error, unsigned int nr_bytes)
{
    if (blk_update_request(rq, error, nr_bytes))
        return true;

    blk_finish_request(rq, error);

    return false;
}




可见主要是对 blk_update_request和blk_finish_request函数的调用。
blk_update_request函数主要是更新 request 结构,将已处理完的字节
从request中移除等,详见后文的说明。如果blk_update_request返回false,
这时说明 request 中包含的 io 操作都已经执行完毕,这时再调用 blk_update_request
函数做一些收尾处理,并释放该 request 结构的内存(通过调用__blk_put_request)。

static void blk_finish_request(struct request *req, int error)
{
    ......
    ......

    // 如果设置了 req 的 end_io 成员函数,则调用定制的 end_io 函数来结束请求;
    // 否则,使用默认的__blk_put_request 函数释放该 request 结构。
    if (req->end_io)        
        req->end_io(req, error);
    else {
        ......
        __blk_put_request(req->q, req);
    }
}




blk_update_request函数做的工作比较多,下面是关于 blk_update_request函数的几点说明:


1. 当blk_update_request函数执行完毕后,req->bio 会指向即将(或正
在)被读写的 bio ; 对应的 bio->bi_idx 指向下一个未完成读写
的 bio_vec ;
2. blk_update_request 函数内部还会重新对 req->buffer 进行映射,使其
指向 req->bio 中的下一个未完成读写的 bio_vec 所对应的数据区域,
所以每次调用完 __blk_end_request 后,都可以用 req->buffer 和
blk_rq_cur_bytes(req) 来访问 req 的下一段数据;(blk_rq_cur_bytes宏
表示的就是req中当前bio的当前bio_vec的数据长度)
3. blk_update_request函数还会根据已经读写完的数据长度来更新 req->__sector ,
这个变量指向磁盘中即将被读写的扇区号。每一个req中所有bio要处理的扇区都是连续的。



下面是与 blk_update_request 函数相关的一些代码,为了增加可读性,
删了一些异常处理的代码,还修改了极少部分代码(不影响语义)


bool blk_update_request(struct request *req, int error, unsigned int nr_bytes)
{
    int total_bytes, bio_nbytes, next_idx = 0;
    struct bio *bio;

    ......
    ......

    total_bytes = bio_nbytes = 0;

    // 调用此函数时,说明已经读写完了 req 中的 nr_bytes 个字节,所以要从 req 中
    // 移除这些已经被读写的字节。下面的每次循环中,都会移除一部分字节,并将 nr_bytes
    // 减掉相应的长度。( nr_bytes始终等于剩余的待被移除的字节数 )

    // 下面的循环中,会首先尝试从 req 中依次移除一个 bio ;
    // 如果检测到某个 bio 的 size 大于 nr_bytes 时,说明当前的 bio 还没有被完全读写完毕,
    // 这时会再尝试移除该bio中的每个 bio_vec , 当检测到某个bio_vec的size大于nr_bytes
    // 时,会通过调整该 bio_vec 数据区域的长度和起始地址来移除已被读写的字节。
    // 即: 在未遇到不完整的 bio 时,每次循环移除一个 bio ;
    //    遇到不完正的 bio 后,未遇到不完整的 bio_vec 前,每次移除一个 bio_vec ;
    //    遇到不完整的 bio_vec 时,退出循环;

    while ((bio = req->bio) != NULL) {
        int nbytes;
        
        // nbytes是本次循环中将被移除的数据长度,total_bytes是req中已被移除的字节数

        // 当遇到不完整的bio后, next_idx表示本次循环所处理的bio_vec在该bio中的索引偏移(相对于bio->bi_idx),
        // bio_nbytes是该bio中已被移除的字节数


        if (nr_bytes >= bio->bi_size) {        
            // 即将被结束的字节数量大于当前bio的字节数,则直接结束当前的bio,并设置next_idx和bio_nbytes为0
            req->bio = bio->bi_next;
            nbytes = bio->bi_size;
            req_bio_endio(req, bio, nbytes, error);        // 结束当前 bio
            next_idx = 0;
            bio_nbytes = 0;
        } else {    // nr_bytes < bio->bi_size , 说明遇到了未被完整读写的 bio

            int idx = bio->bi_idx + next_idx;

            nbytes = bio_iovec_idx(bio, idx)->bv_len;    // 获取当期 bio_vec 的数据长度nbytes

            if (unlikely(nbytes > nr_bytes))
            {    
                // nbytes > nr_bytes 说明当前的这一个bio_vec未被完整的读写
                /*
                 * not a complete bvec done
                 */
                bio_nbytes += nr_bytes;
                total_bytes += nr_bytes;
                break;            //这里直接退出大循环
            }
            else
            {    // 如果当前的这一个bio_vec被完整的读写了,则next_idx指向下一个bio_vec,同时将bio_nbytes加上nbytes
                /*
                 * advance to the next vector
                 */
                next_idx++;    
                bio_nbytes += nbytes;
            }
        }

        total_bytes += nbytes;
        nr_bytes -= nbytes;    // 已移除 nbytes 个字节, 所以减去 nbytes

        bio = req->bio;
        if (bio) {
            /*
             * end more in this run, or just return 'not-done'
             */
            if (unlikely(nr_bytes <= 0))
                break;        // 这里说明 nr_bytes 个字节已经全部被移除了,跳出大循环
        }
    }

    // 大循环结束。(nr_bytes个字节移除完毕)


    /*
     * completely done
     */
    if (!req->bio) {    // 没有剩余的bio,说明一个请求被完整地完成了
        /*
         * Reset counters so that the request stacking driver
         * can find how many bytes remain in the request
         * later.
         */
        req->__data_len = 0;
        return false;    // return false 表示整个 request 已经处理完毕    
    }

    /*
     * if the request wasn't completed, update state
     */

    // request没有完全完成,这时更新该request的信息

    if (bio_nbytes) {
        req_bio_endio(req, bio, bio_nbytes, error);    // 将当前bio结束 bio_nbytes 个字节
        bio->bi_idx += next_idx;    // 更新当前 bio 的 bio_vec 指针。(指向下一个将要被读写的bio_vec)
        bio_iovec(bio)->bv_offset += nr_bytes;    // 更新当前 bio_vec 的数据指针和数据长度
        bio_iovec(bio)->bv_len -= nr_bytes;
    }

    req->__data_len -= total_bytes;        // 设置 request 的当前段数据长度 __data_len 和 当前段数据指针 buffer .
    req->buffer = bio_data(req->bio);

    /* update sector only for requests with clear definition of sector */
    if (req->cmd_type == REQ_TYPE_FS || (req->cmd_flags & REQ_DISCARD))
        req->__sector += total_bytes >> 9;    // 设置request的当前扇区号。 从这一句可以看出一个请求中的所有 bio 的介质地址都是连续的


    return true;    // return true 表示 request 没有完全处理完。
}




// 上面的函数中多次出现了 req_bio_endio 来结束(或部分结束)一个 bio ,下面看下这个函数都干了什么
static void req_bio_endio(struct request *rq, struct bio *bio,
              unsigned int nbytes, int error)
{
    ......
    ......


    bio->bi_size -= nbytes;        // 更新 bio 的数据长度和起始扇区号
    bio->bi_sector += (nbytes >> 9);


    if (bio_integrity(bio))        
        bio_integrity_advance(bio, nbytes);
            // 与 integrity data 相关的函数和结构体我还没有研究过,
            // 不过bio_integrity_advance这个函数只对bio->bi_integrity
            // 进行操作,不会改变 bio 的其他重要成员


    /* don't actually finish bio if it's part of flush sequence */
    if (bio->bi_size == 0 && !(rq->cmd_flags & REQ_FLUSH_SEQ))
        bio_endio(bio, error);    // 如果bio被完成了(size=0),调用bio_endio
}



// bio_endio 函数中主要是对 bio->bi_end_io 的调用。疑点: 一个 bio 处理完后在哪里释放其所占用的内存?

void bio_endio(struct bio *bio, int error)
{
    ......
    ......

    if (bio->bi_end_io)
        bio->bi_end_io(bio, error);
}







附:    __blk_end_request 的实际调用过程

bool __blk_end_request(struct request *rq, int error, unsigned int nr_bytes)
{
    return __blk_end_bidi_request(rq, error, nr_bytes, 0);
}

static bool __blk_end_bidi_request(struct request *rq, int error,
                   unsigned int nr_bytes, unsigned int bidi_bytes)
{
    if (blk_update_bidi_request(rq, error, nr_bytes, bidi_bytes))
        return true;

    blk_finish_request(rq, error);

    return false;
}

static bool blk_update_bidi_request(struct request *rq, int error,
                    unsigned int nr_bytes,
                    unsigned int bidi_bytes)
{
    if (blk_update_request(rq, error, nr_bytes))
        return true;

    /* Bidi request must be completed as a whole */
    if (unlikely(blk_bidi_rq(rq)) &&
        blk_update_request(rq->next_rq, error, bidi_bytes))
        return true;

    if (blk_queue_add_random(rq->q))
        add_disk_randomness(rq->rq_disk);

    return false;
}




评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值