]> git.hungrycats.org Git - linux/commitdiff
block: fix nr_phys_segments miscalculation bug
authorFUJITA Tomonori <fujita.tomonori@lab.ntt.co.jp>
Wed, 12 Nov 2008 06:03:54 +0000 (11:33 +0530)
committerGreg Kroah-Hartman <gregkh@suse.de>
Thu, 20 Nov 2008 22:54:47 +0000 (14:54 -0800)
commit 8677142710516d986d932d6f1fba7be8382c1fec upstream
backported by Nikanth Karthikesan <knikanth@suse.de> to the 2.6.27.y tree.

block: fix nr_phys_segments miscalculation bug

This fixes the bug reported by Nikanth Karthikesan <knikanth@suse.de>:

http://lkml.org/lkml/2008/10/2/203

The root cause of the bug is that blk_phys_contig_segment
miscalculates q->max_segment_size.

blk_phys_contig_segment checks:

req->biotail->bi_size + next_req->bio->bi_size > q->max_segment_size

But blk_recalc_rq_segments might expect that req->biotail and the
previous bio in the req are supposed be merged into one
segment. blk_recalc_rq_segments might also expect that next_req->bio
and the next bio in the next_req are supposed be merged into one
segment. In such case, we merge two requests that can't be merged
here. Later, blk_rq_map_sg gives more segments than it should.

We need to keep track of segment size in blk_recalc_rq_segments and
use it to see if two requests can be merged. This patch implements it
in the similar way that we used to do for hw merging (virtual
merging).

Signed-off-by: FUJITA Tomonori <fujita.tomonori@lab.ntt.co.jp>
Signed-off-by: Jens Axboe <jens.axboe@oracle.com>
Cc: Nikanth Karthikesan <knikanth@suse.de>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
block/blk-merge.c
include/linux/bio.h

index 5efc9e7a68b777fe42cc53b2a7bf3add00adff2a..857dce7634b6c1a5db8c37797677ce553167963c 100644 (file)
@@ -95,6 +95,9 @@ new_hw_segment:
                        nr_hw_segs++;
                }
 
+               if (nr_phys_segs == 1 && seg_size > rq->bio->bi_seg_front_size)
+                       rq->bio->bi_seg_front_size = seg_size;
+
                nr_phys_segs++;
                bvprv = bv;
                seg_size = bv->bv_len;
@@ -106,6 +109,10 @@ new_hw_segment:
                rq->bio->bi_hw_front_size = hw_seg_size;
        if (hw_seg_size > rq->biotail->bi_hw_back_size)
                rq->biotail->bi_hw_back_size = hw_seg_size;
+       if (nr_phys_segs == 1 && seg_size > rq->bio->bi_seg_front_size)
+               rq->bio->bi_seg_front_size = seg_size;
+       if (seg_size > rq->biotail->bi_seg_back_size)
+               rq->biotail->bi_seg_back_size = seg_size;
        rq->nr_phys_segments = nr_phys_segs;
        rq->nr_hw_segments = nr_hw_segs;
 }
@@ -133,7 +140,8 @@ static int blk_phys_contig_segment(struct request_queue *q, struct bio *bio,
 
        if (!BIOVEC_PHYS_MERGEABLE(__BVEC_END(bio), __BVEC_START(nxt)))
                return 0;
-       if (bio->bi_size + nxt->bi_size > q->max_segment_size)
+       if (bio->bi_seg_back_size + nxt->bi_seg_front_size >
+           q->max_segment_size)
                return 0;
 
        /*
@@ -377,6 +385,8 @@ static int ll_merge_requests_fn(struct request_queue *q, struct request *req,
 {
        int total_phys_segments;
        int total_hw_segments;
+       unsigned int seg_size =
+               req->biotail->bi_seg_back_size + next->bio->bi_seg_front_size;
 
        /*
         * First check if the either of the requests are re-queued
@@ -392,8 +402,13 @@ static int ll_merge_requests_fn(struct request_queue *q, struct request *req,
                return 0;
 
        total_phys_segments = req->nr_phys_segments + next->nr_phys_segments;
-       if (blk_phys_contig_segment(q, req->biotail, next->bio))
+       if (blk_phys_contig_segment(q, req->biotail, next->bio)) {
+               if (req->nr_phys_segments == 1)
+                       req->bio->bi_seg_front_size = seg_size;
+               if (next->nr_phys_segments == 1)
+                       next->biotail->bi_seg_back_size = seg_size;
                total_phys_segments--;
+       }
 
        if (total_phys_segments > q->max_phys_segments)
                return 0;
index 0933a14e641423f523d8ed7a56ef7f2a6b849e5c..3d8394732d94794e3a9005a93b224cbd5a8a629c 100644 (file)
@@ -97,6 +97,13 @@ struct bio {
 
        unsigned int            bi_size;        /* residual I/O count */
 
+       /*
+        * 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;
+
        /*
         * To keep track of the max hw size, we account for the
         * sizes of the first and last virtually mergeable segments