ublk zero-copy support
Subsystems (3)
v2 cover ublk zero-copy support
From: Keith Busch <[email protected]>
Previous version was discussed here:
https://lore.kernel.org/linux-block/[email protected]/
The same ublksrv reference code in that link was used to test the kernel side changes.
Before listing what has changed, I want to mention what is the same: the reliance on the ring ctx lock to serialize the register ahead of any use. I'm not ignoring the feedback; I just don't have a solid answer right now, and want to progress on the other fronts in the meantime.
Here's what's different from the previous:
- Introduced an optional 'release' callback when the resource node is no longer referenced. The callback addresses any buggy applications that may complete their request and unregister their index while IO is in flight. This obviates any need to take extra page references since it prevents the request from completing.
- Removed peeking into the io_cache element size and instead use a more intuitive bvec segment count limit to decide if we're caching the imu (suggested by Pavel).
- Dropped the const request changes; it's not needed.
- Merged up to latest block uring branch
Jens Axboe (1):
io_uring: use node for import
Keith Busch (5):
io_uring: create resource release callback
io_uring: add support for kernel registered bvecs
ublk: zc register/unregister bvec
io_uring: add abstraction for buf_table rsrc data
io_uring: cache nodes and mapped buffers
drivers/block/ublk_drv.c | 145 ++++++++++++++----- include/linux/io_uring.h | 1 + include/linux/io_uring_types.h | 28 ++-- include/uapi/linux/ublk_cmd.h | 4 + io_uring/fdinfo.c | 8 +- io_uring/filetable.c | 2 +- io_uring/net.c | 5 +- io_uring/nop.c | 2 +- io_uring/register.c | 2 +- io_uring/rsrc.c | 252 ++++++++++++++++++++++++++------- io_uring/rsrc.h | 11 +- io_uring/rw.c | 4 +- io_uring/uring_cmd.c | 4 +- 13 files changed, 355 insertions(+), 113 deletions(-)
Signature · 1 line
2.43.5
PATCH v2 1/6 io_uring: use node for import
From: Jens Axboe <[email protected]>
Replace the mapped buffer to the parent node. This is preparing for a future for different types with specific handling considerations.
--- io_uring/net.c | 3 +-- io_uring/rsrc.c | 6 +++--- io_uring/rsrc.h | 5 ++--- io_uring/rw.c | 2 +- io_uring/uring_cmd.c | 2 +- 5 files changed, 8 insertions(+), 10 deletions(-)
io_uring/net.c +1 -2
@@ -1377,8 +1377,7 @@ static int io_send_zc_import(struct io_kiocb *req, unsigned int issue_flags)return ret; ret =io_import_fixed (ITER_SOURCE , &kmsg->msg.msg_iter, - node->buf, (u64 )(uintptr_t )sr->buf, - sr->len); + node, (u64 )(uintptr_t )sr->buf, sr->len);if (unlikely (ret))return ret; kmsg->msg.sg_from_iter = io_sg_from_iter;
io_uring/rsrc.c +3 -3
@@ -860,10 +860,10 @@ int io_sqe_buffers_register(struct io_ring_ctx *ctx, void __user *arg,return ret; } -int io_import_fixed (int ddir,struct iov_iter *iter, -struct io_mapped_ubuf *imu, -u64 buf_addr,size_t len) +int io_import_fixed (int ddir,struct iov_iter *iter,struct io_rsrc_node *node, +u64 buf_addr,size_t len) { +struct io_mapped_ubuf *imu = node->buf;u64 buf_end;size_t offset;
io_uring/rsrc.h +2 -3
@@ -50,9 +50,8 @@ void io_free_rsrc_node(struct io_ring_ctx *ctx, struct io_rsrc_node *node);void io_rsrc_data_free (struct io_ring_ctx *ctx,struct io_rsrc_data *data);int io_rsrc_data_alloc (struct io_rsrc_data *data,unsigned nr); -int io_import_fixed (int ddir,struct iov_iter *iter, -struct io_mapped_ubuf *imu, -u64 buf_addr,size_t len); +int io_import_fixed (int ddir,struct iov_iter *iter,struct io_rsrc_node *node, +u64 buf_addr,size_t len);int io_register_clone_buffers (struct io_ring_ctx *ctx,void __user *arg);int io_sqe_buffers_unregister (struct io_ring_ctx *ctx);
io_uring/rw.c +1 -1
@@ -369,7 +369,7 @@ static int io_prep_rw_fixed(struct io_kiocb *req, const struct io_uring_sqe *sqeio_req_assign_buf_node (req, node); io = req->async_data; - ret =io_import_fixed (ddir, &io->iter, node->buf, rw->addr, rw->len); + ret =io_import_fixed (ddir, &io->iter, node, rw->addr, rw->len);iov_iter_save_state (&io->iter, &io->iter_state);return ret; }
io_uring/uring_cmd.c +1 -1
@@ -274,7 +274,7 @@ int io_uring_cmd_import_fixed(u64 ubuf, unsigned long len, int rw,/* Must have had rsrc_node assigned at prep time */ if (node) -return io_import_fixed (rw, iter, node->buf, ubuf, len); +return io_import_fixed (rw, iter, node, ubuf, len);return -EFAULT ; }
Signature · 1 line
2.43.5
PATCH v2 2/6 io_uring: create resource release callback
In reply to Keith BuschFrom: Keith Busch <[email protected]>
When a registered resource is about to be freed, check if it has registered a release function, and call it if so. This is preparing for resources that are related to an external component.
--- io_uring/rsrc.c | 2 ++ io_uring/rsrc.h | 3 +++ 2 files changed, 5 insertions(+)
io_uring/rsrc.c +2 -0
@@ -455,6 +455,8 @@ void io_free_rsrc_node(struct io_ring_ctx *ctx, struct io_rsrc_node *node)case IORING_RSRC_BUFFER :if (node->buf)io_buffer_unmap (ctx, node); +if (node->release) + node->release (node->priv);break ;default :WARN_ON_ONCE (1 );
io_uring/rsrc.h +3 -0
@@ -24,6 +24,9 @@ struct io_rsrc_node {unsigned long file_ptr;struct io_mapped_ubuf *buf; }; + +void (*release)(void *); +void *priv; };struct io_mapped_ubuf {
Signature · 1 line
2.43.5
Re: [PATCHv2 2/6] io_uring: create resource release callback
Quoted 30 lines · Keith Busch
From: Keith Busch <[email protected]>
When a registered resource is about to be freed, check if it has registered a release function, and call it if so. This is preparing for resources that are related to an external component.
Signed-off-by Keith Busch <[email protected]>--- io_uring/rsrc.c | 2 ++ io_uring/rsrc.h | 3 +++ 2 files changed, 5 insertions(+)io_uring/rsrc.c +2
-0@@ -455,6 +455,8 @@ void io_free_rsrc_node(struct io_ring_ctx *ctx, struct io_rsrc_node *node)case IORING_RSRC_BUFFER :if (node->buf)io_buffer_unmap (ctx, node); +if (node->release) + node->release (node->priv);break ;default :WARN_ON_ONCE (1 );io_uring/rsrc.h +0
-0
io_uring/rsrc.h struct io_rsrc_node { · line 29
Quoted 6 lines
io_uring/rsrc.h +3
-0@@ -24,6 +24,9 @@ struct io_rsrc_node {unsigned long file_ptr;struct io_mapped_ubuf *buf; }; + +void (*release)(void *); +void *priv;
Nodes are more generic than buffers. Unless we want to reuse it for files, and I very much doubt that, it should move into io_mapped_ubuf.
Quoted 3 lines
io_uring/rsrc.h +0
-0};struct io_mapped_ubuf {
Signature · 1 line
Pavel Begunkov
Re: [PATCHv2 2/6] io_uring: create resource release callback
Quoted 12 lines · Pavel Begunkov
io_uring/rsrc.h struct io_rsrc_node { · line 29
Quoted 7 lines · Keith Busch
io_uring/rsrc.h +3
-0@@ -24,6 +24,9 @@ struct io_rsrc_node {unsigned long file_ptr;struct io_mapped_ubuf *buf; }; + +void (*release)(void *); +void *priv;Nodes are more generic than buffers. Unless we want to reuse it for files, and I very much doubt that, it should move into
io_mapped_ubuf.
Good point. Cloning is another reason it should be in the imu.
If we want to save space, the "KBUFFER" type doesn't need ubuf or folio_shift, so we could even union the new fields to prevent imu from getting any bigger. The downside would be we can't use "release" to distinguish kernel vs user buffers, which you suggested in that patch's thread. Which option do you prefer?
Re: [PATCHv2 2/6] io_uring: create resource release callback
Quoted 14 lines · Keith Busch
Quoted 12 lines · Pavel Begunkov
io_uring/rsrc.h struct io_rsrc_node { · line 29
Quoted 7 lines · Keith Busch
io_uring/rsrc.h +3
-0@@ -24,6 +24,9 @@ struct io_rsrc_node {unsigned long file_ptr;struct io_mapped_ubuf *buf; }; + +void (*release)(void *); +void *priv;Nodes are more generic than buffers. Unless we want to reuse it for files, and I very much doubt that, it should move into
io_mapped_ubuf.
Quoted 7 lines
Good point. Cloning is another reason it should be in the imu.
If we want to save space, the "KBUFFER" type doesn't need ubuf or
folio_shift, so we could even union the new fields to prevent imu from getting any bigger. The downside would be we can't use "release" to distinguish kernel vs user buffers, which you suggested in that patch's thread. Which option do you prefer?
I think we can just grow the struct for now, it's not a big problem, and follow up on that separately. Should make it easier for the series as well.
Signature · 1 line
Pavel Begunkov
PATCH v2 3/6 io_uring: add support for kernel registered bvecs
In reply to Keith BuschFrom: Keith Busch <[email protected]>
Provide an interface for the kernel to leverage the existing pre-registered buffers that io_uring provides. User space can reference these later to achieve zero-copy IO.
User space must register an empty fixed buffer table with io_uring in order for the kernel to make use of it.
--- include/linux/io_uring.h | 1 + include/linux/io_uring_types.h | 4 ++ io_uring/rsrc.c | 100 +++++++++++++++++++++++++++++++-- io_uring/rsrc.h | 1 + 4 files changed, 100 insertions(+), 6 deletions(-)
include/linux/io_uring.h +1 -0
@@ -5,6 +5,7 @@#include <linux/sched.h> #include <linux/xarray.h> #include <uapi/linux/io_uring.h> +#include <linux/blk-mq.h> #if defined(CONFIG_IO_URING )void __io_uring_cancel (bool cancel_all);
include/linux/io_uring_types.h +4 -0
@@ -693,4 +693,8 @@ static inline bool io_ctx_cqe32(struct io_ring_ctx *ctx) return ctx->flags & IORING_SETUP_CQE32; } +int io_buffer_register_bvec (struct io_ring_ctx *ctx,struct request *rq, +void (*release)(void *),unsigned int index); +void io_buffer_unregister_bvec (struct io_ring_ctx *ctx,unsigned int tag); +#endif
io_uring/rsrc.c +94 -6
150 lines not on this page · raw message
io_uring/rsrc.h +1 -0
@@ -13,6 +13,7 @@enum {IORING_RSRC_FILE =0 ,IORING_RSRC_BUFFER =1 , +IORING_RSRC_KBUFFER =2 , };struct io_rsrc_node {
Signature · 1 line
2.43.5
Re: [PATCHv2 3/6] io_uring: add support for kernel registered bvecs
Quoted 59 lines · Keith Busch
From: Keith Busch <[email protected]>
Provide an interface for the kernel to leverage the existing pre-registered buffers that
io_uringprovides. User space can reference these later to achieve zero-copy IO.User space must register an empty fixed buffer table with
io_uringin order for the kernel to make use of it.Signed-off-by Keith Busch <[email protected]>--- include/linux/io_uring.h | 1 + include/linux/io_uring_types.h | 4 ++ io_uring/rsrc.c | 100 +++++++++++++++++++++++++++++++-- io_uring/rsrc.h | 1 + 4 files changed, 100 insertions(+), 6 deletions(-)include/linux/io_uring.h +1
-0@@ -5,6 +5,7 @@#include <linux/sched.h> #include <linux/xarray.h> #include <uapi/linux/io_uring.h> +#include <linux/blk-mq.h> #if defined(CONFIG_IO_URING )void __io_uring_cancel (bool cancel_all);include/linux/io_uring_types.h +4
-0@@ -693,4 +693,8 @@ static inline bool io_ctx_cqe32(struct io_ring_ctx *ctx) return ctx->flags & IORING_SETUP_CQE32; } +int io_buffer_register_bvec (struct io_ring_ctx *ctx,struct request *rq, +void (*release)(void *),unsigned int index); +void io_buffer_unregister_bvec (struct io_ring_ctx *ctx,unsigned int tag); +#endif io_uring/rsrc.c +3
-2@@ -110,8 +110,9 @@ static void io_buffer_unmap(struct io_ring_ctx *ctx, struct io_rsrc_node *node)if (!refcount_dec_and_test (&imu->refs))return ; -for (i =0 ; i < imu->nr_bvecs; i++) -unpin_user_page (imu->bvec[i].bv_page); +if (node->type ==IORING_RSRC_BUFFER ) +for (i =0 ; i < imu->nr_bvecs; i++) +unpin_user_page (imu->bvec[i].bv_page);if (imu->acct_pages)io_unaccount_mem (ctx, imu->acct_pages);kvfree (imu);
io_uring/rsrc.c static int __io_sqe_buffers_update(struct io_ring_ctx *ctx, · line 246
Quoted 6 lines
io_uring/rsrc.c +3
-0@@ -240,6 +241,13 @@ static int __io_sqe_buffers_update(struct io_ring_ctx *ctx,struct io_rsrc_node *node;u64 tag =0 ; + i =array_index_nospec (up->offset + done, ctx->buf_table.nr); + node =io_rsrc_node_lookup (&ctx->buf_table, i); +if (node && node->type !=IORING_RSRC_BUFFER ) {
Please drop the special rule. It's handled, so if the user wants to do it, it can be allowed in a generic way.
Quoted 4 lines
io_uring/rsrc.c +4
-0+ err = -EBUSY ; +break ; + } +
...
Quoted 7 lines
io_uring/rsrc.c +7
-0+int io_buffer_register_bvec (struct io_ring_ctx *ctx,struct request *rq, +void (*release)(void *),unsigned int index) +{ +struct io_rsrc_data *data = &ctx->buf_table; +struct req_iterator rq_iter; +struct io_mapped_ubuf *imu; +struct io_rsrc_node *node;
io_uring/rsrc.c int io_sqe_buffers_register(struct io_ring_ctx *ctx, void __user *arg, · line 889
Quoted 10 lines
io_uring/rsrc.c +10
-0+struct bio_vec bv; +u16 nr_bvecs; +int i =0 ; + +lockdep_assert_held (&ctx->uring_lock); + +if (!data->nr) +return -EINVAL ; +if (index >= data->nr) +return -EINVAL ;
array_index_nospec
io_uring/rsrc.c int io_sqe_buffers_register(struct io_ring_ctx *ctx, void __user *arg, · line 893
Quoted 4 lines
io_uring/rsrc.c +4
-0+ + node = data->nodes[index]; +if (node) +return -EBUSY ;
...> +void io_buffer_unregister_bvec(struct io_ring_ctx *ctx, unsigned int index)
io_uring/rsrc.c int io_sqe_buffers_register(struct io_ring_ctx *ctx, void __user *arg, · line 935
Quoted 10 lines
io_uring/rsrc.c +10
-0+{ +struct io_rsrc_data *data = &ctx->buf_table; +struct io_rsrc_node *node; + +lockdep_assert_held (&ctx->uring_lock); + +if (!data->nr) +return ; +if (index >= data->nr) +return ;
array_index_nospec
io_uring/rsrc.c int io_sqe_buffers_register(struct io_ring_ctx *ctx, void __user *arg, · line 938
Quoted 3 lines
io_uring/rsrc.c +3
-0+ + node = data->nodes[index]; +if (!node || !node->buf)
How can node->buf be NULL?
io_uring/rsrc.c int io_sqe_buffers_register(struct io_ring_ctx *ctx, void __user *arg, · line 944
Quoted 6 lines
io_uring/rsrc.c +6
-0+return ; +if (node->type !=IORING_RSRC_KBUFFER ) +return ; +io_reset_rsrc_node (ctx, data, index); +} +EXPORT_SYMBOL_GPL (io_buffer_unregister_bvec);
...> diff --git a/io_uring/rsrc.h b/io_uring/rsrc.h
io_uring/rsrc.h line 16
Quoted 8 lines
index
a3826ab84e666..8147dfc26f737100644io_uring/rsrc.h +1
-0@@ -13,6 +13,7 @@enum {IORING_RSRC_FILE =0 ,IORING_RSRC_BUFFER =1 , +IORING_RSRC_KBUFFER =2 ,
nit: you don't even need it, just check for presence of the callback in io_buffer_unmap()
Signature · 1 line
Pavel Begunkov
Re: [PATCHv2 3/6] io_uring: add support for kernel registered bvecs
In reply to Keith BuschQuoted 11 lines · Keith Busch
From: Keith Busch <[email protected]>
Provide an interface for the kernel to leverage the existing pre-registered buffers that
io_uringprovides. User space can reference these later to achieve zero-copy IO.User space must register an empty fixed buffer table with
io_uringin order for the kernel to make use of it.Signed-off-by Keith Busch <[email protected]>---
...
Quoted 33 lines
io_uring/rsrc.c +32
-0+int io_buffer_register_bvec (struct io_ring_ctx *ctx,struct request *rq, +void (*release)(void *),unsigned int index) +{ +struct io_rsrc_data *data = &ctx->buf_table; +struct req_iterator rq_iter; +struct io_mapped_ubuf *imu; +struct io_rsrc_node *node; +struct bio_vec bv; +u16 nr_bvecs; +int i =0 ; + +lockdep_assert_held (&ctx->uring_lock); + +if (!data->nr) +return -EINVAL ; +if (index >= data->nr) +return -EINVAL ; + + node = data->nodes[index]; +if (node) +return -EBUSY ; + + node =io_rsrc_node_alloc (IORING_RSRC_KBUFFER ); +if (!node) +return -ENOMEM ; + + node->release = release; + node->priv = rq; + + nr_bvecs =blk_rq_nr_phys_segments (rq); + imu =kvmalloc (struct_size (imu, bvec, nr_bvecs),GFP_KERNEL ); +if (!imu) {
io_uring/rsrc.c int io_sqe_buffers_register(struct io_ring_ctx *ctx, void __user *arg, · line 914
Quoted 10 lines
io_uring/rsrc.c +10
-0+kfree (node); +return -ENOMEM ; + } + + imu->ubuf =0 ; + imu->len =blk_rq_bytes (rq); + imu->acct_pages =0 ; + imu->nr_bvecs = nr_bvecs; +refcount_set (&imu->refs,1 ); + node->buf = imu;
request buffer direction needs to be stored in imu, for READ, the buffer is write-only, and for WRITE, the buffer is read-only, which isn't different with user mapped buffer.
Meantime in read_fixed/write_fixed side or buffer lookup abstraction helper, the buffer direction needs to be validated.
Thanks,
Ming
Re: [PATCHv2 3/6] io_uring: add support for kernel registered bvecs
Quoted 23 lines · Ming Lei
Quoted 7 lines · Keith Busch
io_uring/rsrc.c +7
-0+ + node->release = release; + node->priv = rq; + + nr_bvecs =blk_rq_nr_phys_segments (rq); + imu =kvmalloc (struct_size (imu, bvec, nr_bvecs),GFP_KERNEL ); +if (!imu) {io_uring/rsrc.c int io_sqe_buffers_register(struct io_ring_ctx *ctx, void __user *arg, · line 914
Quoted 10 lines
io_uring/rsrc.c +10
-0+kfree (node); +return -ENOMEM ; + } + + imu->ubuf =0 ; + imu->len =blk_rq_bytes (rq); + imu->acct_pages =0 ; + imu->nr_bvecs = nr_bvecs; +refcount_set (&imu->refs,1 ); + node->buf = imu;request buffer direction needs to be stored in
imu, for READ, the buffer is write-only, and for WRITE, the buffer is read-only, which isn't different with user mapped buffer.
Quoted 2 lines
Meantime in read_fixed/write_fixed side or buffer lookup abstraction helper, the buffer direction needs to be validated.
I suppose we could add that check, but the primary use case doesn't even use those operations. They're using uring_cmd with the FIXED flag, and io_uring can't readily validate the data direction from that interface.
Re: [PATCHv2 3/6] io_uring: add support for kernel registered bvecs
Quoted 24 lines · Keith Busch
Quoted 23 lines · Ming Lei
Quoted 7 lines · Keith Busch
io_uring/rsrc.c +7
-0+ + node->release = release; + node->priv = rq; + + nr_bvecs =blk_rq_nr_phys_segments (rq); + imu =kvmalloc (struct_size (imu, bvec, nr_bvecs),GFP_KERNEL ); +if (!imu) {io_uring/rsrc.c int io_sqe_buffers_register(struct io_ring_ctx *ctx, void __user *arg, · line 914
Quoted 10 lines
io_uring/rsrc.c +10
-0+kfree (node); +return -ENOMEM ; + } + + imu->ubuf =0 ; + imu->len =blk_rq_bytes (rq); + imu->acct_pages =0 ; + imu->nr_bvecs = nr_bvecs; +refcount_set (&imu->refs,1 ); + node->buf = imu;request buffer direction needs to be stored in
imu, for READ, the buffer is write-only, and for WRITE, the buffer is read-only, which isn't different with user mapped buffer.
Quoted 6 lines
Quoted 2 lines
Meantime in read_fixed/write_fixed side or buffer lookup abstraction helper, the buffer direction needs to be validated.
I suppose we could add that check, but the primary use case doesn't even use those operations. They're using
uring_cmdwith the FIXED flag, andio_uringcan't readily validate the data direction from that interface.
The check can be added to io_import_fixed().
It is a security trouble. Without the validation:
- kernel data can be redirected to user file via write_fixed,
- kernel page data is over-written unexpectedly via read_fixed, cause fs corruption or even kernel panic.
Thanks,
Ming
Re: [PATCHv2 3/6] io_uring: add support for kernel registered bvecs
Quoted 5 lines · Ming Lei
Quoted 3 lines
I suppose we could add that check, but the primary use case doesn't even use those operations. They're using
uring_cmdwith the FIXED flag, andio_uringcan't readily validate the data direction from that interface.The check can be added to
io_import_fixed().
Good point, will incorporate this change.
PATCH v2 4/6 ublk: zc register/unregister bvec
In reply to Keith BuschFrom: Keith Busch <[email protected]>
Provide new operations for the user to request mapping an active request to an io uring instance's buf_table. The user has to provide the index it wants to install the buffer.
A reference count is taken on the request to ensure it can't be completed while it is active in a ring's buf_table.
--- drivers/block/ublk_drv.c | 145 +++++++++++++++++++++++++--------- include/uapi/linux/ublk_cmd.h | 4 + 2 files changed, 113 insertions(+), 36 deletions(-)
drivers/block/ublk_drv.c +109 -36
@@ -51,6 +51,9 @@/* private ioctl command mirror */ #define UBLK_CMD_DEL_DEV_ASYNC _IOC_NR(UBLK_U_CMD_DEL_DEV_ASYNC) +#define UBLK_IO_REGISTER_IO_BUF _IOC_NR(UBLK_U_IO_REGISTER_IO_BUF) +#define UBLK_IO_UNREGISTER_IO_BUF _IOC_NR(UBLK_U_IO_UNREGISTER_IO_BUF) +/* All UBLK_F_* have to be included into UBLK_F_ALL */ #define UBLK_F_ALL (UBLK_F_SUPPORT_ZERO_COPY \ | UBLK_F_URING_CMD_COMP_IN_TASK \
@@ -76,6 +79,9 @@ struct ublk_rq_data {struct llist_node node;struct kref ref; + +#define UBLK_ZC_REGISTERED 0 +unsigned long flags; };struct ublk_uring_cmd_pdu {
@@ -201,7 +207,7 @@ static inline struct ublksrv_io_desc *ublk_get_iod(struct ublk_queue *ubq, int tag);static inline bool ublk_dev_is_user_copy (const struct ublk_device *ub) { -return ub->dev_info.flags &UBLK_F_USER_COPY ; +return ub->dev_info.flags & (UBLK_F_USER_COPY |UBLK_F_SUPPORT_ZERO_COPY ); }static inline bool ublk_dev_is_zoned (const struct ublk_device *ub)
@@ -581,7 +587,7 @@ static void ublk_apply_params(struct ublk_device *ub)static inline bool ublk_support_user_copy (const struct ublk_queue *ubq) { -return ubq->flags &UBLK_F_USER_COPY ; +return ubq->flags & (UBLK_F_USER_COPY |UBLK_F_SUPPORT_ZERO_COPY ); }static inline bool ublk_need_req_ref (const struct ublk_queue *ubq)
@@ -1747,6 +1753,102 @@ static inline void ublk_prep_cancel(struct io_uring_cmd *cmd,io_uring_cmd_mark_cancelable (cmd, issue_flags); } +static inline struct request *__ublk_check_and_get_req (struct ublk_device *ub, +struct ublk_queue *ubq,int tag,size_t offset) +{ +struct request *req; + +if (!ublk_need_req_ref (ubq)) +return NULL ; + + req =blk_mq_tag_to_rq (ub->tag_set.tags[ubq->q_id], tag); +if (!req) +return NULL ; + +if (!ublk_get_req_ref (ubq, req)) +return NULL ; + +if (unlikely (!blk_mq_request_started (req) || req->tag != tag)) + goto fail_put; + +if (!ublk_rq_has_data (req)) + goto fail_put; + +if (offset >blk_rq_bytes (req)) + goto fail_put; + +return req; +fail_put: +ublk_put_req_ref (ubq, req); +return NULL ; +} + +static void ublk_io_release (void *priv) +{ +struct request *rq = priv; +struct ublk_queue *ubq = rq->mq_hctx->driver_data; + +ublk_put_req_ref (ubq, rq); +} + +static int ublk_register_io_buf (struct io_uring_cmd *cmd, +struct ublk_queue *ubq,int tag, +const struct ublksrv_io_cmd *ub_cmd) +{ +struct io_ring_ctx *ctx =cmd_to_io_kiocb (cmd)->ctx; +struct ublk_device *ub = cmd->file->private_data; +int index = (int )ub_cmd->addr, ret; +struct ublk_rq_data *data; +struct request *req; + +if (!ub) +return -EPERM ; + + req =__ublk_check_and_get_req (ub, ubq, tag,0 ); +if (!req) +return -EINVAL ; + + data =blk_mq_rq_to_pdu (req); +if (test_and_set_bit (UBLK_ZC_REGISTERED , &data->flags)) { +ublk_put_req_ref (ubq, req); +return -EBUSY ; + } + + ret =io_buffer_register_bvec (ctx, req, ublk_io_release, index); +if (ret) { +clear_bit (UBLK_ZC_REGISTERED , &data->flags); +ublk_put_req_ref (ubq, req); +return ret; + } + +return 0 ; +} + +static int ublk_unregister_io_buf (struct io_uring_cmd *cmd, +struct ublk_queue *ubq,int tag, +const struct ublksrv_io_cmd *ub_cmd) +{ +struct io_ring_ctx *ctx =cmd_to_io_kiocb (cmd)->ctx; +struct ublk_device *ub = cmd->file->private_data; +int index = (int )ub_cmd->addr; +struct ublk_rq_data *data; +struct request *req; + +if (!ub) +return -EPERM ; + + req =blk_mq_tag_to_rq (ub->tag_set.tags[ubq->q_id], tag); +if (!req) +return -EINVAL ; + + data =blk_mq_rq_to_pdu (req); +if (!test_and_clear_bit (UBLK_ZC_REGISTERED , &data->flags)) +return -EINVAL ; + +io_buffer_unregister_bvec (ctx, index); +return 0 ; +} +static int __ublk_ch_uring_cmd (struct io_uring_cmd *cmd,unsigned int issue_flags,const struct ublksrv_io_cmd *ub_cmd)
@@ -1798,6 +1900,10 @@ static int __ublk_ch_uring_cmd(struct io_uring_cmd *cmd, ret = -EINVAL ;switch (_IOC_NR (cmd_op)) { +case UBLK_IO_REGISTER_IO_BUF : +return ublk_register_io_buf (cmd, ubq, tag, ub_cmd); +case UBLK_IO_UNREGISTER_IO_BUF : +return ublk_unregister_io_buf (cmd, ubq, tag, ub_cmd);case UBLK_IO_FETCH_REQ :/* UBLK_IO_FETCH_REQ is only allowed before queue is setup */ if (ublk_queue_ready (ubq)) {
@@ -1872,36 +1978,6 @@ static int __ublk_ch_uring_cmd(struct io_uring_cmd *cmd,return -EIOCBQUEUED ; } -static inline struct request *__ublk_check_and_get_req (struct ublk_device *ub, -struct ublk_queue *ubq,int tag,size_t offset) -{ -struct request *req; - -if (!ublk_need_req_ref (ubq)) -return NULL ; - - req =blk_mq_tag_to_rq (ub->tag_set.tags[ubq->q_id], tag); -if (!req) -return NULL ; - -if (!ublk_get_req_ref (ubq, req)) -return NULL ; - -if (unlikely (!blk_mq_request_started (req) || req->tag != tag)) - goto fail_put; - -if (!ublk_rq_has_data (req)) - goto fail_put; - -if (offset >blk_rq_bytes (req)) - goto fail_put; - -return req; -fail_put: -ublk_put_req_ref (ubq, req); -return NULL ; -} -static inline int ublk_ch_uring_cmd_local (struct io_uring_cmd *cmd,unsigned int issue_flags) {
@@ -2527,9 +2603,6 @@ static int ublk_ctrl_add_dev(struct io_uring_cmd *cmd) goto out_free_dev_number; } -/* We are not ready to support zero copy */ - ub->dev_info.flags &= ~UBLK_F_SUPPORT_ZERO_COPY ; - ub->dev_info.nr_hw_queues = min_t(unsigned int, ub->dev_info.nr_hw_queues, nr_cpu_ids);ublk_align_max_io_size (ub);
@@ -2860,7 +2933,7 @@ static int ublk_ctrl_get_features(struct io_uring_cmd *cmd) {const struct ublksrv_ctrl_cmd *header =io_uring_sqe_cmd (cmd->sqe);void __user *argp = (void __user *)(unsigned long )header->addr; -u64 features =UBLK_F_ALL & ~UBLK_F_SUPPORT_ZERO_COPY ; +u64 features =UBLK_F_ALL ;if (header->len !=UBLK_FEATURES_LEN || !header->addr)return -EINVAL ;
include/uapi/linux/ublk_cmd.h +4 -0
@@ -94,6 +94,10 @@ _IOWR('u', UBLK_IO_COMMIT_AND_FETCH_REQ, struct ublksrv_io_cmd)#define UBLK_U_IO_NEED_GET_DATA \ _IOWR('u', UBLK_IO_NEED_GET_DATA, struct ublksrv_io_cmd) +#define UBLK_U_IO_REGISTER_IO_BUF \ + _IOWR('u', 0x23, struct ublksrv_io_cmd) +#define UBLK_U_IO_UNREGISTER_IO_BUF \ + _IOWR('u', 0x24, struct ublksrv_io_cmd)/* only ABORT means that no re-fetch */ #define UBLK_IO_RES_OK 0
Signature · 1 line
2.43.5
Re: [PATCHv2 4/6] ublk: zc register/unregister bvec
Quoted 20 lines · Keith Busch
From: Keith Busch <[email protected]>
Provide new operations for the user to request mapping an active request to an io uring instance's
buf_table. The user has to provide the index it wants to install the buffer.A reference count is taken on the request to ensure it can't be completed while it is active in a ring's
buf_table.Signed-off-by Keith Busch <[email protected]>--- drivers/block/ublk_drv.c | 145 +++++++++++++++++++++++++--------- include/uapi/linux/ublk_cmd.h | 4 + 2 files changed, 113 insertions(+), 36 deletions(-)drivers/block/ublk_drv.c +0
-0
drivers/block/ublk_drv.c line 55
Quoted 5 lines
drivers/block/ublk_drv.c +2
-0@@ -51,6 +51,9 @@/* private ioctl command mirror */ #define UBLK_CMD_DEL_DEV_ASYNC _IOC_NR(UBLK_U_CMD_DEL_DEV_ASYNC) +#define UBLK_IO_REGISTER_IO_BUF _IOC_NR(UBLK_U_IO_REGISTER_IO_BUF) +#define UBLK_IO_UNREGISTER_IO_BUF _IOC_NR(UBLK_U_IO_UNREGISTER_IO_BUF)
UBLK_IO_REGISTER_IO_BUF command may be completed, and buffer isn't used by RW_FIXED yet in the following cases:
- application doesn't submit any RW_FIXED consumer OP
- io_uring_enter() only issued UBLK_IO_REGISTER_IO_BUF, and the other OPs can't be issued because of out of resource
...
Then io_uring_enter() returns, and the application is panic or killed, how to avoid buffer leak?
It need to deal with in io_uring cancel code for calling ->release() if the kbuffer node isn't released.
UBLK_IO_UNREGISTER_IO_BUF still need to call ->release() if the node buffer isn't used.
Quoted 63 lines
drivers/block/ublk_drv.c +33
-2+/* All UBLK_F_* have to be included into UBLK_F_ALL */ #define UBLK_F_ALL (UBLK_F_SUPPORT_ZERO_COPY \ | UBLK_F_URING_CMD_COMP_IN_TASK \@@ -76,6 +79,9 @@ struct ublk_rq_data {struct llist_node node;struct kref ref; + +#define UBLK_ZC_REGISTERED 0 +unsigned long flags; };struct ublk_uring_cmd_pdu {@@ -201,7 +207,7 @@ static inline struct ublksrv_io_desc *ublk_get_iod(struct ublk_queue *ubq, int tag);static inline bool ublk_dev_is_user_copy (const struct ublk_device *ub) { -return ub->dev_info.flags &UBLK_F_USER_COPY ; +return ub->dev_info.flags & (UBLK_F_USER_COPY |UBLK_F_SUPPORT_ZERO_COPY ); }static inline bool ublk_dev_is_zoned (const struct ublk_device *ub)@@ -581,7 +587,7 @@ static void ublk_apply_params(struct ublk_device *ub)static inline bool ublk_support_user_copy (const struct ublk_queue *ubq) { -return ubq->flags &UBLK_F_USER_COPY ; +return ubq->flags & (UBLK_F_USER_COPY |UBLK_F_SUPPORT_ZERO_COPY ); }static inline bool ublk_need_req_ref (const struct ublk_queue *ubq)@@ -1747,6 +1753,102 @@ static inline void ublk_prep_cancel(struct io_uring_cmd *cmd,io_uring_cmd_mark_cancelable (cmd, issue_flags); } +static inline struct request *__ublk_check_and_get_req (struct ublk_device *ub, +struct ublk_queue *ubq,int tag,size_t offset) +{ +struct request *req; + +if (!ublk_need_req_ref (ubq)) +return NULL ; + + req =blk_mq_tag_to_rq (ub->tag_set.tags[ubq->q_id], tag); +if (!req) +return NULL ; + +if (!ublk_get_req_ref (ubq, req)) +return NULL ; + +if (unlikely (!blk_mq_request_started (req) || req->tag != tag)) + goto fail_put; + +if (!ublk_rq_has_data (req)) + goto fail_put; + +if (offset >blk_rq_bytes (req)) + goto fail_put; + +return req; +fail_put: +ublk_put_req_ref (ubq, req);
drivers/block/ublk_drv.c static inline void ublk_prep_cancel(struct io_uring_cmd *cmd, · line 1792
Quoted 10 lines
drivers/block/ublk_drv.c +10
-0+return NULL ; +} + +static void ublk_io_release (void *priv) +{ +struct request *rq = priv; +struct ublk_queue *ubq = rq->mq_hctx->driver_data; + +ublk_put_req_ref (ubq, rq); +}
It isn't enough to just get & put request reference here between registering buffer and freeing the registered node buf, because the same reference can be dropped from ublk_commit_completion() which is from queueing UBLK_IO_COMMIT_AND_FETCH_REQ, and buggy app may queue this command multiple times for freeing the request.
One solution is to not allow request completion until the ->release() is returned.
Thanks,
Ming
Re: [PATCHv2 4/6] ublk: zc register/unregister bvec
Quoted 32 lines · Ming Lei
Quoted 20 lines · Keith Busch
From: Keith Busch <[email protected]>
Provide new operations for the user to request mapping an active request to an io uring instance's
buf_table. The user has to provide the index it wants to install the buffer.A reference count is taken on the request to ensure it can't be completed while it is active in a ring's
buf_table.Signed-off-by Keith Busch <[email protected]>--- drivers/block/ublk_drv.c | 145 +++++++++++++++++++++++++--------- include/uapi/linux/ublk_cmd.h | 4 + 2 files changed, 113 insertions(+), 36 deletions(-)drivers/block/ublk_drv.c +0
-0drivers/block/ublk_drv.c line 55
Quoted 5 lines
drivers/block/ublk_drv.c +2
-0@@ -51,6 +51,9 @@/* private ioctl command mirror */ #define UBLK_CMD_DEL_DEV_ASYNC _IOC_NR(UBLK_U_CMD_DEL_DEV_ASYNC) +#define UBLK_IO_REGISTER_IO_BUF _IOC_NR(UBLK_U_IO_REGISTER_IO_BUF) +#define UBLK_IO_UNREGISTER_IO_BUF _IOC_NR(UBLK_U_IO_UNREGISTER_IO_BUF)
UBLK_IO_REGISTER_IO_BUFcommand may be completed, and buffer isn't used byRW_FIXEDyet in the following cases:- application doesn't submit any
RW_FIXEDconsumer OP
Quoted 7 lines
-
io_uring_enter()only issuedUBLK_IO_REGISTER_IO_BUF, and the other OPs can't be issued because of out of resource...
Then
io_uring_enter()returns, and the application is panic or killed, how to avoid buffer leak?
The death of the uring that registered the node tears down the table that it's registered with, which releases its reference. All good.
Quoted 2 lines
It need to deal with in
io_uringcancel code for calling ->release() if the kbuffer node isn't released.
There should be no situation here where it isn't released after its use is completed. Either the resource was gracefully unregistered or the ring close while it was still active, but either one drops its reference.
Quoted 2 lines
UBLK_IO_UNREGISTER_IO_BUFstill need to call ->release() if the node buffer isn't used.
Only once the last reference is dropped. Which should happen no matter which way the node is freed.
Quoted 14 lines
drivers/block/ublk_drv.c static inline void ublk_prep_cancel(struct io_uring_cmd *cmd, · line 1792
Quoted 7 lines
drivers/block/ublk_drv.c +7
-0+static void ublk_io_release (void *priv) +{ +struct request *rq = priv; +struct ublk_queue *ubq = rq->mq_hctx->driver_data; + +ublk_put_req_ref (ubq, rq); +}It isn't enough to just get & put request reference here between registering buffer and freeing the registered node buf, because the same reference can be dropped from
ublk_commit_completion()which is from queueingUBLK_IO_COMMIT_AND_FETCH_REQ, and buggy app may queue this command multiple times for freeing the request.
Quoted 2 lines
One solution is to not allow request completion until the ->release() is returned.
Double completions are tricky because the same request id can be reused pretty quickly and there's no immediate way to tell if the 2nd completion is a double or a genuine completion of the reused request.
We have rotating sequence numbers in the nvme driver to try to detect a similar situation. So far it hasn't revealed any real bugs as far as I know. This feels like the other side screwed up and that's their fault.
Re: [PATCHv2 4/6] ublk: zc register/unregister bvec
Quoted 36 lines · Keith Busch
Quoted 35 lines · Ming Lei
Quoted 20 lines · Keith Busch
From: Keith Busch <[email protected]>
Provide new operations for the user to request mapping an active request to an io uring instance's
buf_table. The user has to provide the index it wants to install the buffer.A reference count is taken on the request to ensure it can't be completed while it is active in a ring's
buf_table.Signed-off-by Keith Busch <[email protected]>--- drivers/block/ublk_drv.c | 145 +++++++++++++++++++++++++--------- include/uapi/linux/ublk_cmd.h | 4 + 2 files changed, 113 insertions(+), 36 deletions(-)drivers/block/ublk_drv.c +0
-0drivers/block/ublk_drv.c line 55
Quoted 5 lines
drivers/block/ublk_drv.c +2
-0@@ -51,6 +51,9 @@/* private ioctl command mirror */ #define UBLK_CMD_DEL_DEV_ASYNC _IOC_NR(UBLK_U_CMD_DEL_DEV_ASYNC) +#define UBLK_IO_REGISTER_IO_BUF _IOC_NR(UBLK_U_IO_REGISTER_IO_BUF) +#define UBLK_IO_UNREGISTER_IO_BUF _IOC_NR(UBLK_U_IO_UNREGISTER_IO_BUF)
UBLK_IO_REGISTER_IO_BUFcommand may be completed, and buffer isn't used byRW_FIXEDyet in the following cases:- application doesn't submit any
RW_FIXEDconsumer OP-
io_uring_enter()only issuedUBLK_IO_REGISTER_IO_BUF, and the other OPs can't be issued because of out of resource
Quoted 7 lines
Quoted 4 lines
...
Then
io_uring_enter()returns, and the application is panic or killed, how to avoid buffer leak?The death of the uring that registered the node tears down the table that it's registered with, which releases its reference. All good.
OK, looks I miss the point.
io_sqe_buffers_unregister() is called from io_ring_ctx_free(), when the registered buffer can be released.
However, it still may cause use-after-free on this request which has been failed from io_uring_try_cancel_uring_cmd(), and please see the following code path:
io_uring_try_cancel_requests
io_uring_try_cancel_uring_cmd ublk_uring_cmd_cancel_fn ublk_abort_requests ublk_abort_queue __ublk_fail_req ublk_put_req_ref
The above race needs to be covered.
Quoted 32 lines
Quoted 3 lines
It need to deal with in
io_uringcancel code for calling ->release() if the kbuffer node isn't released.There should be no situation here where it isn't released after its use is completed. Either the resource was gracefully unregistered or the ring close while it was still active, but either one drops its reference.
Quoted 2 lines
UBLK_IO_UNREGISTER_IO_BUFstill need to call ->release() if the node buffer isn't used.Only once the last reference is dropped. Which should happen no matter which way the node is freed.
Quoted 14 lines
drivers/block/ublk_drv.c static inline void ublk_prep_cancel(struct io_uring_cmd *cmd, · line 1792
Quoted 7 lines
drivers/block/ublk_drv.c +7
-0+static void ublk_io_release (void *priv) +{ +struct request *rq = priv; +struct ublk_queue *ubq = rq->mq_hctx->driver_data; + +ublk_put_req_ref (ubq, rq); +}It isn't enough to just get & put request reference here between registering buffer and freeing the registered node buf, because the same reference can be dropped from
ublk_commit_completion()which is from queueingUBLK_IO_COMMIT_AND_FETCH_REQ, and buggy app may queue this command multiple times for freeing the request.Quoted 2 lines
One solution is to not allow request completion until the ->release() is returned.
Quoted 7 lines
Double completions are tricky because the same request id can be reused pretty quickly and there's no immediate way to tell if the 2nd completion is a double or a genuine completion of the reused request.
We have rotating sequence numbers in the nvme driver to try to detect a similar situation. So far it hasn't revealed any real bugs as far as I know. This feels like the other side screwed up and that's their fault.
Not same with nvme, in which nvme controller won't run DMA on this buffer after the 1st completion.
The ublk request buffer has been leased to io_uring for running read_fixed/write_fixed, meantime it is freed and reused by kernel for other purpose.
As I mentioned, it can be solved by not allowing to complete the IO command if the buffer is leased to io_uring.
Thanks,
Ming
Re: [PATCHv2 4/6] ublk: zc register/unregister bvec
Quoted 7 lines · Ming Lei
On Tue, Feb 11, 2025 at 09:11:10PM -0700, Keith Busch wrote:
However, it still may cause use-after-free on this request which has been failed from
io_uring_try_cancel_uring_cmd(), and please see the following code path:
io_uring_try_cancel_requests
Quoted 8 lines
io_uring_try_cancel_uring_cmd ublk_uring_cmd_cancel_fn ublk_abort_requests ublk_abort_queue __ublk_fail_req ublk_put_req_refThe above race needs to be covered.
This race is covered. The ublk request has one reference taken when it notifies userspace, then a second reference taken when user registers the bvec.
The first reference is dropped from the abort, but the request won't be completed because the second reference prevents that. That second request reference can't be dropped until the ->release callback happens, and that can't happen until two conditions are met:
The bvec is unregistered
All IO using the index completes
I think all the bases are covered here.
Re: [PATCHv2 4/6] ublk: zc register/unregister bvec
In reply to Keith BuschQuoted 20 lines · Keith Busch
From: Keith Busch <[email protected]>
Provide new operations for the user to request mapping an active request to an io uring instance's
buf_table. The user has to provide the index it wants to install the buffer.A reference count is taken on the request to ensure it can't be completed while it is active in a ring's
buf_table.Signed-off-by Keith Busch <[email protected]>--- drivers/block/ublk_drv.c | 145 +++++++++++++++++++++++++--------- include/uapi/linux/ublk_cmd.h | 4 + 2 files changed, 113 insertions(+), 36 deletions(-)drivers/block/ublk_drv.c +0
-0@@ -51,6 +51,9 @@
...
Quoted 10 lines
drivers/block/ublk_drv.c +10
-0+static int ublk_unregister_io_buf (struct io_uring_cmd *cmd, +struct ublk_queue *ubq,int tag, +const struct ublksrv_io_cmd *ub_cmd) +{ +struct io_ring_ctx *ctx =cmd_to_io_kiocb (cmd)->ctx; +struct ublk_device *ub = cmd->file->private_data; +int index = (int )ub_cmd->addr; +struct ublk_rq_data *data; +struct request *req; +
drivers/block/ublk_drv.c static inline void ublk_prep_cancel(struct io_uring_cmd *cmd, · line 1846
Quoted 10 lines
drivers/block/ublk_drv.c +10
-0+if (!ub) +return -EPERM ; + + req =blk_mq_tag_to_rq (ub->tag_set.tags[ubq->q_id], tag); +if (!req) +return -EINVAL ; + + data =blk_mq_rq_to_pdu (req); +if (!test_and_clear_bit (UBLK_ZC_REGISTERED , &data->flags)) +return -EINVAL ;
Why is it cleared here but not when it's auto removed? Do you even need it? For example, take the option of not having UBLK_U_IO_UNREGISTER_IO_BUF and doing all unregistrations the normal io_uring way. Then you install it, and you know it'll be released once and no protection needed.
For ublk_unregister_io_buf() it prevents multiple unregistrations, even though io_uring would tolerate that, and I don't see anything else meaningful going on here on the ublk side.
Btw, if you do it via ublk like this, then I agree, it's an additional callback, though it can be made fancier in the future. E.g. peeking at the {release,priv} and avoid flagging above, and so on (though maybe flagging helps with ref overflows).
All that aside, it looks fine in general. The only concern is ublk going away before all buffers are released, but maybe it does waits for all its requests to compelte?
drivers/block/ublk_drv.c
Quoted 2 lines
drivers/block/ublk_drv.c +2
-0+ +io_buffer_unregister_bvec (ctx, index);
Please pass issue_flags into the helper and do conditional locking inside. I understand that you rely on IO_URING_F_UNLOCKED checks in ublk, but those are an abuse of io_uring internal details by ublk. ublk should've never been allowed to interpret issue_flags.
Signature · 1 line
Pavel Begunkov
PATCH v2 5/6 io_uring: add abstraction for buf_table rsrc data
In reply to Keith BuschFrom: Keith Busch <[email protected]>
We'll need to add more fields specific to the registered buffers, so make a layer for it now. No functional change in this patch.
--- include/linux/io_uring_types.h | 6 +++- io_uring/fdinfo.c | 8 +++--- io_uring/net.c | 2 +- io_uring/nop.c | 2 +- io_uring/register.c | 2 +- io_uring/rsrc.c | 51 +++++++++++++++++----------------- io_uring/rw.c | 2 +- io_uring/uring_cmd.c | 2 +- 8 files changed, 39 insertions(+), 36 deletions(-)
include/linux/io_uring_types.h +5 -1
@@ -67,6 +67,10 @@ struct io_file_table {unsigned int alloc_hint; }; +struct io_buf_table { +struct io_rsrc_data data; +}; +struct io_hash_bucket {struct hlist_head list; }____cacheline_aligned_in_smp ;
@@ -291,7 +295,7 @@ struct io_ring_ctx {struct io_wq_work_list iopoll_list;struct io_file_table file_table; -struct io_rsrc_data buf_table; +struct io_buf_table buf_table;struct io_submit_state submit_state;
io_uring/fdinfo.c +4 -4
@@ -217,12 +217,12 @@ __cold void io_uring_show_fdinfo(struct seq_file *m, struct file *file)seq_puts (m,"\n" ); } } -seq_printf (m,"UserBufs:\t%u\n" , ctx->buf_table.nr); -for (i =0 ; has_lock && i < ctx->buf_table.nr; i++) { +seq_printf (m,"UserBufs:\t%u\n" , ctx->buf_table.data.nr); +for (i =0 ; has_lock && i < ctx->buf_table.data.nr; i++) {struct io_mapped_ubuf *buf =NULL ; -if (ctx->buf_table.nodes[i]) - buf = ctx->buf_table.nodes[i]->buf; +if (ctx->buf_table.data.nodes[i]) + buf = ctx->buf_table.data.nodes[i]->buf;if (buf)seq_printf (m,"%5u: 0x%llx/%u\n" , i, buf->ubuf, buf->len);else
io_uring/net.c +1 -1
@@ -1366,7 +1366,7 @@ static int io_send_zc_import(struct io_kiocb *req, unsigned int issue_flags) ret = -EFAULT ;io_ring_submit_lock (ctx, issue_flags); - node =io_rsrc_node_lookup (&ctx->buf_table, sr->buf_index); + node =io_rsrc_node_lookup (&ctx->buf_table.data, sr->buf_index);if (node) {io_req_assign_buf_node (sr->notif, node); ret =0 ;
io_uring/nop.c +1 -1
@@ -69,7 +69,7 @@ int io_nop(struct io_kiocb *req, unsigned int issue_flags) ret = -EFAULT ;io_ring_submit_lock (ctx, issue_flags); - node =io_rsrc_node_lookup (&ctx->buf_table, nop->buffer); + node =io_rsrc_node_lookup (&ctx->buf_table.data, nop->buffer);if (node) {io_req_assign_buf_node (req, node); ret =0 ;
io_uring/register.c +1 -1
@@ -919,7 +919,7 @@ SYSCALL_DEFINE4(io_uring_register, unsigned int, fd, unsigned int, opcode, ret =__io_uring_register (ctx, opcode, arg, nr_args);trace_io_uring_register (ctx, opcode, ctx->file_table.data.nr, - ctx->buf_table.nr, ret); + ctx->buf_table.data.nr, ret);mutex_unlock (&ctx->uring_lock);fput (file);
io_uring/rsrc.c +25 -26
156 lines not on this page · raw message
io_uring/rw.c +1 -1
@@ -363,7 +363,7 @@ static int io_prep_rw_fixed(struct io_kiocb *req, const struct io_uring_sqe *sqeif (unlikely (ret))return ret; - node =io_rsrc_node_lookup (&ctx->buf_table, req->buf_index); + node =io_rsrc_node_lookup (&ctx->buf_table.data, req->buf_index);if (!node)return -EFAULT ;io_req_assign_buf_node (req, node);
io_uring/uring_cmd.c +1 -1
@@ -206,7 +206,7 @@ int io_uring_cmd_prep(struct io_kiocb *req, const struct io_uring_sqe *sqe)struct io_rsrc_node *node;u16 index =READ_ONCE (sqe->buf_index); - node =io_rsrc_node_lookup (&ctx->buf_table, index); + node =io_rsrc_node_lookup (&ctx->buf_table.data, index);if (unlikely (!node))return -EFAULT ;/*
Signature · 1 line
2.43.5
PATCH v2 6/6 io_uring: cache nodes and mapped buffers
In reply to Keith BuschFrom: Keith Busch <[email protected]>
Frequent alloc/free cycles on these is pretty costly. Use an io cache to more efficiently reuse these buffers.
--- include/linux/io_uring_types.h | 18 +++--- io_uring/filetable.c | 2 +- io_uring/rsrc.c | 115 +++++++++++++++++++++++++-------- io_uring/rsrc.h | 2 +- 4 files changed, 101 insertions(+), 36 deletions(-)
include/linux/io_uring_types.h +10 -8
@@ -67,8 +67,18 @@ struct io_file_table {unsigned int alloc_hint; }; +struct io_alloc_cache { +void **entries; +unsigned int nr_cached; +unsigned int max_cached; +size_t elem_size; +unsigned int init_clear; +}; +struct io_buf_table {struct io_rsrc_data data; +struct io_alloc_cache node_cache; +struct io_alloc_cache imu_cache; };struct io_hash_bucket {
@@ -222,14 +232,6 @@ struct io_submit_state {struct blk_plug plug; }; -struct io_alloc_cache { -void **entries; -unsigned int nr_cached; -unsigned int max_cached; -unsigned int elem_size; -unsigned int init_clear; -}; -struct io_ring_ctx {/* const or read-mostly hot data */ struct {
io_uring/filetable.c +1 -1
@@ -68,7 +68,7 @@ static int io_install_fixed_file(struct io_ring_ctx *ctx, struct file *file,if (slot_index >= ctx->file_table.data.nr)return -EINVAL ; - node =io_rsrc_node_alloc (IORING_RSRC_FILE ); + node =io_rsrc_node_alloc (ctx,IORING_RSRC_FILE );if (!node)return -ENOMEM ;
io_uring/rsrc.c +89 -26
276 lines not on this page · raw message
io_uring/rsrc.h +1 -1
@@ -49,7 +49,7 @@ struct io_imu_folio_data {unsigned int nr_folios; }; -struct io_rsrc_node *io_rsrc_node_alloc (int type); +struct io_rsrc_node *io_rsrc_node_alloc (struct io_ring_ctx *ctx,int type);void io_free_rsrc_node (struct io_ring_ctx *ctx,struct io_rsrc_node *node);void io_rsrc_data_free (struct io_ring_ctx *ctx,struct io_rsrc_data *data);int io_rsrc_data_alloc (struct io_rsrc_data *data,unsigned nr);
Signature · 1 line
2.43.5
kernel test robot
build botRe: [PATCHv2 6/6] io_uring: cache nodes and mapped buffers
kernel test robot noticed the following build errors:
Hi Keith,
kernel test robot noticed the following build errors:
[auto build test ERROR on axboe-block/for-next]
[also build test ERROR on linus/master v6.14-rc2 next-20250210] [If your patch is applied to the wrong git tree, kindly drop us a note. And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]
url: https://github.com/intel-lab-lkp/linux/commits/Keith-Busch/io_uring-use-node-for-import/20250211-091231 base: https://git.kernel.org/pub/scm/linux/kernel/git/axboe/linux-block.git for-next patch link: https://lore.kernel.org/r/20250211005646.222452-7-kbusch%40meta.com patch subject: [PATCHv2 6/6] io_uring: cache nodes and mapped buffers config: i386-buildonly-randconfig-004-20250211 (https://download.01.org/0day-ci/archive/20250211/[email protected]/config) compiler: gcc-12 (Debian 12.2.0-14) 12.2.0 reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20250211/[email protected]/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <[email protected]>
| Closes: https://lore.kernel.org/oe-kbuild-all/[email protected]/
All errors (new ones prefixed by >>):
In file included from <command-line>:
In function 'io_rsrc_buffer_alloc',
inlined from 'io_clone_buffers' at io_uring/rsrc.c:1119:8:
>> include/linux/compiler_types.h:542:45: error: call to '__compiletime_assert_432' declared with attribute error: BUILD_BUG_ON failed: imu_cache_size != 512
542 | _compiletime_assert(condition, msg, __compiletime_assert_, __COUNTER__)
| ^
include/linux/compiler_types.h:523:25: note: in definition of macro '__compiletime_assert'
523 | prefix ## suffix(); \
| ^~~~~~
include/linux/compiler_types.h:542:9: note: in expansion of macro '_compiletime_assert'
542 | _compiletime_assert(condition, msg, __compiletime_assert_, __COUNTER__)
| ^~~~~~~~~~~~~~~~~~~
include/linux/build_bug.h:39:37: note: in expansion of macro 'compiletime_assert'
39 | #define BUILD_BUG_ON_MSG(cond, msg) compiletime_assert(!(cond), msg)
| ^~~~~~~~~~~~~~~~~~
include/linux/build_bug.h:50:9: note: in expansion of macro 'BUILD_BUG_ON_MSG'
50 | BUILD_BUG_ON_MSG(condition, "BUILD_BUG_ON failed: " #condition)
| ^~~~~~~~~~~~~~~~
io_uring/rsrc.c:180:9: note: in expansion of macro 'BUILD_BUG_ON'
180 | BUILD_BUG_ON(imu_cache_size != 512);
| ^~~~~~~~~~~~
In function 'io_rsrc_buffer_alloc',
inlined from 'io_sqe_buffers_register' at io_uring/rsrc.c:881:8:
>> include/linux/compiler_types.h:542:45: error: call to '__compiletime_assert_432' declared with attribute error: BUILD_BUG_ON failed: imu_cache_size != 512
542 | _compiletime_assert(condition, msg, __compiletime_assert_, __COUNTER__)
| ^
include/linux/compiler_types.h:523:25: note: in definition of macro '__compiletime_assert'
523 | prefix ## suffix(); \
| ^~~~~~
include/linux/compiler_types.h:542:9: note: in expansion of macro '_compiletime_assert'
542 | _compiletime_assert(condition, msg, __compiletime_assert_, __COUNTER__)
| ^~~~~~~~~~~~~~~~~~~
include/linux/build_bug.h:39:37: note: in expansion of macro 'compiletime_assert'
39 | #define BUILD_BUG_ON_MSG(cond, msg) compiletime_assert(!(cond), msg)
| ^~~~~~~~~~~~~~~~~~
include/linux/build_bug.h:50:9: note: in expansion of macro 'BUILD_BUG_ON_MSG'
50 | BUILD_BUG_ON_MSG(condition, "BUILD_BUG_ON failed: " #condition)
| ^~~~~~~~~~~~~~~~
io_uring/rsrc.c:180:9: note: in expansion of macro 'BUILD_BUG_ON'
180 | BUILD_BUG_ON(imu_cache_size != 512);
| ^~~~~~~~~~~~vim +/__compiletime_assert_432 +542 include/linux/compiler_types.h
eb5c2d4b45e3d2 Will Deacon 2020-07-21 528 eb5c2d4b45e3d2 Will Deacon 2020-07-21 529 #define _compiletime_assert(condition, msg, prefix, suffix) \ eb5c2d4b45e3d2 Will Deacon 2020-07-21 530 __compiletime_assert(condition, msg, prefix, suffix) eb5c2d4b45e3d2 Will Deacon 2020-07-21 531 eb5c2d4b45e3d2 Will Deacon 2020-07-21 532 /** eb5c2d4b45e3d2 Will Deacon 2020-07-21 533 * compiletime_assert - break build and emit msg if condition is false eb5c2d4b45e3d2 Will Deacon 2020-07-21 534 * @condition: a compile-time constant condition to check eb5c2d4b45e3d2 Will Deacon 2020-07-21 535 * @msg: a message to emit if condition is false eb5c2d4b45e3d2 Will Deacon 2020-07-21 536 * eb5c2d4b45e3d2 Will Deacon 2020-07-21 537 * In tradition of POSIX assert, this macro will break the build if the eb5c2d4b45e3d2 Will Deacon 2020-07-21 538 * supplied condition is *false*, emitting the supplied error message if the eb5c2d4b45e3d2 Will Deacon 2020-07-21 539 * compiler has support to do so. eb5c2d4b45e3d2 Will Deacon 2020-07-21 540 */ eb5c2d4b45e3d2 Will Deacon 2020-07-21 541 #define compiletime_assert(condition, msg) \
eb5c2d4b45e3d2 Will Deacon 2020-07-21 @542 _compiletime_assert(condition, msg, __compiletime_assert_, __COUNTER__)eb5c2d4b45e3d2 Will Deacon 2020-07-21 543
Signature · 2 lines
0-DAY CI Kernel Test Service https://github.com/intel/lkp-tests/wiki
Re: [PATCHv2 6/6] io_uring: cache nodes and mapped buffers
In reply to Keith Buschio_uring/rsrc.c __cold int io_rsrc_data_alloc(struct io_rsrc_data *data, unsigned nr) · line 180
Quoted 7 lines · Keith Busch
io_uring/rsrc.c +7
-0+static __cold int io_rsrc_buffer_alloc (struct io_buf_table *table,unsigned nr) +{ + const int imu_cache_size = struct_size_t(struct io_mapped_ubuf, bvec, +IO_CACHED_BVECS_SEGS ); +int ret; + +BUILD_BUG_ON (imu_cache_size !=512 );
This isn't accurate for 32-bit machines.
kernel test robot
build botRe: [PATCHv2 6/6] io_uring: cache nodes and mapped buffers
In reply to Keith Buschkernel test robot noticed the following build errors:
Hi Keith,
kernel test robot noticed the following build errors:
[auto build test ERROR on axboe-block/for-next]
[also build test ERROR on linus/master v6.14-rc2 next-20250210] [If your patch is applied to the wrong git tree, kindly drop us a note. And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]
url: https://github.com/intel-lab-lkp/linux/commits/Keith-Busch/io_uring-use-node-for-import/20250211-091231 base: https://git.kernel.org/pub/scm/linux/kernel/git/axboe/linux-block.git for-next patch link: https://lore.kernel.org/r/20250211005646.222452-7-kbusch%40meta.com patch subject: [PATCHv2 6/6] io_uring: cache nodes and mapped buffers config: hexagon-randconfig-001-20250212 (https://download.01.org/0day-ci/archive/20250212/[email protected]/config) compiler: clang version 21.0.0git (https://github.com/llvm/llvm-project 6807164500e9920638e2ab0cdb4bf8321d24f8eb) reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20250212/[email protected]/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <[email protected]>
| Closes: https://lore.kernel.org/oe-kbuild-all/[email protected]/
All errors (new ones prefixed by >>):
>> io_uring/rsrc.c:180:2: error: call to '__compiletime_assert_443' declared with 'error' attribute: BUILD_BUG_ON failed: imu_cache_size != 512
180 | BUILD_BUG_ON(imu_cache_size != 512);
| ^
include/linux/build_bug.h:50:2: note: expanded from macro 'BUILD_BUG_ON'
50 | BUILD_BUG_ON_MSG(condition, "BUILD_BUG_ON failed: " #condition)
| ^
include/linux/build_bug.h:39:37: note: expanded from macro 'BUILD_BUG_ON_MSG'
39 | #define BUILD_BUG_ON_MSG(cond, msg) compiletime_assert(!(cond), msg)
| ^
include/linux/compiler_types.h:542:2: note: expanded from macro 'compiletime_assert'
542 | _compiletime_assert(condition, msg, __compiletime_assert_, __COUNTER__)
| ^
include/linux/compiler_types.h:530:2: note: expanded from macro '_compiletime_assert'
530 | __compiletime_assert(condition, msg, prefix, suffix)
| ^
include/linux/compiler_types.h:523:4: note: expanded from macro '__compiletime_assert'
523 | prefix ## suffix(); \
| ^
<scratch space>:193:1: note: expanded from here
193 | __compiletime_assert_443
| ^
>> io_uring/rsrc.c:180:2: error: call to '__compiletime_assert_443' declared with 'error' attribute: BUILD_BUG_ON failed: imu_cache_size != 512
include/linux/build_bug.h:50:2: note: expanded from macro 'BUILD_BUG_ON'
50 | BUILD_BUG_ON_MSG(condition, "BUILD_BUG_ON failed: " #condition)
| ^
include/linux/build_bug.h:39:37: note: expanded from macro 'BUILD_BUG_ON_MSG'
39 | #define BUILD_BUG_ON_MSG(cond, msg) compiletime_assert(!(cond), msg)
| ^
include/linux/compiler_types.h:542:2: note: expanded from macro 'compiletime_assert'
542 | _compiletime_assert(condition, msg, __compiletime_assert_, __COUNTER__)
| ^
include/linux/compiler_types.h:530:2: note: expanded from macro '_compiletime_assert'
530 | __compiletime_assert(condition, msg, prefix, suffix)
| ^
include/linux/compiler_types.h:523:4: note: expanded from macro '__compiletime_assert'
523 | prefix ## suffix(); \
| ^
<scratch space>:193:1: note: expanded from here
193 | __compiletime_assert_443
| ^2 errors generated.
vim +180 io_uring/rsrc.c
173
174 static __cold int io_rsrc_buffer_alloc(struct io_buf_table *table, unsigned nr)
175 {
176 const int imu_cache_size = struct_size_t(struct io_mapped_ubuf, bvec,
177 IO_CACHED_BVECS_SEGS);
178 int ret;
179
> 180 BUILD_BUG_ON(imu_cache_size != 512);
181 ret = io_rsrc_data_alloc(&table->data, nr);
182 if (ret)
183 return ret;
184
185 ret = io_alloc_cache_init(&table->node_cache, nr,
186 sizeof(struct io_rsrc_node), 0);
187 if (ret)
188 goto out_1;
189
190 ret = io_alloc_cache_init(&table->imu_cache, nr, imu_cache_size, 0);
191 if (ret)
192 goto out_2;
193
194 return 0;
195 out_2:
196 io_alloc_cache_free(&table->node_cache, kfree);
197 out_1:
198 __io_rsrc_data_free(&table->data);
199 return ret;
200 }
201
Signature · 2 lines
0-DAY CI Kernel Test Service https://github.com/intel/lkp-tests/wiki
Quoted 6 lines · Keith Busch
From: Keith Busch <[email protected]>
Previous version was discussed here:
Quoted 7 lines
The same ublksrv reference code in that link was used to test the kernel side changes.
Before listing what has changed, I want to mention what is the same: the reliance on the ring ctx lock to serialize the register ahead of any use. I'm not ignoring the feedback; I just don't have a solid answer right now, and want to progress on the other fronts in the meantime.
It is explained in the following links:
https://lore.kernel.org/linux-block/[email protected]/
- node kbuffer is registered in ublk uring_cmd's ->issue(), but lookup in RW_FIXED OP's ->prep(), and ->prep() is always called before calling ->issue() when the two are submitted in same io_uring_enter(), so you need to move io_rsrc_node_lookup() & buffer importing from RW_FIXED's ->prep() to ->issue() first.
- secondly, ->issue() order is only respected by IO_LINK, and io_uring can't provide such guarantee without using IO_LINK:
Pavel explained it in the following link:
https://lore.kernel.org/linux-block/[email protected]/
There are also other examples, such as, register buffer stays in one link chain, and the consumer OP isn't in this chain, the consumer OP can still be issued before issuing register_buffer.
Thanks,
Ming
Quoted 9 lines · Ming Lei
It is explained in the following links:
https://lore.kernel.org/linux-block/[email protected]/
- node kbuffer is registered in ublk uring_cmd's ->issue(), but lookup in
RW_FIXEDOP's ->prep(), and ->prep() is always called before calling ->issue() when the two are submitted in sameio_uring_enter(), so you need to moveio_rsrc_node_lookup()& buffer importing from RW_FIXED's ->prep() to ->issue() first.
I don't think that's accurate, at least in practice. In a normal flow, we'll have this sequence:
io_submit_sqes
io_submit_sqe (uring_cmd ublk register)
io_init_req
->prep()
io_queue_sqe
->issue()
io_submit_sqe (read/write_fixed)
io_init_req
->prep()
io_queue_sqe
->issue()The first SQE is handled in its entirety before even looking at the subsequent SQE. Since the register is first, then the read/write_fixed's prep will have a valid index. Testing this patch series appears to show this reliably works.
Quoted 10 lines
- secondly, ->issue() order is only respected by
IO_LINK, andio_uringcan't provide such guarantee without usingIO_LINK:Pavel explained it in the following link:
https://lore.kernel.org/linux-block/[email protected]/
There are also other examples, such as, register buffer stays in one link chain, and the consumer OP isn't in this chain, the consumer OP can still be issued before issuing
register_buffer.
Yep, I got that. Linking is just something I was hoping to avoid. I understand there are conditions that can break the normal flow I'm relying on regarding the ordering. This hasn't appeared to be a problem in practice, but I agree this needs to be handled.
Quoted 26 lines · Keith Busch
Quoted 9 lines · Ming Lei
It is explained in the following links:
https://lore.kernel.org/linux-block/[email protected]/
- node kbuffer is registered in ublk uring_cmd's ->issue(), but lookup in
RW_FIXEDOP's ->prep(), and ->prep() is always called before calling ->issue() when the two are submitted in sameio_uring_enter(), so you need to moveio_rsrc_node_lookup()& buffer importing from RW_FIXED's ->prep() to ->issue() first.I don't think that's accurate, at least in practice. In a normal flow, we'll have this sequence:
io_submit_sqesio_submit_sqe (uring_cmd ublk register) io_init_req ->prep() io_queue_sqe ->issue() io_submit_sqe (read/write_fixed) io_init_req ->prep() io_queue_sqe ->issue()
Quoted 4 lines
The first SQE is handled in its entirety before even looking at the subsequent SQE. Since the register is first, then the read/write_fixed's prep will have a valid index. Testing this patch series appears to show this reliably works.
Ming describes how it works for links. This one is indeed how non links are normally executed. Though I'd repeat it's an implementation detail and not a part of the uapi. Interestingly, Keith, you sent some patches changing the ordering here quite a while ago, just as an example of how it can change.
Quoted 15 lines
Quoted 10 lines
- secondly, ->issue() order is only respected by
IO_LINK, andio_uringcan't provide such guarantee without usingIO_LINK:Pavel explained it in the following link:
https://lore.kernel.org/linux-block/[email protected]/
There are also other examples, such as, register buffer stays in one link chain, and the consumer OP isn't in this chain, the consumer OP can still be issued before issuing
register_buffer.Yep, I got that. Linking is just something I was hoping to avoid. I understand there are conditions that can break the normal flow I'm relying on regarding the ordering. This hasn't appeared to be a problem in practice, but I agree this needs to be handled.
Signature · 1 line
Pavel Begunkov
Quoted 32 lines · Pavel Begunkov
Quoted 26 lines · Keith Busch
Quoted 9 lines · Ming Lei
It is explained in the following links:
https://lore.kernel.org/linux-block/[email protected]/
- node kbuffer is registered in ublk uring_cmd's ->issue(), but lookup in
RW_FIXEDOP's ->prep(), and ->prep() is always called before calling ->issue() when the two are submitted in sameio_uring_enter(), so you need to moveio_rsrc_node_lookup()& buffer importing from RW_FIXED's ->prep() to ->issue() first.I don't think that's accurate, at least in practice. In a normal flow, we'll have this sequence:
io_submit_sqesio_submit_sqe (uring_cmd ublk register) io_init_req ->prep() io_queue_sqe ->issue() io_submit_sqe (read/write_fixed) io_init_req ->prep() io_queue_sqe ->issue()Quoted 4 lines
The first SQE is handled in its entirety before even looking at the subsequent SQE. Since the register is first, then the read/write_fixed's prep will have a valid index. Testing this patch series appears to show this reliably works.
Quoted 5 lines
Ming describes how it works for links. This one is indeed how non links are normally executed. Though I'd repeat it's an implementation detail and not a part of the uapi. Interestingly, Keith, you sent some patches changing the ordering here quite a while ago, just as an example of how it can change.
My fault, I should have provided the link or async background.
Quoted 9 lines
Quoted 9 lines
Quoted 9 lines
- secondly, ->issue() order is only respected by
IO_LINK, andio_uringcan't provide such guarantee without usingIO_LINK:Pavel explained it in the following link:
https://lore.kernel.org/linux-block/[email protected]/
Quoted 8 lines
Quoted 8 lines
Quoted 3 lines
There are also other examples, such as, register buffer stays in one link chain, and the consumer OP isn't in this chain, the consumer OP can still be issued before issuing
register_buffer.Yep, I got that. Linking is just something I was hoping to avoid. I understand there are conditions that can break the normal flow I'm relying on regarding the ordering. This hasn't appeared to be a problem in practice, but I agree this needs to be handled.
LINK/ASYNC needs to be supported, and sometimes they are useful.
- IO_LINK is the only way for respecting IO order
io_uring only supports non-link or link all in one batch
- ASYNC sometimes can avoid to call two ->issue() unnecessarily if you know that the OP can't be dealt with async way in advance, maybe not one problem for ublk uring_cmd, but it is helpful for some FS write (un-allocated write)
Thanks,
Ming
Hi,
Quoted 31 lines
-----Original Message-----
From: Keith Busch <[email protected]>
Sent: Tuesday, February 11, 2025 8:57 AM
To: [email protected]; [email protected]; [email protected]; linux- [email protected]; [email protected]
Cc: [email protected]; Keith Busch <[email protected]>
Subject: [PATCHv2 0/6] ublk zero-copy supportFrom: Keith Busch <[email protected]>
Previous version was discussed here:
https://lore.kernel.org/linux-block/20250203154517.937623-1-
[email protected]/The same ublksrv reference code in that link was used to test the kernel side changes.
Before listing what has changed, I want to mention what is the same: the reliance on the ring ctx lock to serialize the register ahead of any use. I'm not ignoring the feedback; I just don't have a solid answer right now, and want to progress on the other fronts in the meantime.
Here's what's different from the previous:
- Introduced an optional 'release' callback when the resource node is no longer referenced. The callback addresses any buggy applications that may complete their request and unregister their index while IO is in flight. This obviates any need to take extra page references since it prevents the request from completing.
Quoted 5 lines
- Removed peeking into the
io_cacheelement size and instead use a more intuitive bvec segment count limit to decide if we're caching the imu (suggested by Pavel).- Dropped the const request changes; it's not needed.
I tested this patch set. When I use null as the device, the test results are like your v1.
When the bs is 4k, there is a slight improvement; when the bs is 64k, there is a significant improvement. However, when I used loop as the device, I found that there was no improvement, whether using 4k or 64k. As follow:
ublk add -t loop -f ./ublk-loop.img
ublk add -t loop -f ./ublk-loop-zerocopy.img
fio -filename=/dev/ublkb0 -direct=1 -rw=read -iodepth=1 -ioengine=io_uring -bs=128k -size=5G
read: IOPS=2015, BW=126MiB/s (132MB/s)(1260MiB/10005msec)
fio -filename=/dev/ublkb1 -direct=1 -rw=read -iodepth=1 -ioengine=io_uring -bs=128k -size=5G
read: IOPS=1998, BW=125MiB/s (131MB/s)(1250MiB/10005msec)
So, this patch set is optimized for null type devices? Or if I've missed any key information, please let me know.
---
Li Zetao
Quoted 7 lines · lizetao
I tested this patch set. When I use null as the device, the test results are like your v1.
When the bs is 4k, there is a slight improvement; when the bs is 64k, there is a significant improvement. However, when I used loop as the device, I found that there was no improvement, whether using 4k or 64k. As follow:ublk add -t loop -f ./ublk-loop.img
ublk add -t loop -f ./ublk-loop-zerocopy.img
Quoted 8 lines
fio -filename=/dev/ublkb0 -direct=1 -rw=read -iodepth=1 -ioengine=io_uring -bs=128k -size=5G
read: IOPS=2015, BW=126MiB/s (132MB/s)(1260MiB/10005msec)fio -filename=/dev/ublkb1 -direct=1 -rw=read -iodepth=1 -ioengine=io_uring -bs=128k -size=5G
read: IOPS=1998, BW=125MiB/s (131MB/s)(1250MiB/10005msec)So, this patch set is optimized for null type devices? Or if I've missed any key information, please let me know.
What do you get if if you run your fio job directly on your ublk-loop.img file?
Throughput should improve until you've saturated the backend device. Once you hit that point, the primary benefit of zero-copy come from decreased memory and CPU utilizations.
Hi,
Quoted 24 lines
-----Original Message-----
From: Keith Busch <[email protected]>
Sent: Friday, February 14, 2025 12:07 AM
To: lizetao <[email protected]>
Cc: Keith Busch <[email protected]>; [email protected]; [email protected]
Subject: Re: [PATCHv2 0/6] ublk zero-copy supportQuoted 1 line · lizetao
I tested this patch set. When I use null as the device, the test results are like
your v1.
Quoted 1 line
When the bs is 4k, there is a slight improvement; when the bs is 64k, there is
a significant improvement.
Quoted 1 line
However, when I used loop as the device, I found that there was no
improvement, whether using 4k or 64k. As follow:
Quoted 5 lines
ublk add -t loop -f ./ublk-loop.img
ublk add -t loop -f ./ublk-loop-zerocopy.imgfio -filename=/dev/ublkb0 -direct=1 -rw=read -iodepth=1 -ioengine=io_uring
-bs=128k -size=5G
Quoted 3 lines
read: IOPS=2015, BW=126MiB/s (132MB/s)(1260MiB/10005msec)fio -filename=/dev/ublkb1 -direct=1 -rw=read -iodepth=1 -ioengine=io_uring
Quoted 8 lines
-bs=128k -size=5G
Quoted 4 lines
read: IOPS=1998, BW=125MiB/s (131MB/s)(1250MiB/10005msec)So, this patch set is optimized for null type devices? Or if I've missed any key
information, please let me know.
What do you get if if you run your fio job directly on your ublk-loop.img file?
I test it directly on ublk-loop.img, and the result is as follow:
fio -filename=./ublk-loop.img -direct=1 -rw=read -iodepth=1 -ioengine=io_uring -bs=128k -size=5G
read: IOPS=1005, BW=126MiB/s (132MB/s)(1258MiB/10009msec)
From the test results, this seems to be limited by the performance limitations of the file system where ./ublk-loop.img is located.
Quoted 3 lines
Throughput should improve until you've saturated the backend device. Once you hit that point, the primary benefit of zero-copy come from decreased memory and CPU utilizations.
---
Li Zetao
Quoted 34 lines · lizetao
Hi,
Quoted 31 lines
-----Original Message-----
From: Keith Busch <[email protected]>
Sent: Tuesday, February 11, 2025 8:57 AM
To: [email protected]; [email protected]; [email protected]; linux- [email protected]; [email protected]
Cc: [email protected]; Keith Busch <[email protected]>
Subject: [PATCHv2 0/6] ublk zero-copy supportFrom: Keith Busch <[email protected]>
Previous version was discussed here:
https://lore.kernel.org/linux-block/20250203154517.937623-1-
[email protected]/The same ublksrv reference code in that link was used to test the kernel side changes.
Before listing what has changed, I want to mention what is the same: the reliance on the ring ctx lock to serialize the register ahead of any use. I'm not ignoring the feedback; I just don't have a solid answer right now, and want to progress on the other fronts in the meantime.
Here's what's different from the previous:
- Introduced an optional 'release' callback when the resource node is no longer referenced. The callback addresses any buggy applications that may complete their request and unregister their index while IO is in flight. This obviates any need to take extra page references since it prevents the request from completing.
Quoted 8 lines
Quoted 5 lines
- Removed peeking into the
io_cacheelement size and instead use a more intuitive bvec segment count limit to decide if we're caching the imu (suggested by Pavel).- Dropped the const request changes; it's not needed.
I tested this patch set. When I use null as the device, the test results are like your v1. When the bs is 4k, there is a slight improvement; when the bs is 64k, there is a significant improvement.
Yes, the improvement is usually more obvious with a big IO size(>= 64K).
Quoted 5 lines
However, when I used loop as the device, I found that there was no improvement, whether using 4k or 64k. As follow:
ublk add -t loop -f ./ublk-loop.img
ublk add -t loop -f ./ublk-loop-zerocopy.img
Quoted 8 lines
fio -filename=/dev/ublkb0 -direct=1 -rw=read -iodepth=1 -ioengine=io_uring -bs=128k -size=5G
read: IOPS=2015, BW=126MiB/s (132MB/s)(1260MiB/10005msec)fio -filename=/dev/ublkb1 -direct=1 -rw=read -iodepth=1 -ioengine=io_uring -bs=128k -size=5G
read: IOPS=1998, BW=125MiB/s (131MB/s)(1250MiB/10005msec)So, this patch set is optimized for null type devices? Or if I've missed any key information, please let me know.
Latency may have decreased a bit.
System sources can't be saturated in single queue depth, please run the same test with
high queue depth per Keith's suggestion:
--iodepth=128 --iodepth_batch_submit=16 --iodepth_batch_complete_min=16
Also if you set up the backing file as ramfs image, the improvement should be pretty
obvious, I observed IOPS doubled in this way.
Thanks,
Ming
Hi,
Quoted 75 lines
-----Original Message-----
From: Ming Lei <[email protected]>
Sent: Friday, February 14, 2025 10:41 AM
To: lizetao <[email protected]>
Cc: Keith Busch <[email protected]>; [email protected]; [email protected]; Ming Lei <[email protected]>
Subject: Re: [PATCHv2 0/6] ublk zero-copy supportQuoted 25 lines · lizetao
Hi,
Quoted 22 lines
-----Original Message-----
From: Keith Busch <[email protected]>
Sent: Tuesday, February 11, 2025 8:57 AM
To: [email protected]; [email protected]; [email protected]; linux- [email protected]; [email protected]
Cc: [email protected]; Keith Busch <[email protected]>
Subject: [PATCHv2 0/6] ublk zero-copy supportFrom: Keith Busch <[email protected]>
Previous version was discussed here:
https://lore.kernel.org/linux-block/20250203154517.937623-1-
[email protected]/The same ublksrv reference code in that link was used to test the kernel side changes.
Before listing what has changed, I want to mention what is the same:
the reliance on the ring ctx lock to serialize the register ahead of any use. I'm not ignoring the feedback; I just don't have a solid answer right now, and want to progress on the other fronts in themeantime.
Quoted 9 lines
Quoted 9 lines
Here's what's different from the previous:
- Introduced an optional 'release' callback when the resource node is no longer referenced. The callback addresses any buggy applications that may complete their request and unregister their index while IO is in flight. This obviates any need to take extra page references since it prevents the request from completing.
Quoted 7 lines
Quoted 5 lines
- Removed peeking into the
io_cacheelement size and instead use a more intuitive bvec segment count limit to decide if we're caching the imu (suggested by Pavel).- Dropped the const request changes; it's not needed.
I tested this patch set. When I use null as the device, the test results are like
your v1.
Quoted 1 line
When the bs is 4k, there is a slight improvement; when the bs is 64k, there is
a significant improvement.
Yes, the improvement is usually more obvious with a big IO size(>= 64K).
Quoted 1 line
However, when I used loop as the device, I found that there was no
improvement, whether using 4k or 64k. As follow:
Quoted 5 lines
ublk add -t loop -f ./ublk-loop.img
ublk add -t loop -f ./ublk-loop-zerocopy.imgfio -filename=/dev/ublkb0 -direct=1 -rw=read -iodepth=1 -ioengine=io_uring
-bs=128k -size=5G
Quoted 3 lines
read: IOPS=2015, BW=126MiB/s (132MB/s)(1260MiB/10005msec)fio -filename=/dev/ublkb1 -direct=1 -rw=read -iodepth=1 -ioengine=io_uring
-bs=128k -size=5G
Quoted 4 lines
read: IOPS=1998, BW=125MiB/s (131MB/s)(1250MiB/10005msec)So, this patch set is optimized for null type devices? Or if I've missed any key
information, please let me know.
Quoted 7 lines
Latency may have decreased a bit.
System sources can't be saturated in single queue depth, please run the same test with high queue depth per Keith's suggestion:
--iodepth=128 --iodepth_batch_submit=16 --iodepth_batch_complete_min=16
I tested it with these settings, but the result is similar to iodepth=1:
fio -filename=/dev/ublkb0 -direct=1 -rw=read --iodepth=128 --iodepth_batch_submit=16 --iodepth_batch_complete_min=16 -ioengine=io_uring -bs=64k -size=8G -numjobs=10
read: IOPS=2182, BW=136MiB/s (143MB/s)(1440MiB/10558msec)
fio -filename=/dev/ublkb1 -direct=1 -rw=read --iodepth=128 --iodepth_batch_submit=16 --iodepth_batch_complete_min=16 -ioengine=io_uring -bs=64k -size=8G -numjobs=10
read: IOPS=2174, BW=136MiB/s (143MB/s)(1438MiB/10580msec)So I believe this is limited by the performance limitations of the file system where ./ublk-loop.img is located.
Quoted 3 lines
Also if you set up the backing file as ramfs image, the improvement should be pretty obvious, I observed IOPS doubled in this way.
This is true, I tested it in /tmp/ and got a large optimizations. The results as follow:
fio -filename=/dev/ublkb0 -direct=1 -rw=read --iodepth=128 --iodepth_batch_submit=16 --iodepth_batch_complete_min=16 -ioengine=io_uring -bs=64k -size=8G -numjobs=10
read: IOPS=95.8k, BW=5985MiB/s (6276MB/s)(58.5GiB/10014msec)
fio -filename=/dev/ublkb1 -direct=1 -rw=read --iodepth=128 --iodepth_batch_submit=16 --iodepth_batch_complete_min=16 -ioengine=io_uring -bs=64k -size=8G -numjobs=10
read: IOPS=170k, BW=10.4GiB/s (11.1GB/s)(80.0GiB/7721msec)So this test result is in line with expectations.
---
Li Zetao