写块设备驱动时要多处用到__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;
}