Fixes for gapped scatters
Subsystems (2)
cover Fixes for gapped scatters
This small set addresses some issues with gapped scattered IOs. The motivation of this is to have the iser driver take advantage of it too.
Patch 1 prevent gapped SG_IO if the driver can't handle gaps. Patches 2,3 prevent gapped integrity payloads (for drivers that support data integrity).
Review is welcome.
Christoph Hellwig (1):
block: copy multi iovec user mappings if QUEUE_FLAG_SG_GAPS is set
Sagi Grimberg (2):
block: Refuse request/bio merges with gaps in the integrity payload
block: Refuse adding appending a gapped integrity page to a bio
block/bio-integrity.c | 9 +++++++++ block/blk-integrity.c | 32 +++++++++++++++++++++++++++----- block/blk-map.c | 4 +++- 3 files changed, 39 insertions(+), 6 deletions(-)
Signature · 1 line
1.8.4.3
PATCH 1/3 block: copy multi iovec user mappings if QUEUE_FLAG_SG_GAPS is set
From: Christoph Hellwig <[email protected]>
For drivers that don't support gaps in the SG lists handed to them we must not create bios from multiple iovecs as they can be (and usually are) discontiguous. This doesn't matter for any current user, but will help to allow iSER which can't handle gaps to use the QUEUE_FLAG_SG_GAPS flag instead of using driver-local bounce buffering.
--- block/blk-map.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-)
block/blk-map.c +3 -1
@@ -85,7 +85,9 @@ int blk_rq_map_user_iov(struct request_queue *q, struct request *rq, unaligned =1 ; } -if (unaligned || (q->dma_pad_mask & iter->count) || map_data) +if (unaligned || map_data || + (q->dma_pad_mask & iter->count) || + ((q->queue_flags & (1 <<QUEUE_FLAG_SG_GAPS ) && iter->nr_segs >1 ))) bio =bio_copy_user_iov (q, map_data, iter, gfp_mask);else bio =bio_map_user_iov (q, iter, gfp_mask);
Signature · 1 line
1.8.4.3
PATCH 1/3 block: copy multi iovec user mappings if QUEUE_FLAG_SG_GAPS is set
Quoted 5 lines · Sagi Grimberg
For drivers that don't support gaps in the SG lists handed to them we must not create bios from multiple iovecs as they can be (and usually are) discontiguous. This doesn't matter for any current user, but will help to allow iSER which can't handle gaps to use the
QUEUE_FLAG_SG_GAPSflag instead of using driver-local bounce buffering.
Maybe SG_GAPS is a bit of a misnomer. There are cases we can directly map a user iov with multiple discontiguous vectors. At least for NVMe it'd work if each iov's base and length are on page boundaries, we don't need to do the indirect copy.
Like with this user example:
---
int fd = open("/dev/nvme0n1", O_RDONLY | O_DIRECT); struct iovec iov[2];
unsigned char *buffer;
posix_memalign((void **)&buffer, 0x4000, 0x1000); iov[0].iov_base = &buffer[0]; iov[1].iov_base = &buffer[0x2000]; iov[0].iov_len = 0x1000; iov[1].iov_len = 0x1000;
readv(fd, iov, 2);
--
Jens Axboe
maintainerPATCH 1/3 block: copy multi iovec user mappings if QUEUE_FLAG_SG_GAPS is set
Quoted 15 lines · Keith Busch
Quoted 6 lines · Sagi Grimberg
For drivers that don't support gaps in the SG lists handed to them we must
not create bios from multiple iovecs as they can be (and usually are) discontiguous. This doesn't matter for any current user, but will help to allow iSER which can't handle gaps to use theQUEUE_FLAG_SG_GAPSflag instead of using driver-local bounce buffering.Maybe
SG_GAPSis a bit of a misnomer. There are cases we can directly map a user iov with multiple discontiguous vectors. At least for NVMe it'd work if each iov's base and length are on page boundaries, we don't need to do the indirect copy.Like with this user example:
---
Quoted 12 lines
int fd = open("/dev/nvme0n1", O_RDONLY | O_DIRECT);
struct iovec iov[2];
unsigned char *buffer;
posix_memalign((void **)&buffer, 0x4000, 0x1000);
iov[0].iov_base = &buffer[0];
iov[1].iov_base = &buffer[0x2000];
iov[0].iov_len = 0x1000;
iov[1].iov_len = 0x1000;
readv(fd, iov, 2);SG_GAPS is a bit of a misnomer, but it's not easy to explain exactly what that logic is in a few short words! SG gaps was the closest I could come up with.
Signature · 1 line
Jens Axboe
PATCH 1/3 block: copy multi iovec user mappings if QUEUE_FLAG_SG_GAPS is set
Quoted 3 lines · Jens Axboe
SG_GAPSis a bit of a misnomer, but it's not easy to explain exactly what that logic is in a few short words! SG gaps was the closest I could come up with.
The sense is wrong -- surely it should be 'NOGAPS'?
Jens Axboe
maintainerPATCH 1/3 block: copy multi iovec user mappings if QUEUE_FLAG_SG_GAPS is set
Quoted 6 lines · Matthew Wilcox
Quoted 3 lines · Jens Axboe
SG_GAPSis a bit of a misnomer, but it's not easy to explain exactly what that logic is in a few short words! SG gaps was the closest I could come up with.The sense is wrong -- surely it should be 'NOGAPS'?
That might make it clearer :-)
Signature · 1 line
Jens Axboe
Christoph Hellwig
maintainerPATCH 1/3 block: copy multi iovec user mappings if QUEUE_FLAG_SG_GAPS is set
In reply to Jens AxboeQuoted 3 lines · Jens Axboe
SG_GAPSis a bit of a misnomer, but it's not easy to explain exactly what that logic is in a few short words! SG gaps was the closest I could come up with.
SG_NO_OFFSETS_INTO_TRAILING_PAGES would desscribe it, but is a little too long..
Christoph Hellwig
maintainerPATCH 1/3 block: copy multi iovec user mappings if QUEUE_FLAG_SG_GAPS is set
In reply to Keith BuschQuoted 11 lines · Keith Busch
Quoted 5 lines · Sagi Grimberg
For drivers that don't support gaps in the SG lists handed to them we must not create bios from multiple iovecs as they can be (and usually are) discontiguous. This doesn't matter for any current user, but will help to allow iSER which can't handle gaps to use the
QUEUE_FLAG_SG_GAPSflag instead of using driver-local bounce buffering.Maybe
SG_GAPSis a bit of a misnomer. There are cases we can directly map a user iov with multiple discontiguous vectors. At least for NVMe it'd work if each iov's base and length are on page boundaries, we don't need to do the indirect copy.
Yes, right now the check is a little pessimistic. If we really care for this use case we can iterate over all iovecs and check if they would introduce offsets into a page.
Note that NVMe never uses blk_rq_map_user_iov with more than a single iov so it doesn't apply there, there concern here is iSER, but the same rules apply there as well.
PATCH 2/3 block: Refuse request/bio merges with gaps in the integrity payload
In reply to Sagi GrimbergIf a driver turned on QUEUE_FLAG_SG_GAPS, it means that it cannot handle gaps so we must not allow those in the integrity payload as well.
--- block/blk-integrity.c | 32 +++++++++++++++++++++++++++----- 1 file changed, 27 insertions(+), 5 deletions(-)
block/blk-integrity.c +27 -5
@@ -189,20 +189,31 @@ EXPORT_SYMBOL(blk_integrity_compare);bool blk_integrity_merge_rq (struct request_queue *q,struct request *req,struct request *next) { +struct bio_integrity_payload *bip =bio_integrity (req->bio); +struct bio_integrity_payload *bip_next =bio_integrity (next->bio); +if (blk_integrity_rq (req) ==0 &&blk_integrity_rq (next) ==0 )return true;if (blk_integrity_rq (req) ==0 ||blk_integrity_rq (next) ==0 )return false; -if (bio_integrity (req->bio)->bip_flags != -bio_integrity (next->bio)->bip_flags) +if (bip->bip_flags != bip_next->bip_flags)return false;if (req->nr_integrity_segments + next->nr_integrity_segments > q->limits.max_integrity_segments)return false; +if (q->queue_flags & (1 <<QUEUE_FLAG_SG_GAPS )) { +struct bio_vec *iv, *iv_next; + + iv = &bip->bip_vec[bip->bip_vcnt]; + iv_next = &bip_next->bip_vec[0 ]; +if (bvec_gap_to_prev (iv, iv_next->bv_offset)) +return false; + } +return true; }EXPORT_SYMBOL (blk_integrity_merge_rq);
@@ -212,16 +223,27 @@ bool blk_integrity_merge_bio(struct request_queue *q, struct request *req, {int nr_integrity_segs;struct bio *next = bio->bi_next; +struct bio_integrity_payload *bip =bio_integrity (req->bio); +struct bio_integrity_payload *bip_next =bio_integrity (bio); -if (blk_integrity_rq (req) ==0 &&bio_integrity (bio) ==NULL ) +if (blk_integrity_rq (req) ==0 && bip_next ==NULL )return true; -if (blk_integrity_rq (req) ==0 ||bio_integrity (bio) ==NULL ) +if (blk_integrity_rq (req) ==0 || bip_next ==NULL )return false; -if (bio_integrity (req->bio)->bip_flags !=bio_integrity (bio)->bip_flags) +if (bip->bip_flags != bip_next->bip_flags)return false; +if (q->queue_flags & (1 <<QUEUE_FLAG_SG_GAPS )) { +struct bio_vec *iv, *iv_next; + + iv = &bip->bip_vec[bip->bip_vcnt -1 ]; + iv_next = &bip_next->bip_vec[bip_next->bip_vcnt]; +if (bvec_gap_to_prev (iv, iv_next->bv_offset)) +return false; + } + bio->bi_next =NULL ; nr_integrity_segs =blk_rq_count_integrity_sg (q, bio); bio->bi_next = next;
Signature · 1 line
1.8.4.3
PATCH 3/3 block: Refuse adding appending a gapped integrity page to a bio
In reply to Sagi GrimbergThis is only theoretical at the moment given that the only subsystems that generate integrity payloads are the block layer itself and the scsi target. But when we will expose integrity meta-data to user-space, we'll need to refuse appending a page with a gap when QUEUE_FLAG_SG_GAPS flag is on.
--- block/bio-integrity.c | 9 +++++++++ 1 file changed, 9 insertions(+)
block/bio-integrity.c +9 -0
@@ -140,6 +140,15 @@ int bio_integrity_add_page(struct bio *bio, struct page *page, iv = bip->bip_vec + bip->bip_vcnt; +if (bip->bip_vcnt) { +struct request_queue *q =bdev_get_queue (bio->bi_bdev); +struct bio_vec *iv_prev = &bip->bip_vec[bip->bip_vcnt -1 ]; + +if (q->queue_flags & (1 <<QUEUE_FLAG_SG_GAPS ) && +bvec_gap_to_prev (iv_prev, offset)) +return 0 ; + } + iv->bv_page = page; iv->bv_len = len; iv->bv_offset = offset;
Signature · 1 line
1.8.4.3
Jens Axboe
maintainerPATCH 3/3 block: Refuse adding appending a gapped integrity page to a bio
Quoted 17 lines · Sagi Grimberg
This is only theoretical at the moment given that the only subsystems that generate integrity payloads are the block layer itself and the scsi target. But when we will expose integrity meta-data to user-space, we'll need to refuse appending a page with a gap when
QUEUE_FLAG_SG_GAPSflag is on.Signed-off-by Sagi Grimberg <sagig at mellanox.com>--- block/bio-integrity.c | 9 +++++++++ 1 file changed, 9 insertions(+)block/bio-integrity.c +0
-0@@ -140,6 +140,15 @@ int bio_integrity_add_page(struct bio *bio, struct page *page,
block/bio-integrity.c int bio_integrity_add_page(struct bio *bio, struct page *page, · line 150
Quoted 10 lines
block/bio-integrity.c +8
-0iv = bip->bip_vec + bip->bip_vcnt; +if (bip->bip_vcnt) { +struct request_queue *q =bdev_get_queue (bio->bi_bdev); +struct bio_vec *iv_prev = &bip->bip_vec[bip->bip_vcnt -1 ]; + +if (q->queue_flags & (1 <<QUEUE_FLAG_SG_GAPS ) && +bvec_gap_to_prev (iv_prev, offset)) +return 0 ; + }
This chunk of code largely ends up being duplicated in several places with your patches, might be a good idea to pull that into a helper function. Then you can add the comment in one place as well :-)
Signature · 1 line
Jens Axboe
PATCH 3/3 block: Refuse adding appending a gapped integrity page to a bio
Quoted 3 lines · Jens Axboe
This chunk of code largely ends up being duplicated in several places with your patches, might be a good idea to pull that into a helper function. Then you can add the comment in one place as well :-)
Basically all the bio_vec-related code in block-integrity is copy & paste of the I/O path. In the long run we'll need to refactor it by adding bio_vec iteration helpers that can pick different anchors from the bio. But for now I think I'd rather have this quick fix.
Jens Axboe
maintainerPATCH 3/3 block: Refuse adding appending a gapped integrity page to a bio
Quoted 9 lines · Christoph Hellwig
Quoted 3 lines · Jens Axboe
This chunk of code largely ends up being duplicated in several places with your patches, might be a good idea to pull that into a helper function. Then you can add the comment in one place as well :-)
Basically all the bio_vec-related code in block-integrity is copy & paste of the I/O path. In the long run we'll need to refactor it by adding
bio_veciteration helpers that can pick different anchors from the bio. But for now I think I'd rather have this quick fix.
I wasn't asking for a refactor of the bio_vec integrity code (though that would be nice...), just that we don't add 3 more instances of 'does this gap with previous'.
Signature · 1 line
Jens Axboe
PATCH 3/3 block: Refuse adding appending a gapped integrity page to a bio
Quoted 8 lines · Jens Axboe
Quoted 7 lines · Christoph Hellwig
Quoted 5 lines · Jens Axboe
This chunk of code largely ends up being duplicated in several places with
your patches, might be a good idea to pull that into a helper function. Then
you can add the comment in one place as well :-)
Quoted 9 lines
Quoted 4 lines
Basically all the bio_vec-related code in block-integrity is copy & paste of the I/O path. In the long run we'll need to refactor it by adding
bio_veciteration helpers that can pick different anchors from the bio. But for now I think I'd rather have this quick fix.I wasn't asking for a refactor of the
bio_vecintegrity code (though that would be nice...), just that we don't add 3 more instances of 'does this gap with previous'.
Hi Jens,
I'll centralize the checks. No problem.
Oddly enough I still see "gappy" IO when I'm running in the non-mq path. I suspect it's coming from the elevator code somehow...
I assume no one tried to run it on non-mq right?
Sagi.
PATCH 3/3 block: Refuse adding appending a gapped integrity page to a bio
Quoted 1 line · Sagi Grimberg
I'll centralize the checks. No problem.
Did you get a chance to respin these patches? It would be good to get them in this merge window.
PATCH 3/3 block: Refuse adding appending a gapped integrity page to a bio
Quoted 6 lines · Christoph Hellwig
Quoted 1 line · Sagi Grimberg
I'll centralize the checks. No problem.
Did you get a chance to respin these patches? It would be good to get them in this merge window.
Actually I didn't. I started to, but then I noticed that I was still seeing gaps when using cfq (e.g. non-mq code path), I assume that this was never tested?
Since I was busy with the iser-target iWARP support with Steve so I didn't get a chance to get back to it yet...
Sagi.
PATCH 3/3 block: Refuse adding appending a gapped integrity page to a bio
Quoted 3 lines · Sagi Grimberg
Actually I didn't. I started to, but then I noticed that I was still seeing gaps when using cfq (e.g. non-mq code path), I assume that this was never tested?
It probably wasn't. The only user so far is the NVMe driver which is blk-mq only.
Quoted 2 lines
Since I was busy with the iser-target iWARP support with Steve so I didn't get a chance to get back to it yet...
Ok.
PATCH 3/3 block: Refuse adding appending a gapped integrity page to a bio
Quoted 7 lines · Christoph Hellwig
Quoted 3 lines · Sagi Grimberg
Actually I didn't. I started to, but then I noticed that I was still seeing gaps when using cfq (e.g. non-mq code path), I assume that this was never tested?
It probably wasn't. The only user so far is the NVMe driver which is blk-mq only.
So I got back to have a look on this. I originally thought that this issue was specific to io schedulers, but I don't think it is anymore, its just easier to trigger with io schedulers.
It seems we are only protecting against gapped back merges (i.e. appending a gapped bio to a request biotail) but we are not protecting against front merges (i.e. adding a gapped bio to request as the bio head).
Imagine we have two bio_vec elements and the queue boundary is 0xfff:req_bvec: offset=0xe00 length=0x200bio_bvec: offset=0x0 length=0x200
bvec_gap_to_prev() will allow back merging {req_bvec, bio_bvec} as bio_vec->offset=0x0 and req_bvec->offset + req_bvec->length is aligned to the queue boundary, but the problem is we might do a front merge {bio_bvec, req_bvec} which gives us a gap.
I'm able to reproduce this with iser with 512B sequential reads (encourage gapped merges) but I wonder how this issue was missed in nvme (the code seem to allow front merges)?
Anyway, the patch below seems to solved the issue for me:
Comments?
--
commit 100383e208045368b4e3ac20e3fa46bdad966df2
Author: Sagi Grimberg <sagig at mellanox.com> Date: Wed Sep 2 01:31:39 2015 +0300
block: Protect against front merges with gaps
We seem to have missed gaps detection on front merges. Introduce req_gap_to_next call that is should detect a gap in a front merge.
block/blk-merge.c +0 -0
@@ -327,9 +327,20 @@ int ll_back_merge_fn(struct request_queue *q,
struct request *req,
return ll_new_hw_segment(q, req, bio);
}
+static int req_gap_to_next(struct request *req, struct bio *bio) +{
+ struct bio *next = req->bio;
+
+ return bvec_gap_to_prev(req->q, &bio->bi_io_vec[bio->bi_vcnt - 1],
+ next->bi_io_vec[0].bv_offset);
+}
+
int ll_front_merge_fn(struct request_queue *q, struct request *req,
struct bio *bio)
{
+ if (req_gap_to_next(req, bio))
+ return 0;
+
if (blk_rq_sectors(req) + bio_sectors(bio) >
blk_rq_get_max_sectors(req)) {
req->cmd_flags |= REQ_NOMERGE;--
Jens Axboe
maintainerPATCH 3/3 block: Refuse adding appending a gapped integrity page to a bio
Quoted 27 lines · Sagi Grimberg
Quoted 7 lines · Christoph Hellwig
Quoted 3 lines · Sagi Grimberg
Actually I didn't. I started to, but then I noticed that I was still seeing gaps when using cfq (e.g. non-mq code path), I assume that this was never tested?
It probably wasn't. The only user so far is the NVMe driver which is blk-mq only.
So I got back to have a look on this. I originally thought that this issue was specific to io schedulers, but I don't think it is anymore, its just easier to trigger with io schedulers.
It seems we are only protecting against gapped back merges (i.e. appending a gapped bio to a request biotail) but we are not protecting against front merges (i.e. adding a gapped bio to request as the bio head).
Imagine we have two
bio_vecelements and the queue boundary is0xfff:req_bvec: offset=0xe00 length=0x200bio_bvec: offset=0x0 length=0x200
bvec_gap_to_prev()will allow back merging {req_bvec,bio_bvec} as bio_vec->offset=0x0 andreq_bvec->offset+req_bvec->lengthis aligned to the queue boundary, but the problem is we might do a front merge {bio_bvec,req_bvec} which gives us a gap.
Quoted 7 lines
I'm able to reproduce this with iser with 512B sequential reads (encourage gapped merges) but I wonder how this issue was missed in nvme (the code seem to allow front merges)?
Anyway, the patch below seems to solved the issue for me:
Comments?
Weird, I guess front merging was overlooked when the initial patch was added. Looks correct to me, and as far as I can see, we have now got all bases covered.
But there's room for some cleanup now, where we check is a bit of a mess. If we kill the check in blk_rq_merge_ok() and only do them in the front/back merge points (and the req-to-req case), then I think that would be an improvement. Does the below work for you?
block/blk-merge.c +34 -20
@@ -309,9 +309,39 @@ no_merge:return 0 ; } +static bool queue_gap_check (struct request_queue *q,struct bio *bio) +{ +if (q->queue_flags & (1 <<QUEUE_FLAG_SG_GAPS ) &&bio_has_data (bio)) +return true; + +return false; +} + +static bool bio_gap_to_prev (struct bio *prev,struct bio *next) +{ +return bvec_gap_to_prev (&prev->bi_io_vec[prev->bi_vcnt -1 ], + next->bi_io_vec[0 ].bv_offset); +} + +static bool req_gap_to_prev (struct request *req,struct bio *bio) +{ +return bio_gap_to_prev (req->biotail, bio); +} + +static bool req_gap_to_next (struct request *req,struct bio *bio) +{ +struct bio *next = req->bio; + +return bvec_gap_to_prev (&bio->bi_io_vec[bio->bi_vcnt -1 ], + next->bi_io_vec[0 ].bv_offset); +} +int ll_back_merge_fn (struct request_queue *q,struct request *req,struct bio *bio) { +if (queue_gap_check (q, bio) &&req_gap_to_prev (req, bio)) +return 0 ; +if (blk_rq_sectors (req) +bio_sectors (bio) >blk_rq_get_max_sectors (req)) { req->cmd_flags |=REQ_NOMERGE ;
@@ -330,6 +360,9 @@ int ll_back_merge_fn(struct request_queue *q, struct request *req,int ll_front_merge_fn (struct request_queue *q,struct request *req,struct bio *bio) { +if (queue_gap_check (q, bio) &&req_gap_to_next (req, bio)) +return 0 ; +if (blk_rq_sectors (req) +bio_sectors (bio) >blk_rq_get_max_sectors (req)) { req->cmd_flags |=REQ_NOMERGE ;
@@ -356,14 +389,6 @@ static bool req_no_special_merge(struct request *req)return !q->mq_ops && req->special; } -static int req_gap_to_prev (struct request *req,struct request *next) -{ -struct bio *prev = req->biotail; - -return bvec_gap_to_prev (&prev->bi_io_vec[prev->bi_vcnt -1 ], - next->bio->bi_io_vec[0 ].bv_offset); -} -static int ll_merge_requests_fn (struct request_queue *q,struct request *req,struct request *next) {
@@ -379,7 +404,7 @@ static int ll_merge_requests_fn(struct request_queue *q, struct request *req,return 0 ;if (test_bit (QUEUE_FLAG_SG_GAPS , &q->queue_flags) && -req_gap_to_prev (req, next)) +req_gap_to_prev (req, next->bio))return 0 ;/*
@@ -564,8 +589,6 @@ int blk_attempt_req_merge(struct request_queue *q, struct request *rq,bool blk_rq_merge_ok (struct request *rq,struct bio *bio) { -struct request_queue *q = rq->q; -if (!rq_mergeable (rq) || !bio_mergeable (bio))return false;
@@ -589,15 +612,6 @@ bool blk_rq_merge_ok(struct request *rq, struct bio *bio) !blk_write_same_mergeable(rq->bio, bio))return false; -/* Only check gaps if the bio carries data */ -if (q->queue_flags & (1 <<QUEUE_FLAG_SG_GAPS ) &&bio_has_data (bio)) { -struct bio_vec *bprev; - - bprev = &rq->biotail->bi_io_vec[rq->biotail->bi_vcnt -1 ]; -if (bvec_gap_to_prev (bprev, bio->bi_io_vec[0 ].bv_offset)) -return false; - } -return true; }
Signature · 1 line
Jens Axboe
PATCH 3/3 block: Refuse adding appending a gapped integrity page to a bio
Quoted 36 lines · Jens Axboe
Quoted 27 lines · Sagi Grimberg
Quoted 7 lines · Christoph Hellwig
Quoted 3 lines · Sagi Grimberg
Actually I didn't. I started to, but then I noticed that I was still seeing gaps when using cfq (e.g. non-mq code path), I assume that this was never tested?
It probably wasn't. The only user so far is the NVMe driver which is blk-mq only.
So I got back to have a look on this. I originally thought that this issue was specific to io schedulers, but I don't think it is anymore, its just easier to trigger with io schedulers.
It seems we are only protecting against gapped back merges (i.e. appending a gapped bio to a request biotail) but we are not protecting against front merges (i.e. adding a gapped bio to request as the bio head).
Imagine we have two
bio_vecelements and the queue boundary is0xfff:req_bvec: offset=0xe00 length=0x200bio_bvec: offset=0x0 length=0x200
bvec_gap_to_prev()will allow back merging {req_bvec,bio_bvec} as bio_vec->offset=0x0 andreq_bvec->offset+req_bvec->lengthis aligned to the queue boundary, but the problem is we might do a front merge {bio_bvec,req_bvec} which gives us a gap.Quoted 7 lines
I'm able to reproduce this with iser with 512B sequential reads (encourage gapped merges) but I wonder how this issue was missed in nvme (the code seem to allow front merges)?
Anyway, the patch below seems to solved the issue for me:
Comments?
Quoted 8 lines
Weird, I guess front merging was overlooked when the initial patch was added. Looks correct to me, and as far as I can see, we have now got all bases covered.
But there's room for some cleanup now, where we check is a bit of a mess. If we kill the check in
blk_rq_merge_ok()and only do them in the front/back merge points (and the req-to-req case), then I think that would be an improvement.
Yea, we do get to checking back merges even for front merges in this point...
Quoted 1 line
Does the below work for you?
It's not on top of Keith virt_boundary patch right? First glance looks ok though.
Quoted 111 lines
block/blk-merge.c +33
-0@@ -309,9 +309,39 @@ no_merge:return 0 ; } +static bool queue_gap_check (struct request_queue *q,struct bio *bio) +{ +if (q->queue_flags & (1 <<QUEUE_FLAG_SG_GAPS ) &&bio_has_data (bio)) +return true; + +return false; +} + +static bool bio_gap_to_prev (struct bio *prev,struct bio *next) +{ +return bvec_gap_to_prev (&prev->bi_io_vec[prev->bi_vcnt -1 ], + next->bi_io_vec[0 ].bv_offset); +} + +static bool req_gap_to_prev (struct request *req,struct bio *bio) +{ +return bio_gap_to_prev (req->biotail, bio); +} + +static bool req_gap_to_next (struct request *req,struct bio *bio) +{ +struct bio *next = req->bio; + +return bvec_gap_to_prev (&bio->bi_io_vec[bio->bi_vcnt -1 ], + next->bi_io_vec[0 ].bv_offset); +} +int ll_back_merge_fn (struct request_queue *q,struct request *req,struct bio *bio) { +if (queue_gap_check (q, bio) &&req_gap_to_prev (req, bio)) +return 0 ; +if (blk_rq_sectors (req) +bio_sectors (bio) >blk_rq_get_max_sectors (req)) { req->cmd_flags |=REQ_NOMERGE ;@@ -330,6 +360,9 @@ int ll_back_merge_fn(struct request_queue *q, struct request *req, int ll_front_merge_fn(struct request_queue *q, struct request *req, struct bio *bio) { + if (queue_gap_check(q, bio) && req_gap_to_next(req, bio)) + return 0; + if (blk_rq_sectors(req) + bio_sectors(bio) > blk_rq_get_max_sectors(req)) {req->cmd_flags |= REQ_NOMERGE;block/blk-merge.c +0
-8@@ -356,14 +389,6 @@ static bool req_no_special_merge(struct request *req)return !q->mq_ops && req->special; } -static int req_gap_to_prev (struct request *req,struct request *next) -{ -struct bio *prev = req->biotail; - -return bvec_gap_to_prev (&prev->bi_io_vec[prev->bi_vcnt -1 ], - next->bio->bi_io_vec[0 ].bv_offset); -} -static int ll_merge_requests_fn(struct
request_queue*q, struct request *req,
struct request *next)
{block/blk-merge.c +1
-1@@ -379,7 +404,7 @@ static int ll_merge_requests_fn(struct request_queue *q, struct request *req, return 0;if (test_bit (QUEUE_FLAG_SG_GAPS , &q->queue_flags) && -req_gap_to_prev (req, next)) +req_gap_to_prev (req, next->bio))return 0 ;/*block/blk-merge.c +0
-11@@ -564,8 +589,6 @@ int blk_attempt_req_merge(struct request_queue *q, struct request *rq, bool blk_rq_merge_ok(struct request *rq, struct bio *bio) { - struct request_queue *q = rq->q; -if (!rq_mergeable (rq) || !bio_mergeable (bio))return false;@@ -589,15 +612,6 @@ bool blk_rq_merge_ok(struct request *rq, struct bio *bio) !blk_write_same_mergeable(rq->bio, bio))return false; -/* Only check gaps if the bio carries data */ -if (q->queue_flags & (1 <<QUEUE_FLAG_SG_GAPS ) &&bio_has_data (bio)) { -struct bio_vec *bprev; - - bprev = &rq->biotail->bi_io_vec[rq->biotail->bi_vcnt -1 ]; -if (bvec_gap_to_prev (bprev, bio->bi_io_vec[0 ].bv_offset)) -return false; - } -return true; }
Jens Axboe
maintainerPATCH 3/3 block: Refuse adding appending a gapped integrity page to a bio
Quoted 41 lines · Sagi Grimberg
Quoted 40 lines · Jens Axboe
Quoted 27 lines · Sagi Grimberg
Quoted 7 lines · Christoph Hellwig
Quoted 3 lines · Sagi Grimberg
Actually I didn't. I started to, but then I noticed that I was still seeing gaps when using cfq (e.g. non-mq code path), I assume that this was never tested?
It probably wasn't. The only user so far is the NVMe driver which is blk-mq only.
So I got back to have a look on this. I originally thought that this issue was specific to io schedulers, but I don't think it is anymore, its just easier to trigger with io schedulers.
It seems we are only protecting against gapped back merges (i.e. appending a gapped bio to a request biotail) but we are not protecting against front merges (i.e. adding a gapped bio to request as the bio head).
Imagine we have two
bio_vecelements and the queue boundary is0xfff:req_bvec: offset=0xe00 length=0x200bio_bvec: offset=0x0 length=0x200
bvec_gap_to_prev()will allow back merging {req_bvec,bio_bvec} as bio_vec->offset=0x0 andreq_bvec->offset+req_bvec->lengthis aligned to the queue boundary, but the problem is we might do a front merge {bio_bvec,req_bvec} which gives us a gap.Quoted 7 lines
I'm able to reproduce this with iser with 512B sequential reads (encourage gapped merges) but I wonder how this issue was missed in nvme (the code seem to allow front merges)?
Anyway, the patch below seems to solved the issue for me:
Comments?
Weird, I guess front merging was overlooked when the initial patch was added. Looks correct to me, and as far as I can see, we have now got all bases covered.
Quoted 8 lines
Quoted 4 lines
But there's room for some cleanup now, where we check is a bit of a mess. If we kill the check in
blk_rq_merge_ok()and only do them in the front/back merge points (and the req-to-req case), then I think that would be an improvement.Yea, we do get to checking back merges even for front merges in this point...
Yup
Quoted 4 lines
Quoted 1 line
Does the below work for you?
It's not on top of Keith
virt_boundarypatch right? First glance looks ok though.
Looks like I was on the wrong branch, master. Let me update it for post that patch.
Signature · 1 line
Jens Axboe
Jens Axboe
maintainerPATCH 3/3 block: Refuse adding appending a gapped integrity page to a bio
In reply to Sagi GrimbergQuoted 4 lines · Sagi Grimberg
Quoted 1 line
Does the below work for you?
It's not on top of Keith
virt_boundarypatch right? First glance looks ok though.
Updated for that.
block/blk-merge.c +21 -12
@@ -436,9 +436,27 @@ no_merge:return 0 ; } +static bool req_gap_to_prev (struct request *req,struct bio *next) +{ +struct bio *prev = req->biotail; + +return bvec_gap_to_prev (req->q, &prev->bi_io_vec[prev->bi_vcnt -1 ], + next->bi_io_vec[0 ].bv_offset); +} + +static bool req_gap_to_next (struct request *req,struct bio *bio) +{ +struct bio *next = req->bio; + +return bvec_gap_to_prev (req->q, &bio->bi_io_vec[bio->bi_vcnt -1 ], + next->bi_io_vec[0 ].bv_offset); +} +int ll_back_merge_fn (struct request_queue *q,struct request *req,struct bio *bio) { +if (bio_has_data (bio) &&req_gap_to_prev (req, bio)) +return 0 ;if (blk_rq_sectors (req) +bio_sectors (bio) >blk_rq_get_max_sectors (req)) { req->cmd_flags |=REQ_NOMERGE ;
@@ -457,6 +475,9 @@ int ll_back_merge_fn(struct request_queue *q, struct request *req,int ll_front_merge_fn (struct request_queue *q,struct request *req,struct bio *bio) { + +if (bio_has_data (bio) &&req_gap_to_next (req, bio)) +return 0 ;if (blk_rq_sectors (req) +bio_sectors (bio) >blk_rq_get_max_sectors (req)) { req->cmd_flags |=REQ_NOMERGE ;
@@ -483,14 +504,6 @@ static bool req_no_special_merge(struct request *req)return !q->mq_ops && req->special; } -static int req_gap_to_prev (struct request *req,struct bio *next) -{ -struct bio *prev = req->biotail; - -return bvec_gap_to_prev (req->q, &prev->bi_io_vec[prev->bi_vcnt -1 ], - next->bi_io_vec[0 ].bv_offset); -} -static int ll_merge_requests_fn (struct request_queue *q,struct request *req,struct request *next) {
@@ -713,10 +726,6 @@ bool blk_rq_merge_ok(struct request *rq, struct bio *bio) !blk_write_same_mergeable(rq->bio, bio))return false; -/* Only check gaps if the bio carries data */ -if (bio_has_data (bio) &&req_gap_to_prev (rq, bio)) -return false; -return true; }
Signature · 1 line
Jens Axboe
PATCH 3/3 block: Refuse adding appending a gapped integrity page to a bio
Quoted 8 lines · Jens Axboe
Quoted 4 lines · Sagi Grimberg
Quoted 1 line
Does the below work for you?
It's not on top of Keith
virt_boundarypatch right? First glance looks ok though.Updated for that.
Thanks Jens,
I'll test it.
Quoted 34 lines
block/blk-merge.c +18
-0@@ -436,9 +436,27 @@ no_merge:return 0 ; } +static bool req_gap_to_prev (struct request *req,struct bio *next) +{ +struct bio *prev = req->biotail; + +return bvec_gap_to_prev (req->q, &prev->bi_io_vec[prev->bi_vcnt -1 ], + next->bi_io_vec[0 ].bv_offset); +} + +static bool req_gap_to_next (struct request *req,struct bio *bio) +{ +struct bio *next = req->bio; + +return bvec_gap_to_prev (req->q, &bio->bi_io_vec[bio->bi_vcnt -1 ], + next->bi_io_vec[0 ].bv_offset); +} +int ll_back_merge_fn (struct request_queue *q,struct request *req,struct bio *bio) { +if (bio_has_data (bio) &&req_gap_to_prev (req, bio)) +return 0 ;if (blk_rq_sectors (req) +bio_sectors (bio) >blk_rq_get_max_sectors (req)) { req->cmd_flags |=REQ_NOMERGE ;
block/blk-merge.c int ll_back_merge_fn(struct request_queue *q, struct request *req, · line 480
Quoted 6 lines
block/blk-merge.c +3
-0@@ -457,6 +475,9 @@ int ll_back_merge_fn(struct request_queue *q, struct request *req,int ll_front_merge_fn (struct request_queue *q,struct request *req,struct bio *bio) { + +if (bio_has_data (bio) &&req_gap_to_next (req, bio)) +return 0 ;
I think it will be cleaner to git bio_has_data(bio) check into req_gap_to_[prev|next]
Jens Axboe
maintainerPATCH 3/3 block: Refuse adding appending a gapped integrity page to a bio
Quoted 50 lines · Sagi Grimberg
Quoted 8 lines · Jens Axboe
Quoted 4 lines · Sagi Grimberg
Quoted 1 line
Does the below work for you?
It's not on top of Keith
virt_boundarypatch right? First glance looks ok though.Updated for that.
Thanks Jens,
I'll test it.
Quoted 36 lines
block/blk-merge.c +18
-0@@ -436,9 +436,27 @@ no_merge:return 0 ; } +static bool req_gap_to_prev (struct request *req,struct bio *next) +{ +struct bio *prev = req->biotail; + +return bvec_gap_to_prev (req->q, &prev->bi_io_vec[prev->bi_vcnt -1 ], + next->bi_io_vec[0 ].bv_offset); +} + +static bool req_gap_to_next (struct request *req,struct bio *bio) +{ +struct bio *next = req->bio; + +return bvec_gap_to_prev (req->q, &bio->bi_io_vec[bio->bi_vcnt -1 ], + next->bi_io_vec[0 ].bv_offset); +} +int ll_back_merge_fn (struct request_queue *q,struct request *req,struct bio *bio) { +if (bio_has_data (bio) &&req_gap_to_prev (req, bio)) +return 0 ;if (blk_rq_sectors (req) +bio_sectors (bio) >blk_rq_get_max_sectors (req)) { req->cmd_flags |=REQ_NOMERGE ;@@ -457,6 +475,9 @@ int ll_back_merge_fn(struct request_queue *q, struct request *req, int ll_front_merge_fn(struct request_queue *q, struct request *req,
Quoted 8 lines
block/blk-merge.c int ll_back_merge_fn(struct request_queue *q, struct request *req, · line 480
Quoted 5 lines
block/blk-merge.c +3
-0struct bio *bio) { + +if (bio_has_data (bio) &&req_gap_to_next (req, bio)) +return 0 ;I think it will be cleaner to git
bio_has_data(bio)check into req_gap_to_[prev|next]
Yeah I agree, I'll make that change.
Signature · 1 line
Jens Axboe
Jens Axboe
maintainerPATCH 3/3 block: Refuse adding appending a gapped integrity page to a bio
In reply to Sagi GrimbergQuoted 7 lines · Sagi Grimberg
Quoted 6 lines · Jens Axboe
Quoted 4 lines · Sagi Grimberg
Quoted 1 line
Does the below work for you?
It's not on top of Keith
virt_boundarypatch right? First glance looks ok though.
Quoted 6 lines
Quoted 2 lines
Updated for that.
Thanks Jens,
I'll test it.
Cleaned up version, unified the gap checking and changed the names of the function to make it easier to understand. And then we only need to check bio_has_data() in bio_will_gap().
Functionally it should be identical to the previous one.
block/blk-merge.c +26 -13
@@ -435,9 +435,31 @@ no_merge:return 0 ; } +static bool bio_will_gap (struct request_queue *q,struct bio *prev, +struct bio *next) +{ +if (!bio_has_data (prev)) +return false; + +return bvec_gap_to_prev (q, &prev->bi_io_vec[prev->bi_vcnt -1 ], + next->bi_io_vec[0 ].bv_offset); +} + +static bool req_gap_back_merge (struct request *req,struct bio *next) +{ +return bio_will_gap (req->q, req->biotail, next); +} + +static bool req_gap_front_merge (struct request *req,struct bio *bio) +{ +return bio_will_gap (req->q, bio, req->bio); +} +int ll_back_merge_fn (struct request_queue *q,struct request *req,struct bio *bio) { +if (req_gap_back_merge (req, bio)) +return 0 ;if (blk_rq_sectors (req) +bio_sectors (bio) >blk_rq_get_max_sectors (req)) { req->cmd_flags |=REQ_NOMERGE ;
@@ -456,6 +478,9 @@ int ll_back_merge_fn(struct request_queue *q, struct request *req,int ll_front_merge_fn (struct request_queue *q,struct request *req,struct bio *bio) { + +if (req_gap_front_merge (req, bio)) +return 0 ;if (blk_rq_sectors (req) +bio_sectors (bio) >blk_rq_get_max_sectors (req)) { req->cmd_flags |=REQ_NOMERGE ;
@@ -482,14 +507,6 @@ static bool req_no_special_merge(struct request *req)return !q->mq_ops && req->special; } -static int req_gap_to_prev (struct request *req,struct bio *next) -{ -struct bio *prev = req->biotail; - -return bvec_gap_to_prev (req->q, &prev->bi_io_vec[prev->bi_vcnt -1 ], - next->bi_io_vec[0 ].bv_offset); -} -static int ll_merge_requests_fn (struct request_queue *q,struct request *req,struct request *next) {
@@ -504,7 +521,7 @@ static int ll_merge_requests_fn(struct request_queue *q, struct request *req,if (req_no_special_merge (req) ||req_no_special_merge (next))return 0 ; -if (req_gap_to_prev (req, next->bio)) +if (req_gap_back_merge (req, next->bio))return 0 ;/*
@@ -712,10 +729,6 @@ bool blk_rq_merge_ok(struct request *rq, struct bio *bio) !blk_write_same_mergeable(rq->bio, bio))return false; -/* Only check gaps if the bio carries data */ -if (bio_has_data (bio) &&req_gap_to_prev (rq, bio)) -return false; -return true; }
Signature · 1 line
Jens Axboe
PATCH 3/3 block: Refuse adding appending a gapped integrity page to a bio
Quoted 11 lines · Jens Axboe
Quoted 10 lines · Sagi Grimberg
Quoted 8 lines · Jens Axboe
Quoted 4 lines · Sagi Grimberg
Quoted 1 line
Does the below work for you?
It's not on top of Keith
virt_boundarypatch right? First glance looks ok though.Updated for that.
Quoted 7 lines
Quoted 3 lines
Thanks Jens,
I'll test it.
Cleaned up version, unified the gap checking and changed the names of the function to make it easier to understand. And then we only need to check
bio_has_data()inbio_will_gap().
:)
I was just going to say that maybe we should keep the check explicit..
I am going to fix this also for integrity payload [1], and there I need to check for blk_integrity_rq(req) explicitly because it doesn't really makes sense to call an integrity gap checker if you don't have integrity...
Also, lets move the gap helpers to be static inline in blkdev.h so I can put my integrity gap helpers there too.
Anyway, its just cosmetics...
Let me include your patch in a series I'm planning on sending soon enough as I don't see merges anymore so I guess the issue is gone now.
[1]:
commit 48679ce5f6ffe6827d40287b521ea139cdf95ff7
Author: Sagi Grimberg <sagig at mellanox.com> Date: Wed Jul 15 15:06:04 2015 +0300
block: Refuse request/bio merges with gaps in the integrity payload
If a driver sets the block queue virtual boundary, it means that it cannot handle gaps so we must not allow those in the integrity payload as well.
block/blk-integrity.c +0 -0
@@ -204,6 +204,9 @@ bool blk_integrity_merge_rq(struct request_queue *q,
struct request *req,
q->limits.max_integrity_segments)
return false;+ if (integrity_req_gap_to_prev(req, next->bio))
+ return false;
+
return true;
}
EXPORT_SYMBOL(blk_integrity_merge_rq);
block/blk-merge.c +0 -0
@@ -315,6 +315,10 @@ int ll_back_merge_fn(struct request_queue *q,
struct request *req,
if (bio_has_data(bio) && req_gap_to_prev(req, bio))
return 0;+ if (blk_integrity_rq(req) &&
+ integrity_req_gap_to_prev(req, bio))
+ return 0;
+
if (blk_rq_sectors(req) + bio_sectors(bio) >
blk_rq_get_max_sectors(req)) {
blk_rq_get_max_sectors(req)) {
req->cmd_flags |= REQ_NOMERGE;
@@ -336,6 +340,10 @@ int ll_front_merge_fn(struct request_queue *q,
struct request *req,
if (bio_has_data(bio) && req_gap_to_next(req, bio))
return 0;+ if (blk_integrity_rq(req) &&
+ integrity_req_gap_to_next(req, bio))
+ return 0;
+
if (blk_rq_sectors(req) + bio_sectors(bio) >
blk_rq_get_max_sectors(req)) {
req->cmd_flags |= REQ_NOMERGE;include/linux/blkdev.h +0 -0
@@ -1389,6 +1389,26 @@ static inline bool req_gap_to_next(struct request
*req, struct bio *bio)
next->bi_io_vec[0].bv_offset);
}
+static inline bool integrity_req_gap_to_prev(struct request *req,
+ struct bio *next)
+{
+ struct bio_integrity_payload *bip = bio_integrity(req->bio);
+ struct bio_integrity_payload *bip_next = bio_integrity(next);
+
+ return bvec_gap_to_prev(req->q, &bip->bip_vec[bip->bip_vcnt - 1],
+ bip_next->bip_vec[0].bv_offset);
+}
+
+static inline bool integrity_req_gap_to_next(struct request *req,
+ struct bio *bio)
+{
+ struct bio_integrity_payload *bip = bio_integrity(bio);
+ struct bio_integrity_payload *bip_next = bio_integrity(req->bio);
+
+ return bvec_gap_to_prev(req->q, &bip->bip_vec[bip->bip_vcnt - 1],
+ bip_next->bip_vec[0].bv_offset);
+}
struct work_struct;
int kblockd_schedule_work(struct work_struct *work);
int kblockd_schedule_delayed_work(struct delayed_work *dwork, unsigned
long delay);
--
Jens Axboe
maintainerPATCH 3/3 block: Refuse adding appending a gapped integrity page to a bio
Quoted 16 lines · Sagi Grimberg
Quoted 15 lines · Jens Axboe
Quoted 13 lines · Sagi Grimberg
Quoted 8 lines · Jens Axboe
Quoted 4 lines · Sagi Grimberg
Quoted 1 line
Does the below work for you?
It's not on top of Keith
virt_boundarypatch right? First glance looks ok though.Updated for that.
Thanks Jens,
I'll test it.
Quoted 8 lines
Quoted 3 lines
Cleaned up version, unified the gap checking and changed the names of the function to make it easier to understand. And then we only need to check
bio_has_data()inbio_will_gap().:)
I was just going to say that maybe we should keep the check explicit..
Hehe. I think it makes sense to check whether it carries data or not in the gap check, it's somewhat different than the integrity check.
Quoted 5 lines
I am going to fix this also for integrity payload [1], and there I need to check for
blk_integrity_rq(req)explicitly because it doesn't really makes sense to call an integrity gap checker if you don't have integrity...
Quoted 8 lines
Also, lets move the gap helpers to be static inline in
blkdev.hso I can put my integrity gap helpers there too.Anyway, its just cosmetics...
Let me include your patch in a series I'm planning on sending soon enough as I don't see merges anymore so I guess the issue is gone now.
Updated patch below. Let me know when you have sufficient confidence in it working, and I will add your reviewed/tested/whatever-by and we can queue it up for this series.
Quoted 87 lines
commit
48679ce5f6ffe6827d40287b521ea139cdf95ff7Author: Sagi Grimberg <sagig at mellanox.com> Date: Wed Jul 15 15:06:04 2015 +0300block: Refuse request/bio merges with gaps in the integrity payload
If a driver sets the block queue virtual boundary, it means that it cannot handle gaps so we must not allow those in the integrity payload as well.
Signed-off-by Sagi Grimberg <sagig at mellanox.com>block/blk-integrity.c +3
-0@@ -204,6 +204,9 @@ bool blk_integrity_merge_rq(struct request_queue *q,struct request *req, q->limits.max_integrity_segments) return false; +if (integrity_req_gap_to_prev (req, next->bio)) +return false; +return true; }EXPORT_SYMBOL(blk_integrity_merge_rq);block/blk-merge.c +4
-0@@ -315,6 +315,10 @@ int ll_back_merge_fn(struct request_queue *q, struct request *req, if (bio_has_data(bio) && req_gap_to_prev(req, bio)) return 0; + if (blk_integrity_rq(req) && + integrity_req_gap_to_prev(req, bio)) + return 0; + if (blk_rq_sectors(req) + bio_sectors(bio) > blk_rq_get_max_sectors(req)) {blk_rq_get_max_sectors(req)) { req->cmd_flags |= REQ_NOMERGE;block/blk-merge.c +4
-0@@ -336,6 +340,10 @@ int ll_front_merge_fn(struct request_queue *q, struct request *req, if (bio_has_data(bio) && req_gap_to_next(req, bio)) return 0; + if (blk_integrity_rq(req) && + integrity_req_gap_to_next(req, bio)) + return 0; + if (blk_rq_sectors(req) + bio_sectors(bio) > blk_rq_get_max_sectors(req)) {req->cmd_flags |= REQ_NOMERGE;include/linux/blkdev.h +19
-0@@ -1389,6 +1389,26 @@ static inline bool req_gap_to_next(struct request *req, struct bio *bio) next->bi_io_vec[0].bv_offset); } +static inline bool integrity_req_gap_to_prev (struct request *req, +struct bio *next) +{ +struct bio_integrity_payload *bip =bio_integrity (req->bio); +struct bio_integrity_payload *bip_next =bio_integrity (next); + +return bvec_gap_to_prev (req->q, &bip->bip_vec[bip->bip_vcnt -1 ], + bip_next->bip_vec[0 ].bv_offset); +} + +static inline bool integrity_req_gap_to_next (struct request *req, +struct bio *bio) +{ +struct bio_integrity_payload *bip =bio_integrity (bio); +struct bio_integrity_payload *bip_next =bio_integrity (req->bio); + +return bvec_gap_to_prev (req->q, &bip->bip_vec[bip->bip_vcnt -1 ], + bip_next->bip_vec[0 ].bv_offset); +}struct work_struct ;int kblockd_schedule_work (struct work_struct *work); int kblockd_schedule_delayed_work(struct delayed_work *dwork, unsigned
Quoted 1 line
long delay);
Looks good to me.
block/blk-merge.c +6 -13
@@ -438,6 +438,8 @@ no_merge:int ll_back_merge_fn (struct request_queue *q,struct request *req,struct bio *bio) { +if (req_gap_back_merge (req, bio)) +return 0 ;if (blk_rq_sectors (req) +bio_sectors (bio) >blk_rq_get_max_sectors (req)) { req->cmd_flags |=REQ_NOMERGE ;
@@ -456,6 +458,9 @@ int ll_back_merge_fn(struct request_queue *q, struct request *req,int ll_front_merge_fn (struct request_queue *q,struct request *req,struct bio *bio) { + +if (req_gap_front_merge (req, bio)) +return 0 ;if (blk_rq_sectors (req) +bio_sectors (bio) >blk_rq_get_max_sectors (req)) { req->cmd_flags |=REQ_NOMERGE ;
@@ -482,14 +487,6 @@ static bool req_no_special_merge(struct request *req)return !q->mq_ops && req->special; } -static int req_gap_to_prev (struct request *req,struct bio *next) -{ -struct bio *prev = req->biotail; - -return bvec_gap_to_prev (req->q, &prev->bi_io_vec[prev->bi_vcnt -1 ], - next->bi_io_vec[0 ].bv_offset); -} -static int ll_merge_requests_fn (struct request_queue *q,struct request *req,struct request *next) {
@@ -504,7 +501,7 @@ static int ll_merge_requests_fn(struct request_queue *q, struct request *req,if (req_no_special_merge (req) ||req_no_special_merge (next))return 0 ; -if (req_gap_to_prev (req, next->bio)) +if (req_gap_back_merge (req, next->bio))return 0 ;/*
@@ -712,10 +709,6 @@ bool blk_rq_merge_ok(struct request *rq, struct bio *bio) !blk_write_same_mergeable(rq->bio, bio))return false; -/* Only check gaps if the bio carries data */ -if (bio_has_data (bio) &&req_gap_to_prev (rq, bio)) -return false; -return true; }
include/linux/blkdev.h +20 -0
@@ -1368,6 +1368,26 @@ static inline bool bvec_gap_to_prev(struct request_queue *q, ((bprv->bv_offset + bprv->bv_len) &queue_virt_boundary (q)); } +static inline bool bio_will_gap (struct request_queue *q,struct bio *prev, +struct bio *next) +{ +if (!bio_has_data (prev)) +return false; + +return bvec_gap_to_prev (q, &prev->bi_io_vec[prev->bi_vcnt -1 ], + next->bi_io_vec[0 ].bv_offset); +} + +static inline bool req_gap_back_merge (struct request *req,struct bio *next) +{ +return bio_will_gap (req->q, req->biotail, next); +} + +static inline bool req_gap_front_merge (struct request *req,struct bio *bio) +{ +return bio_will_gap (req->q, bio, req->bio); +} +struct work_struct ;int kblockd_schedule_work (struct work_struct *work);int kblockd_schedule_delayed_work (struct delayed_work *dwork,unsigned long delay);
Signature · 1 line
Jens Axboe
PATCH 3/3 block: Refuse adding appending a gapped integrity page to a bio
In reply to Christoph HellwigQuoted 1 line
Quoted 1 line
Quoted 1 line
Quoted 1 line
Quoted 1 line
"Christoph" == Christoph Hellwig <hch at infradead.org> writes:
Christoph> In the long run we'll need to refactor it by adding bio_vec Christoph> iteration helpers that can pick different anchors from the Christoph> bio.
Yep. Always wanted to avoid that duplication. Keith's work made it easier. I just haven't taken things one step further yet...
Signature · 1 line
Martin K. Petersen Oracle Linux Engineering
cover Fixes for gapped scatters
In reply to Sagi GrimbergQuoted 1 line
Quoted 1 line
Quoted 1 line
Quoted 1 line
Quoted 1 line
"Sagi" == Sagi Grimberg <sagig at mellanox.com> writes:
Sagi> This small set addresses some issues with gapped scattered Sagi> IOs. The motivation of this is to have the iser driver take Sagi> advantage of it too.
Sagi> Patch 1 prevent gapped SG_IO if the driver can't handle gaps. Sagi> Patches 2,3 prevent gapped integrity payloads (for drivers that Sagi> support data integrity).
Back in the prototyping days of DIX we dealt with several controllers that had various oddball scatterlist requirements for PI. The only workaround we found that performed acceptably was to grab a page and bounce the PI to that. In the end none of these controllers ended up shipping, and DIX explicitly requires a controller to be able to handle a separate scatterlist element per PI interval.
I know that for NVMe latency is king and so on. But I do question the sanity of having to issue a flurry 512+8 I/Os instead of quarter of a meg and a page of PI. You're at the whim of the kernel memory allocator and for a long time slab or slub was allocating backwards so you never ended up being able to merge PI scatterlist entries.
Also, if you were to go down this path you'd have to handle cases where a bio itself would need to get split. The PI for two contiguous 512-byte data blocks in a bio_vec entry could reside in separate integrity pages at offsets that would make them "gappy".
Signature · 1 line
Martin K. Petersen Oracle Linux Engineering