dbb1ed6d87
reproducer: https://www.spinics.net/lists/linux-block/msg28507.html ubuntu bugreport: https://bugs.launchpad.net/ubuntu/+source/linux/+bug/1796542 Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
179 lines
5.3 KiB
Diff
179 lines
5.3 KiB
Diff
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
|
From: Christoph Hellwig <hch@lst.de>
|
|
Date: Tue, 9 Oct 2018 17:04:39 +0100
|
|
Subject: [PATCH] block: add a lower-level bio_add_page interface
|
|
|
|
Buglink: https://bugs.launchpad.net/bugs/1796542
|
|
|
|
For the upcoming removal of buffer heads in XFS we need to keep track of
|
|
the number of outstanding writeback requests per page. For this we need
|
|
to know if bio_add_page merged a region with the previous bvec or not.
|
|
Instead of adding additional arguments this refactors bio_add_page to
|
|
be implemented using three lower level helpers which users like XFS can
|
|
use directly if they care about the merge decisions.
|
|
|
|
Signed-off-by: Christoph Hellwig <hch@lst.de>
|
|
Reviewed-by: Jens Axboe <axboe@kernel.dk>
|
|
Reviewed-by: Ming Lei <ming.lei@redhat.com>
|
|
Reviewed-by: Darrick J. Wong <darrick.wong@oracle.com>
|
|
Signed-off-by: Darrick J. Wong <darrick.wong@oracle.com>
|
|
(cherry picked from commit 0aa69fd32a5f766e997ca8ab4723c5a1146efa8b)
|
|
Signed-off-by: Colin Ian King <colin.king@canonical.com>
|
|
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
|
|
---
|
|
block/bio.c | 98 ++++++++++++++++++++++++++++++++++-------------------
|
|
include/linux/bio.h | 9 +++++
|
|
2 files changed, 73 insertions(+), 34 deletions(-)
|
|
|
|
diff --git a/block/bio.c b/block/bio.c
|
|
index 4b48f8eefc4c..2636d15af979 100644
|
|
--- a/block/bio.c
|
|
+++ b/block/bio.c
|
|
@@ -773,7 +773,7 @@ int bio_add_pc_page(struct request_queue *q, struct bio *bio, struct page
|
|
return 0;
|
|
}
|
|
|
|
- if (bio->bi_vcnt >= bio->bi_max_vecs)
|
|
+ if (bio_full(bio))
|
|
return 0;
|
|
|
|
/*
|
|
@@ -821,6 +821,65 @@ int bio_add_pc_page(struct request_queue *q, struct bio *bio, struct page
|
|
EXPORT_SYMBOL(bio_add_pc_page);
|
|
|
|
/**
|
|
+ * __bio_try_merge_page - try appending data to an existing bvec.
|
|
+ * @bio: destination bio
|
|
+ * @page: page to add
|
|
+ * @len: length of the data to add
|
|
+ * @off: offset of the data in @page
|
|
+ *
|
|
+ * Try to add the data at @page + @off to the last bvec of @bio. This is a
|
|
+ * a useful optimisation for file systems with a block size smaller than the
|
|
+ * page size.
|
|
+ *
|
|
+ * Return %true on success or %false on failure.
|
|
+ */
|
|
+bool __bio_try_merge_page(struct bio *bio, struct page *page,
|
|
+ unsigned int len, unsigned int off)
|
|
+{
|
|
+ if (WARN_ON_ONCE(bio_flagged(bio, BIO_CLONED)))
|
|
+ return false;
|
|
+
|
|
+ if (bio->bi_vcnt > 0) {
|
|
+ struct bio_vec *bv = &bio->bi_io_vec[bio->bi_vcnt - 1];
|
|
+
|
|
+ if (page == bv->bv_page && off == bv->bv_offset + bv->bv_len) {
|
|
+ bv->bv_len += len;
|
|
+ bio->bi_iter.bi_size += len;
|
|
+ return true;
|
|
+ }
|
|
+ }
|
|
+ return false;
|
|
+}
|
|
+EXPORT_SYMBOL_GPL(__bio_try_merge_page);
|
|
+
|
|
+/**
|
|
+ * __bio_add_page - add page to a bio in a new segment
|
|
+ * @bio: destination bio
|
|
+ * @page: page to add
|
|
+ * @len: length of the data to add
|
|
+ * @off: offset of the data in @page
|
|
+ *
|
|
+ * Add the data at @page + @off to @bio as a new bvec. The caller must ensure
|
|
+ * that @bio has space for another bvec.
|
|
+ */
|
|
+void __bio_add_page(struct bio *bio, struct page *page,
|
|
+ unsigned int len, unsigned int off)
|
|
+{
|
|
+ struct bio_vec *bv = &bio->bi_io_vec[bio->bi_vcnt];
|
|
+
|
|
+ WARN_ON_ONCE(bio_flagged(bio, BIO_CLONED));
|
|
+ WARN_ON_ONCE(bio_full(bio));
|
|
+
|
|
+ bv->bv_page = page;
|
|
+ bv->bv_offset = off;
|
|
+ bv->bv_len = len;
|
|
+
|
|
+ bio->bi_iter.bi_size += len;
|
|
+ bio->bi_vcnt++;
|
|
+}
|
|
+EXPORT_SYMBOL_GPL(__bio_add_page);
|
|
+
|
|
+/**
|
|
* bio_add_page - attempt to add page to bio
|
|
* @bio: destination bio
|
|
* @page: page to add
|
|
@@ -833,40 +892,11 @@ EXPORT_SYMBOL(bio_add_pc_page);
|
|
int bio_add_page(struct bio *bio, struct page *page,
|
|
unsigned int len, unsigned int offset)
|
|
{
|
|
- struct bio_vec *bv;
|
|
-
|
|
- /*
|
|
- * cloned bio must not modify vec list
|
|
- */
|
|
- if (WARN_ON_ONCE(bio_flagged(bio, BIO_CLONED)))
|
|
- return 0;
|
|
-
|
|
- /*
|
|
- * For filesystems with a blocksize smaller than the pagesize
|
|
- * we will often be called with the same page as last time and
|
|
- * a consecutive offset. Optimize this special case.
|
|
- */
|
|
- if (bio->bi_vcnt > 0) {
|
|
- bv = &bio->bi_io_vec[bio->bi_vcnt - 1];
|
|
-
|
|
- if (page == bv->bv_page &&
|
|
- offset == bv->bv_offset + bv->bv_len) {
|
|
- bv->bv_len += len;
|
|
- goto done;
|
|
- }
|
|
+ if (!__bio_try_merge_page(bio, page, len, offset)) {
|
|
+ if (bio_full(bio))
|
|
+ return 0;
|
|
+ __bio_add_page(bio, page, len, offset);
|
|
}
|
|
-
|
|
- if (bio->bi_vcnt >= bio->bi_max_vecs)
|
|
- return 0;
|
|
-
|
|
- bv = &bio->bi_io_vec[bio->bi_vcnt];
|
|
- bv->bv_page = page;
|
|
- bv->bv_len = len;
|
|
- bv->bv_offset = offset;
|
|
-
|
|
- bio->bi_vcnt++;
|
|
-done:
|
|
- bio->bi_iter.bi_size += len;
|
|
return len;
|
|
}
|
|
EXPORT_SYMBOL(bio_add_page);
|
|
diff --git a/include/linux/bio.h b/include/linux/bio.h
|
|
index a98c6ac575cf..3440870712d4 100644
|
|
--- a/include/linux/bio.h
|
|
+++ b/include/linux/bio.h
|
|
@@ -123,6 +123,11 @@ static inline void *bio_data(struct bio *bio)
|
|
return NULL;
|
|
}
|
|
|
|
+static inline bool bio_full(struct bio *bio)
|
|
+{
|
|
+ return bio->bi_vcnt >= bio->bi_max_vecs;
|
|
+}
|
|
+
|
|
/*
|
|
* will die
|
|
*/
|
|
@@ -447,6 +452,10 @@ void bio_chain(struct bio *, struct bio *);
|
|
extern int bio_add_page(struct bio *, struct page *, unsigned int,unsigned int);
|
|
extern int bio_add_pc_page(struct request_queue *, struct bio *, struct page *,
|
|
unsigned int, unsigned int);
|
|
+bool __bio_try_merge_page(struct bio *bio, struct page *page,
|
|
+ unsigned int len, unsigned int off);
|
|
+void __bio_add_page(struct bio *bio, struct page *page,
|
|
+ unsigned int len, unsigned int off);
|
|
int bio_iov_iter_get_pages(struct bio *bio, struct iov_iter *iter);
|
|
struct rq_map_data;
|
|
extern struct bio *bio_map_user_iov(struct request_queue *,
|