[Devel] [PATCH VZ9 RFC] vhost-blk: split bio at 1M alignment

Pavel Tikhomirov ptikhomirov at virtuozzo.com
Fri Nov 8 11:37:16 MSK 2024


The idea is to make iovec spliting across bios to be more aligned.

Imagine we have two io vectors in one virtqueue request: first is of
arbitrary unaligned size e.g. (4G-4K) and second io vector is both
1M aligned and is of aligned size of 1M.

If we just leave the algorithm as it is now, it will add the first io
vector to a first bio, and then will also put there first 4K of the
second io vector that still fits in the first bio. Next, leftover of
1M-4K will go to a second bio. That will make the second bio start not
aligned by 1M.

With this patch, algorithm will detect this bad alignment and will put
the whole second io vector into the second bio instead.

https://virtuozzo.atlassian.net/browse/VSTOR-94596
Signed-off-by: Pavel Tikhomirov <ptikhomirov at virtuozzo.com>

Feature: vhost-blk: in-kernel accelerator for virtio-blk guests

---
This is an RFC,

Q1: Do we really need it? Every other place that adds pages to bio does
not care about alignment of next bio.

Q2: Do we want the alignment to be configurable? (E.g. by module
parameter / module sysfs parameter.)

(Not yet fully tested, as I'm not sure how to generate such requests)
---
 block/bio.c         |  3 ++-
 drivers/vhost/blk.c | 18 +++++++++++++++++-
 include/linux/bio.h |  1 +
 3 files changed, 20 insertions(+), 2 deletions(-)

diff --git a/block/bio.c b/block/bio.c
index 9d702f0229e50..304e9507d31a4 100644
--- a/block/bio.c
+++ b/block/bio.c
@@ -894,7 +894,7 @@ EXPORT_SYMBOL(bio_init_clone);
  * Return true if @bio is full and one segment with @len bytes can't be
  * added to the bio, otherwise return false
  */
-static inline bool bio_full(struct bio *bio, unsigned len)
+bool bio_full(struct bio *bio, unsigned len)
 {
 	if (bio->bi_vcnt >= bio->bi_max_vecs)
 		return true;
@@ -902,6 +902,7 @@ static inline bool bio_full(struct bio *bio, unsigned len)
 		return true;
 	return false;
 }
+EXPORT_SYMBOL(bio_full);
 
 static bool bvec_try_merge_page(struct bio_vec *bv, struct page *page,
 		unsigned int len, unsigned int off, bool *same_page)
diff --git a/drivers/vhost/blk.c b/drivers/vhost/blk.c
index 90d20d0eb722c..b00e2ade17439 100644
--- a/drivers/vhost/blk.c
+++ b/drivers/vhost/blk.c
@@ -21,6 +21,8 @@
 #include <linux/kthread.h>
 #include <linux/blkdev.h>
 #include <linux/llist.h>
+#include <linux/minmax.h>
+#include <linux/bio.h>
 
 #include "vhost.h"
 
@@ -240,6 +242,19 @@ static struct page **vhost_blk_prepare_req(struct vhost_blk_req *req,
 	return buf + pl_len;
 }
 
+/*
+ * If next iovec part is 1M alligned and next 1M of it will not fully fit in
+ * current bio, let's split the iovec at 1M boundary.
+ */
+static bool need_new_bio(struct bio *bio, unsigned long iov_base,
+				    unsigned long iov_len) {
+	if (iov_base & (SZ_1M - 1))
+		return false;
+	if (!bio_full(bio, min(iov_len, (unsigned long)SZ_1M)))
+		return false;
+	return true;
+}
+
 static int vhost_blk_bio_make(struct vhost_blk_req *req,
 			      struct block_device *bdev)
 {
@@ -296,7 +311,8 @@ static int vhost_blk_bio_make(struct vhost_blk_req *req,
 			if (len > iov_len)
 				len = iov_len;
 
-			while (!bio || !bio_add_page(bio, page, len, off)) {
+			while (!bio || need_new_bio(bio, iov_base, iov_len) ||
+			       !bio_add_page(bio, page, len, off)) {
 				bio = bio_alloc(bdev, bio_max_segs(pages_nr_total),
 						req->bi_opf, GFP_KERNEL);
 				if (!bio)
diff --git a/include/linux/bio.h b/include/linux/bio.h
index 4c877b846a81e..71bfc106ec7e5 100644
--- a/include/linux/bio.h
+++ b/include/linux/bio.h
@@ -415,6 +415,7 @@ struct bio *bio_alloc_clone(struct block_device *bdev, struct bio *bio_src,
 		gfp_t gfp, struct bio_set *bs);
 int bio_init_clone(struct block_device *bdev, struct bio *bio,
 		struct bio *bio_src, gfp_t gfp);
+bool bio_full(struct bio *bio, unsigned len);
 
 extern struct bio_set fs_bio_set;
 
-- 
2.46.2



More information about the Devel mailing list