mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-09-12 05:48:40 +00:00
![Yasuaki Ishimatsu](/assets/img/avatar_default.png)
/proc/diskstats would display a strange output as follows. $ cat /proc/diskstats |grep sda 8 0 sda 90524 7579 102154 20464 0 0 0 0 0 14096 20089 8 1 sda1 19085 1352 21841 4209 0 0 0 0 4294967064 15689 4293424691 ~~~~~~~~~~ 8 2 sda2 71252 3624 74891 15950 0 0 0 0 232 23995 1562390 8 3 sda3 54 487 2188 92 0 0 0 0 0 88 92 8 4 sda4 4 0 8 0 0 0 0 0 0 0 0 8 5 sda5 81 2027 2130 138 0 0 0 0 0 87 137 Its reason is the wrong way of accounting hd_struct->in_flight. When a bio is merged into a request belongs to different partition by ELEVATOR_FRONT_MERGE. The detailed root cause is as follows. Assuming that there are two partition, sda1 and sda2. 1. A request for sda2 is in request_queue. Hence sda1's hd_struct->in_flight is 0 and sda2's one is 1. | hd_struct->in_flight --------------------------- sda1 | 0 sda2 | 1 --------------------------- 2. A bio belongs to sda1 is issued and is merged into the request mentioned on step1 by ELEVATOR_BACK_MERGE. The first sector of the request is changed from sda2 region to sda1 region. However the two partition's hd_struct->in_flight are not changed. | hd_struct->in_flight --------------------------- sda1 | 0 sda2 | 1 --------------------------- 3. The request is finished and blk_account_io_done() is called. In this case, sda2's hd_struct->in_flight, not a sda1's one, is decremented. | hd_struct->in_flight --------------------------- sda1 | -1 sda2 | 1 --------------------------- The patch fixes the problem by caching the partition lookup inside the request structure, hence making sure that the increment and decrement will always happen on the same partition struct. This also speeds up IO with accounting enabled, since it cuts down on the number of lookups we have to do. When reloading partition tables, quiesce IO to ensure that no request references to the partition struct exists. When it is safe to free the partition table, the IO for that device is restarted again. Signed-off-by: Yasuaki Ishimatsu <isimatu.yasuaki@jp.fujitsu.com> Cc: stable@kernel.org Signed-off-by: Jens Axboe <jaxboe@fusionio.com>
454 lines
11 KiB
C
454 lines
11 KiB
C
/*
|
|
* Functions related to segment and merge handling
|
|
*/
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/bio.h>
|
|
#include <linux/blkdev.h>
|
|
#include <linux/scatterlist.h>
|
|
|
|
#include "blk.h"
|
|
|
|
static unsigned int __blk_recalc_rq_segments(struct request_queue *q,
|
|
struct bio *bio)
|
|
{
|
|
struct bio_vec *bv, *bvprv = NULL;
|
|
int cluster, i, high, highprv = 1;
|
|
unsigned int seg_size, nr_phys_segs;
|
|
struct bio *fbio, *bbio;
|
|
|
|
if (!bio)
|
|
return 0;
|
|
|
|
fbio = bio;
|
|
cluster = test_bit(QUEUE_FLAG_CLUSTER, &q->queue_flags);
|
|
seg_size = 0;
|
|
nr_phys_segs = 0;
|
|
for_each_bio(bio) {
|
|
bio_for_each_segment(bv, bio, i) {
|
|
/*
|
|
* the trick here is making sure that a high page is
|
|
* never considered part of another segment, since that
|
|
* might change with the bounce page.
|
|
*/
|
|
high = page_to_pfn(bv->bv_page) > queue_bounce_pfn(q);
|
|
if (high || highprv)
|
|
goto new_segment;
|
|
if (cluster) {
|
|
if (seg_size + bv->bv_len
|
|
> queue_max_segment_size(q))
|
|
goto new_segment;
|
|
if (!BIOVEC_PHYS_MERGEABLE(bvprv, bv))
|
|
goto new_segment;
|
|
if (!BIOVEC_SEG_BOUNDARY(q, bvprv, bv))
|
|
goto new_segment;
|
|
|
|
seg_size += bv->bv_len;
|
|
bvprv = bv;
|
|
continue;
|
|
}
|
|
new_segment:
|
|
if (nr_phys_segs == 1 && seg_size >
|
|
fbio->bi_seg_front_size)
|
|
fbio->bi_seg_front_size = seg_size;
|
|
|
|
nr_phys_segs++;
|
|
bvprv = bv;
|
|
seg_size = bv->bv_len;
|
|
highprv = high;
|
|
}
|
|
bbio = bio;
|
|
}
|
|
|
|
if (nr_phys_segs == 1 && seg_size > fbio->bi_seg_front_size)
|
|
fbio->bi_seg_front_size = seg_size;
|
|
if (seg_size > bbio->bi_seg_back_size)
|
|
bbio->bi_seg_back_size = seg_size;
|
|
|
|
return nr_phys_segs;
|
|
}
|
|
|
|
void blk_recalc_rq_segments(struct request *rq)
|
|
{
|
|
rq->nr_phys_segments = __blk_recalc_rq_segments(rq->q, rq->bio);
|
|
}
|
|
|
|
void blk_recount_segments(struct request_queue *q, struct bio *bio)
|
|
{
|
|
struct bio *nxt = bio->bi_next;
|
|
|
|
bio->bi_next = NULL;
|
|
bio->bi_phys_segments = __blk_recalc_rq_segments(q, bio);
|
|
bio->bi_next = nxt;
|
|
bio->bi_flags |= (1 << BIO_SEG_VALID);
|
|
}
|
|
EXPORT_SYMBOL(blk_recount_segments);
|
|
|
|
static int blk_phys_contig_segment(struct request_queue *q, struct bio *bio,
|
|
struct bio *nxt)
|
|
{
|
|
if (!test_bit(QUEUE_FLAG_CLUSTER, &q->queue_flags))
|
|
return 0;
|
|
|
|
if (bio->bi_seg_back_size + nxt->bi_seg_front_size >
|
|
queue_max_segment_size(q))
|
|
return 0;
|
|
|
|
if (!bio_has_data(bio))
|
|
return 1;
|
|
|
|
if (!BIOVEC_PHYS_MERGEABLE(__BVEC_END(bio), __BVEC_START(nxt)))
|
|
return 0;
|
|
|
|
/*
|
|
* bio and nxt are contiguous in memory; check if the queue allows
|
|
* these two to be merged into one
|
|
*/
|
|
if (BIO_SEG_BOUNDARY(q, bio, nxt))
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* map a request to scatterlist, return number of sg entries setup. Caller
|
|
* must make sure sg can hold rq->nr_phys_segments entries
|
|
*/
|
|
int blk_rq_map_sg(struct request_queue *q, struct request *rq,
|
|
struct scatterlist *sglist)
|
|
{
|
|
struct bio_vec *bvec, *bvprv;
|
|
struct req_iterator iter;
|
|
struct scatterlist *sg;
|
|
int nsegs, cluster;
|
|
|
|
nsegs = 0;
|
|
cluster = test_bit(QUEUE_FLAG_CLUSTER, &q->queue_flags);
|
|
|
|
/*
|
|
* for each bio in rq
|
|
*/
|
|
bvprv = NULL;
|
|
sg = NULL;
|
|
rq_for_each_segment(bvec, rq, iter) {
|
|
int nbytes = bvec->bv_len;
|
|
|
|
if (bvprv && cluster) {
|
|
if (sg->length + nbytes > queue_max_segment_size(q))
|
|
goto new_segment;
|
|
|
|
if (!BIOVEC_PHYS_MERGEABLE(bvprv, bvec))
|
|
goto new_segment;
|
|
if (!BIOVEC_SEG_BOUNDARY(q, bvprv, bvec))
|
|
goto new_segment;
|
|
|
|
sg->length += nbytes;
|
|
} else {
|
|
new_segment:
|
|
if (!sg)
|
|
sg = sglist;
|
|
else {
|
|
/*
|
|
* If the driver previously mapped a shorter
|
|
* list, we could see a termination bit
|
|
* prematurely unless it fully inits the sg
|
|
* table on each mapping. We KNOW that there
|
|
* must be more entries here or the driver
|
|
* would be buggy, so force clear the
|
|
* termination bit to avoid doing a full
|
|
* sg_init_table() in drivers for each command.
|
|
*/
|
|
sg->page_link &= ~0x02;
|
|
sg = sg_next(sg);
|
|
}
|
|
|
|
sg_set_page(sg, bvec->bv_page, nbytes, bvec->bv_offset);
|
|
nsegs++;
|
|
}
|
|
bvprv = bvec;
|
|
} /* segments in rq */
|
|
|
|
|
|
if (unlikely(rq->cmd_flags & REQ_COPY_USER) &&
|
|
(blk_rq_bytes(rq) & q->dma_pad_mask)) {
|
|
unsigned int pad_len =
|
|
(q->dma_pad_mask & ~blk_rq_bytes(rq)) + 1;
|
|
|
|
sg->length += pad_len;
|
|
rq->extra_len += pad_len;
|
|
}
|
|
|
|
if (q->dma_drain_size && q->dma_drain_needed(rq)) {
|
|
if (rq->cmd_flags & REQ_WRITE)
|
|
memset(q->dma_drain_buffer, 0, q->dma_drain_size);
|
|
|
|
sg->page_link &= ~0x02;
|
|
sg = sg_next(sg);
|
|
sg_set_page(sg, virt_to_page(q->dma_drain_buffer),
|
|
q->dma_drain_size,
|
|
((unsigned long)q->dma_drain_buffer) &
|
|
(PAGE_SIZE - 1));
|
|
nsegs++;
|
|
rq->extra_len += q->dma_drain_size;
|
|
}
|
|
|
|
if (sg)
|
|
sg_mark_end(sg);
|
|
|
|
return nsegs;
|
|
}
|
|
EXPORT_SYMBOL(blk_rq_map_sg);
|
|
|
|
static inline int ll_new_hw_segment(struct request_queue *q,
|
|
struct request *req,
|
|
struct bio *bio)
|
|
{
|
|
int nr_phys_segs = bio_phys_segments(q, bio);
|
|
|
|
if (req->nr_phys_segments + nr_phys_segs > queue_max_segments(q))
|
|
goto no_merge;
|
|
|
|
if (bio_integrity(bio) && blk_integrity_merge_bio(q, req, bio))
|
|
goto no_merge;
|
|
|
|
/*
|
|
* This will form the start of a new hw segment. Bump both
|
|
* counters.
|
|
*/
|
|
req->nr_phys_segments += nr_phys_segs;
|
|
return 1;
|
|
|
|
no_merge:
|
|
req->cmd_flags |= REQ_NOMERGE;
|
|
if (req == q->last_merge)
|
|
q->last_merge = NULL;
|
|
return 0;
|
|
}
|
|
|
|
int ll_back_merge_fn(struct request_queue *q, struct request *req,
|
|
struct bio *bio)
|
|
{
|
|
unsigned short max_sectors;
|
|
|
|
if (unlikely(req->cmd_type == REQ_TYPE_BLOCK_PC))
|
|
max_sectors = queue_max_hw_sectors(q);
|
|
else
|
|
max_sectors = queue_max_sectors(q);
|
|
|
|
if (blk_rq_sectors(req) + bio_sectors(bio) > max_sectors) {
|
|
req->cmd_flags |= REQ_NOMERGE;
|
|
if (req == q->last_merge)
|
|
q->last_merge = NULL;
|
|
return 0;
|
|
}
|
|
if (!bio_flagged(req->biotail, BIO_SEG_VALID))
|
|
blk_recount_segments(q, req->biotail);
|
|
if (!bio_flagged(bio, BIO_SEG_VALID))
|
|
blk_recount_segments(q, bio);
|
|
|
|
return ll_new_hw_segment(q, req, bio);
|
|
}
|
|
|
|
int ll_front_merge_fn(struct request_queue *q, struct request *req,
|
|
struct bio *bio)
|
|
{
|
|
unsigned short max_sectors;
|
|
|
|
if (unlikely(req->cmd_type == REQ_TYPE_BLOCK_PC))
|
|
max_sectors = queue_max_hw_sectors(q);
|
|
else
|
|
max_sectors = queue_max_sectors(q);
|
|
|
|
|
|
if (blk_rq_sectors(req) + bio_sectors(bio) > max_sectors) {
|
|
req->cmd_flags |= REQ_NOMERGE;
|
|
if (req == q->last_merge)
|
|
q->last_merge = NULL;
|
|
return 0;
|
|
}
|
|
if (!bio_flagged(bio, BIO_SEG_VALID))
|
|
blk_recount_segments(q, bio);
|
|
if (!bio_flagged(req->bio, BIO_SEG_VALID))
|
|
blk_recount_segments(q, req->bio);
|
|
|
|
return ll_new_hw_segment(q, req, bio);
|
|
}
|
|
|
|
static int ll_merge_requests_fn(struct request_queue *q, struct request *req,
|
|
struct request *next)
|
|
{
|
|
int total_phys_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
|
|
* requests. Can't merge them if they are.
|
|
*/
|
|
if (req->special || next->special)
|
|
return 0;
|
|
|
|
/*
|
|
* Will it become too large?
|
|
*/
|
|
if ((blk_rq_sectors(req) + blk_rq_sectors(next)) > queue_max_sectors(q))
|
|
return 0;
|
|
|
|
total_phys_segments = req->nr_phys_segments + next->nr_phys_segments;
|
|
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 > queue_max_segments(q))
|
|
return 0;
|
|
|
|
if (blk_integrity_rq(req) && blk_integrity_merge_rq(q, req, next))
|
|
return 0;
|
|
|
|
/* Merge is OK... */
|
|
req->nr_phys_segments = total_phys_segments;
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
* blk_rq_set_mixed_merge - mark a request as mixed merge
|
|
* @rq: request to mark as mixed merge
|
|
*
|
|
* Description:
|
|
* @rq is about to be mixed merged. Make sure the attributes
|
|
* which can be mixed are set in each bio and mark @rq as mixed
|
|
* merged.
|
|
*/
|
|
void blk_rq_set_mixed_merge(struct request *rq)
|
|
{
|
|
unsigned int ff = rq->cmd_flags & REQ_FAILFAST_MASK;
|
|
struct bio *bio;
|
|
|
|
if (rq->cmd_flags & REQ_MIXED_MERGE)
|
|
return;
|
|
|
|
/*
|
|
* @rq will no longer represent mixable attributes for all the
|
|
* contained bios. It will just track those of the first one.
|
|
* Distributes the attributs to each bio.
|
|
*/
|
|
for (bio = rq->bio; bio; bio = bio->bi_next) {
|
|
WARN_ON_ONCE((bio->bi_rw & REQ_FAILFAST_MASK) &&
|
|
(bio->bi_rw & REQ_FAILFAST_MASK) != ff);
|
|
bio->bi_rw |= ff;
|
|
}
|
|
rq->cmd_flags |= REQ_MIXED_MERGE;
|
|
}
|
|
|
|
static void blk_account_io_merge(struct request *req)
|
|
{
|
|
if (blk_do_io_stat(req)) {
|
|
struct hd_struct *part;
|
|
int cpu;
|
|
|
|
cpu = part_stat_lock();
|
|
part = req->part;
|
|
|
|
part_round_stats(cpu, part);
|
|
part_dec_in_flight(part, rq_data_dir(req));
|
|
|
|
part_stat_unlock();
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Has to be called with the request spinlock acquired
|
|
*/
|
|
static int attempt_merge(struct request_queue *q, struct request *req,
|
|
struct request *next)
|
|
{
|
|
if (!rq_mergeable(req) || !rq_mergeable(next))
|
|
return 0;
|
|
|
|
/*
|
|
* not contiguous
|
|
*/
|
|
if (blk_rq_pos(req) + blk_rq_sectors(req) != blk_rq_pos(next))
|
|
return 0;
|
|
|
|
if (rq_data_dir(req) != rq_data_dir(next)
|
|
|| req->rq_disk != next->rq_disk
|
|
|| next->special)
|
|
return 0;
|
|
|
|
/*
|
|
* If we are allowed to merge, then append bio list
|
|
* from next to rq and release next. merge_requests_fn
|
|
* will have updated segment counts, update sector
|
|
* counts here.
|
|
*/
|
|
if (!ll_merge_requests_fn(q, req, next))
|
|
return 0;
|
|
|
|
/*
|
|
* If failfast settings disagree or any of the two is already
|
|
* a mixed merge, mark both as mixed before proceeding. This
|
|
* makes sure that all involved bios have mixable attributes
|
|
* set properly.
|
|
*/
|
|
if ((req->cmd_flags | next->cmd_flags) & REQ_MIXED_MERGE ||
|
|
(req->cmd_flags & REQ_FAILFAST_MASK) !=
|
|
(next->cmd_flags & REQ_FAILFAST_MASK)) {
|
|
blk_rq_set_mixed_merge(req);
|
|
blk_rq_set_mixed_merge(next);
|
|
}
|
|
|
|
/*
|
|
* At this point we have either done a back merge
|
|
* or front merge. We need the smaller start_time of
|
|
* the merged requests to be the current request
|
|
* for accounting purposes.
|
|
*/
|
|
if (time_after(req->start_time, next->start_time))
|
|
req->start_time = next->start_time;
|
|
|
|
req->biotail->bi_next = next->bio;
|
|
req->biotail = next->biotail;
|
|
|
|
req->__data_len += blk_rq_bytes(next);
|
|
|
|
elv_merge_requests(q, req, next);
|
|
|
|
/*
|
|
* 'next' is going away, so update stats accordingly
|
|
*/
|
|
blk_account_io_merge(next);
|
|
|
|
req->ioprio = ioprio_best(req->ioprio, next->ioprio);
|
|
if (blk_rq_cpu_valid(next))
|
|
req->cpu = next->cpu;
|
|
|
|
/* owner-ship of bio passed from next to req */
|
|
next->bio = NULL;
|
|
__blk_put_request(q, next);
|
|
return 1;
|
|
}
|
|
|
|
int attempt_back_merge(struct request_queue *q, struct request *rq)
|
|
{
|
|
struct request *next = elv_latter_request(q, rq);
|
|
|
|
if (next)
|
|
return attempt_merge(q, rq, next);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int attempt_front_merge(struct request_queue *q, struct request *rq)
|
|
{
|
|
struct request *prev = elv_former_request(q, rq);
|
|
|
|
if (prev)
|
|
return attempt_merge(q, prev, rq);
|
|
|
|
return 0;
|
|
}
|