Re: [PATCH v6 5/6] fuse: add zero-copy over io-uring

Bernd Schubert <[email protected]>
Newsgroups dev.linux.lists.fuse-devel
Message-ID <[email protected]>

On 8/13/26 01:19, Joanne Koong wrote:
> On Wed, Aug 12, 2026 at 3:30 PM Bernd Schubert <[email protected]> wrote:
>>
>>> diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c
>>> index d50792162d8b..3d1910a9db52 100644
>>> --- a/fs/fuse/dev.c
>>> +++ b/fs/fuse/dev.c
>>> @@ -1247,11 +1247,20 @@ int fuse_copy_folio(struct fuse_copy_state *cs, struct folio **foliop,
>>>
>>> @@ -746,6 +786,62 @@ static int fuse_uring_copy_from_ring(struct fuse_req *req,
>>>       return err;
>>>  }
>>>
>>> +static void fuse_zero_copy_release(void *priv)
>>> +{
>>> +     struct fuse_zero_copy_bvs *zc_bvs = priv;
>>> +     unsigned int i;
>>> +
>>> +     for (i = 0; i < zc_bvs->nr_bvs; i++)
>>> +             folio_put(page_folio(zc_bvs->bvs[i].bv_page));
>>> +
>>> +     kfree(zc_bvs);
>>
>> kvfree (see below)?
>>
>>> +}
>>> +
>>> +static int fuse_uring_set_up_zero_copy(struct fuse_ring_ent *ent,
>>> +                                    struct fuse_req *req,
>>> +                                    unsigned int issue_flags)
>>> +{
>>> +     struct fuse_args_pages *ap;
>>> +     int err, i, ddir = 0;
>>> +     struct fuse_zero_copy_bvs *zc_bvs;
>>> +     struct bio_vec *bvs;
>>> +
>>> +     /* out_pages indicates a read, in_pages indicates a write */
>>> +     if (req->args->out_pages)
>>> +             ddir |= IO_BUF_DEST;
>>> +     if (req->args->in_pages)
>>> +             ddir |= IO_BUF_SOURCE;
>>> +
>>> +     ap = container_of(req->args, typeof(*ap), args);
>>> +
>>> +     zc_bvs = kmalloc(struct_size(zc_bvs, bvs, ap->num_folios),
>>> +                      GFP_KERNEL_ACCOUNT);
>>
>> Assuming one folio per page and 1MB max_nr_pages:
>>
>> struct_size(zc_bvs, bvs, n) == 8 + 16 * 256 = 4104
>>
>> Shouldn't this be kvmalloc()?
> 
> I'm not sure what the upper limit is for a kmalloc, but I'll change
> this to kvmalloc().


As far as I know kmalloc will allocate continuos pages and might fail
under fragmentation if more than a page is needed. I'm not entirely sure
if kvmalloc for such a small allocation will actually do the job.
How about the attached patch? And with that maybe we should limit
max_nr_pages to 256 for ZC?

Thanks,
Bernd
io_buffer_register_bvec-self-release.patch (text/x-patch, 5.1 KB)
diff --git a/fs/fuse/dev_uring.c b/fs/fuse/dev_uring.c
index d7d517497a1e..e1b55f7c0c09 100644
--- a/fs/fuse/dev_uring.c
+++ b/fs/fuse/dev_uring.c
@@ -34,11 +34,6 @@ struct fuse_uring_pdu {
 	struct fuse_ring_ent *ent;
 };
 
-struct fuse_zero_copy_bvs {
-	unsigned int nr_bvs;
-	struct bio_vec bvs[];
-};
-
 static const struct fuse_iqueue_ops fuse_io_uring_ops;
 
 enum fuse_uring_header_type {
@@ -786,24 +781,12 @@ static int fuse_uring_copy_from_ring(struct fuse_req *req,
 	return err;
 }
 
-static void fuse_zero_copy_release(void *priv)
-{
-	struct fuse_zero_copy_bvs *zc_bvs = priv;
-	unsigned int i;
-
-	for (i = 0; i < zc_bvs->nr_bvs; i++)
-		folio_put(page_folio(zc_bvs->bvs[i].bv_page));
-
-	kfree(zc_bvs);
-}
-
 static int fuse_uring_set_up_zero_copy(struct fuse_ring_ent *ent,
 				       struct fuse_req *req,
 				       unsigned int issue_flags)
 {
 	struct fuse_args_pages *ap;
 	int err, i, ddir = 0;
-	struct fuse_zero_copy_bvs *zc_bvs;
 	struct bio_vec *bvs;
 
 	/* out_pages indicates a read, in_pages indicates a write */
@@ -814,13 +797,11 @@ static int fuse_uring_set_up_zero_copy(struct fuse_ring_ent *ent,
 
 	ap = container_of(req->args, typeof(*ap), args);
 
-	zc_bvs = kmalloc(struct_size(zc_bvs, bvs, ap->num_folios),
-			 GFP_KERNEL_ACCOUNT);
-	if (!zc_bvs)
+	/* only needed until registration copies it into the ring */
+	bvs = kmalloc_array(ap->num_folios, sizeof(*bvs), GFP_KERNEL_ACCOUNT);
+	if (!bvs)
 		return -ENOMEM;
 
-	zc_bvs->nr_bvs = ap->num_folios;
-	bvs = zc_bvs->bvs;
 	for (i = 0; i < ap->num_folios; i++) {
 		bvs[i].bv_page = folio_page(ap->folios[i], 0);
 		bvs[i].bv_offset = ap->descs[i].offset;
@@ -828,12 +809,13 @@ static int fuse_uring_set_up_zero_copy(struct fuse_ring_ent *ent,
 		folio_get(ap->folios[i]);
 	}
 
-	err = io_buffer_register_bvec(ent->cmd, bvs, ap->num_folios,
-				      fuse_zero_copy_release, zc_bvs,
-				      ddir, ent->zero_copy_index,
-				      issue_flags);
+	err = io_buffer_register_bvec(ent->cmd, bvs, ap->num_folios, NULL, NULL,
+				      ddir, IO_BUF_F_PUT_FOLIOS,
+				      ent->zero_copy_index, issue_flags);
+	kfree(bvs);
 	if (err) {
-		fuse_zero_copy_release(zc_bvs);
+		for (i = 0; i < ap->num_folios; i++)
+			folio_put(ap->folios[i]);
 		return err;
 	}
 
diff --git a/include/linux/io_uring/cmd.h b/include/linux/io_uring/cmd.h
index 42801f0b6456..980948c86c62 100644
--- a/include/linux/io_uring/cmd.h
+++ b/include/linux/io_uring/cmd.h
@@ -41,6 +41,15 @@ static inline void io_uring_cmd_private_sz_check(size_t cmd_sz)
 	((pdu_type *)&(cmd)->pdu) \
 )
 
+/* Flags for io_buffer_register_bvec() */
+enum io_buffer_register_flags {
+	/*
+	 * The caller holds a folio reference per bvec, to be dropped when the
+	 * buffer is released. Registers without release/priv.
+	 */
+	IO_BUF_F_PUT_FOLIOS	= (1u << 0),
+};
+
 #if defined(CONFIG_IO_URING)
 int io_uring_cmd_import_fixed(u64 ubuf, unsigned long len, int rw,
 			      struct iov_iter *iter,
@@ -96,8 +105,8 @@ int io_buffer_register_request(struct io_uring_cmd *cmd, struct request *rq,
 			       unsigned int issue_flags);
 int io_buffer_register_bvec(struct io_uring_cmd *cmd, const struct bio_vec *bvs,
 			    unsigned int nr_bvecs, void (*release)(void *),
-			    void *priv, u8 dir, unsigned int index,
-			    unsigned int issue_flags);
+			    void *priv, u8 dir, unsigned int flags,
+			    unsigned int index, unsigned int issue_flags);
 int io_buffer_unregister(struct io_uring_cmd *cmd, unsigned int index,
 			 unsigned int issue_flags);
 #else
@@ -154,7 +163,8 @@ static inline int io_buffer_register_bvec(struct io_uring_cmd *cmd,
 					  const struct bio_vec *bvs,
 					  unsigned int nr_bvecs,
 					  void (*release)(void *), void *priv,
-					  u8 dir, unsigned int index,
+					  u8 dir, unsigned int flags,
+					  unsigned int index,
 					  unsigned int issue_flags)
 {
 	return -EOPNOTSUPP;
diff --git a/io_uring/rsrc.c b/io_uring/rsrc.c
index f3f01e0c8102..bd73a7ee98cc 100644
--- a/io_uring/rsrc.c
+++ b/io_uring/rsrc.c
@@ -158,6 +158,15 @@ static void io_release_ubuf(void *priv)
 	}
 }
 
+static void io_release_kbuf_folios(void *priv)
+{
+	struct io_mapped_ubuf *imu = priv;
+	unsigned int i;
+
+	for (i = 0; i < imu->nr_bvecs; i++)
+		folio_put(bvec_folio(&imu->bvec[i]));
+}
+
 static struct io_mapped_ubuf *io_alloc_imu(struct io_ring_ctx *ctx,
 					   int nr_bvecs)
 {
@@ -1101,8 +1110,8 @@ EXPORT_SYMBOL_GPL(io_buffer_register_request);
  */
 int io_buffer_register_bvec(struct io_uring_cmd *cmd, const struct bio_vec *bvs,
 			    unsigned int nr_bvecs, void (*release)(void *),
-			    void *priv, u8 dir, unsigned int index,
-			    unsigned int issue_flags)
+			    void *priv, u8 dir, unsigned int flags,
+			    unsigned int index, unsigned int issue_flags)
 {
 	struct io_ring_ctx *ctx = cmd_to_io_kiocb(cmd)->ctx;
 	struct io_mapped_ubuf *imu;
@@ -1125,6 +1134,12 @@ int io_buffer_register_bvec(struct io_uring_cmd *cmd, const struct bio_vec *bvs,
 	for (i = 0; i < nr_bvecs; i++)
 		bvec[i] = bvs[i];
 
+	/* imu is only known here, and needs the bvecs in place */
+	if (flags & IO_BUF_F_PUT_FOLIOS) {
+		imu->release = io_release_kbuf_folios;
+		imu->priv = imu;
+	}
+
 unlock:
 	io_ring_submit_unlock(ctx, issue_flags);
 	return ret;
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.