5.20 cleanups and poll optimisations
cover 5.20 cleanups and poll optimisations
1-13 are cleanups after splitting io_uring into files.
Patch 14 from Hao should remove some overhead from poll requests
Patch 15 from Hao adds per-bucket spinlocks, and 16-19 do a little bit of cleanup. The downside of per-bucket spinlocks is that it adds additional spinlock/unlock pair in the poll request completion side, which shouldn't matter much with 20/25.
Patch 20 uses inline completion infra for poll requests, this nicely improves perf when there is a good tw batching.
Patch 21 implements the userspace visible side of IORING_SETUP_SINGLE_ISSUER, it'll be used for poll requests and later for spinlock optimisations.
22-25 introduces ->uring_lock protected cancellation hashing. It requires us to grab ->uring_lock in the completion side, but saves two spin lock/unlock pairs. We apply it automatically in cases the mutex is already likely to be held (see 25/25 description), so there is no additional mutex overhead and potential latency problemes.
Numbers:
I used a simple poll benchmark temporarily stored at [1], which each iteration queues a batch of 32 POLLIN poll requests and triggers all of them with read (+write).
baseline (patches 1-18):
11720 K req/s
base + 19 (+ inline completion infra)
12419 K req/s, ~+6%
base + 19-25 (+ uring_lock hashing):
12804 K req/s, +9.2% from the baseline, or +3.2% relative to patch 19.[1] https://github.com/isilence/liburing/tree/poll-bench
Hao Xu (2):
io_uring: poll: remove unnecessary req->ref set
io_uring: switch cancel_hash to use per entry spinlock
Pavel Begunkov (23):
io_uring: make reg buf init consistent
io_uring: move defer_list to slow data
io_uring: better caching for ctx timeout fields
io_uring: refactor ctx slow data placement
io_uring: move cancel_seq out of io-wq
io_uring: move small helpers to headers
io_uring: inline ->registered_rings
io_uring: don't set REQ_F_COMPLETE_INLINE in tw
io_uring: never defer-complete multi-apoll
io_uring: kill REQ_F_COMPLETE_INLINE
io_uring: refactor io_req_task_complete()
io_uring: don't inline io_put_kbuf
io_uring: remove check_cq checking from hot paths
io_uring: pass poll_find lock back
io_uring: clean up io_try_cancel
io_uring: limit number hash buckets
io_uring: clean up io_ring_ctx_alloc
io_uring: use state completion infra for poll reqs
io_uring: add IORING_SETUP_SINGLE_ISSUER
io_uring: pass hash table into poll_find
io_uring: introduce a struct for hash table
io_uring: propagate locking state to poll cancel
io_uring: mutex locked poll hashing
include/uapi/linux/io_uring.h | 5 +- io_uring/cancel.c | 27 ++-- io_uring/cancel.h | 4 +- io_uring/fdinfo.c | 11 +- io_uring/io-wq.h | 1 - io_uring/io_uring.c | 149 +++++++++++----------- io_uring/io_uring.h | 17 +++ io_uring/io_uring_types.h | 109 +++++++++------- io_uring/kbuf.c | 33 +++++ io_uring/kbuf.h | 38 +----- io_uring/poll.c | 229 ++++++++++++++++++++++++---------- io_uring/poll.h | 3 +- io_uring/rsrc.c | 9 +- io_uring/tctx.c | 34 +++-- io_uring/tctx.h | 7 +- io_uring/timeout.c | 7 +- 16 files changed, 426 insertions(+), 257 deletions(-)
Signature · 1 line
2.36.1
PATCH 1/25 io_uring: make reg buf init consistent
The default (i.e. empty) state of register buffer is dummy_ubuf, so set it to dummy on init instead of NULL.
--- io_uring/rsrc.c | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-)
io_uring/rsrc.c +3 -6
@@ -567,7 +567,7 @@ static int __io_sqe_buffers_update(struct io_ring_ctx *ctx,io_buffer_unmap (ctx, &imu);break ; } - ctx->user_bufs[i] =NULL ; + ctx->user_bufs[i] = ctx->dummy_ubuf; needs_switch = true; }
@@ -1200,14 +1200,11 @@ static int io_sqe_buffer_register(struct io_ring_ctx *ctx, struct iovec *iov,size_t size;int ret, nr_pages, i; -if (!iov->iov_base) { - *pimu = ctx->dummy_ubuf; + *pimu = ctx->dummy_ubuf; +if (!iov->iov_base)return 0 ; - } - *pimu =NULL ; ret = -ENOMEM ; - pages =io_pin_pages ((unsigned long ) iov->iov_base, iov->iov_len, &nr_pages);if (IS_ERR (pages)) {
Signature · 1 line
2.36.1
PATCH 2/25 io_uring: move defer_list to slow data
In reply to Pavel Begunkovdraining is slow path, move defer_list to the end where slow data lives inside the context.
--- io_uring/io_uring_types.h | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-)
io_uring/io_uring_types.h +4 -1
@@ -160,7 +160,6 @@ struct io_ring_ctx {struct io_uring_sqe *sq_sqes;unsigned cached_sq_head;unsigned sq_entries; -struct list_head defer_list;/* * Fixed resources fast path, should be accessed only under
@@ -272,8 +271,12 @@ struct io_ring_ctx {struct work_struct exit_work;struct list_head tctx_list;struct completion ref_comp; + +/* io-wq management, e.g. thread count */ u32 iowq_limits[2 ];bool iowq_limits_set; + +struct list_head defer_list; }; };
Signature · 1 line
2.36.1
PATCH 3/25 io_uring: better caching for ctx timeout fields
In reply to Pavel BegunkovFollowing timeout fields access patterns, move all of them into a separate cache line inside ctx, so they don't intervene with normal completion caching, especially since timeout removals and completion are separated and the later is done via tw.
It also sheds some bytes from io_ring_ctx, 1216B -> 1152B
--- io_uring/io_uring_types.h | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-)
io_uring/io_uring_types.h +9 -6
@@ -179,8 +179,6 @@ struct io_ring_ctx {struct xarray io_bl_xa;struct list_head io_buffers_cache; -struct list_head timeout_list; -struct list_head ltimeout_list;struct list_head cq_overflow_list;struct list_head apoll_cache;struct xarray personalities;
@@ -213,15 +211,11 @@ struct io_ring_ctx {struct io_ev_fd __rcu *io_ev_fd;struct wait_queue_head cq_wait;unsigned cq_extra; -atomic_t cq_timeouts; -unsigned cq_last_tm_flush; }____cacheline_aligned_in_smp ;struct {spinlock_t completion_lock; -spinlock_t timeout_lock; -/* * ->iopoll_list is protected by the ctx->uring_lock for * io_uring instances that don't use IORING_SETUP_SQPOLL.
@@ -253,6 +247,15 @@ struct io_ring_ctx {struct list_head io_buffers_pages; }; +/* timeouts */ +struct { +spinlock_t timeout_lock; +atomic_t cq_timeouts; +struct list_head timeout_list; +struct list_head ltimeout_list; +unsigned cq_last_tm_flush; + }____cacheline_aligned_in_smp ; +/* Keep this last, we don't need it for the fast path */ struct {#if defined(CONFIG_UNIX )
Signature · 1 line
2.36.1
PATCH 4/25 io_uring: refactor ctx slow data placement
In reply to Pavel BegunkovShove all slow path data at the end of ctx and get rid of extra indention.
--- io_uring/io_uring_types.h | 81 +++++++++++++++++++-------------------- 1 file changed, 39 insertions(+), 42 deletions(-)
io_uring/io_uring_types.h +39 -42
@@ -183,7 +183,6 @@ struct io_ring_ctx {struct list_head apoll_cache;struct xarray personalities;u32 pers_next; -unsigned sq_thread_idle; }____cacheline_aligned_in_smp ;/* IRQ completion list, under ->completion_lock */
@@ -230,23 +229,6 @@ struct io_ring_ctx {struct list_head io_buffers_comp; }____cacheline_aligned_in_smp ; -struct io_restriction restrictions; - -/* slow path rsrc auxilary data, used by update/register */ -struct { -struct io_rsrc_node *rsrc_backup_node; -struct io_mapped_ubuf *dummy_ubuf; -struct io_rsrc_data *file_data; -struct io_rsrc_data *buf_data; - -struct delayed_work rsrc_put_work; -struct llist_head rsrc_put_llist; -struct list_head rsrc_ref_list; -spinlock_t rsrc_ref_lock; - -struct list_head io_buffers_pages; - }; -/* timeouts */ struct {spinlock_t timeout_lock;
@@ -257,30 +239,45 @@ struct io_ring_ctx { }____cacheline_aligned_in_smp ;/* Keep this last, we don't need it for the fast path */ -struct { -#if defined(CONFIG_UNIX ) -struct socket *ring_sock; -#endif -/* hashed buffered write serialization */ -struct io_wq_hash *hash_map; - -/* Only used for accounting purposes */ -struct user_struct *user; -struct mm_struct *mm_account; - -/* ctx exit and cancelation */ -struct llist_head fallback_llist; -struct delayed_work fallback_work; -struct work_struct exit_work; -struct list_head tctx_list; -struct completion ref_comp; - -/* io-wq management, e.g. thread count */ -u32 iowq_limits[2 ]; -bool iowq_limits_set; - -struct list_head defer_list; - }; + +struct io_restriction restrictions; + +/* slow path rsrc auxilary data, used by update/register */ +struct io_rsrc_node *rsrc_backup_node; +struct io_mapped_ubuf *dummy_ubuf; +struct io_rsrc_data *file_data; +struct io_rsrc_data *buf_data; + +struct delayed_work rsrc_put_work; +struct llist_head rsrc_put_llist; +struct list_head rsrc_ref_list; +spinlock_t rsrc_ref_lock; + +struct list_head io_buffers_pages; + +#if defined(CONFIG_UNIX ) +struct socket *ring_sock; +#endif +/* hashed buffered write serialization */ +struct io_wq_hash *hash_map; + +/* Only used for accounting purposes */ +struct user_struct *user; +struct mm_struct *mm_account; + +/* ctx exit and cancelation */ +struct llist_head fallback_llist; +struct delayed_work fallback_work; +struct work_struct exit_work; +struct list_head tctx_list; +struct completion ref_comp; + +/* io-wq management, e.g. thread count */ +u32 iowq_limits[2 ]; +bool iowq_limits_set; + +struct list_head defer_list; +unsigned sq_thread_idle; };enum {
Signature · 1 line
2.36.1
PATCH 5/25 io_uring: move cancel_seq out of io-wq
In reply to Pavel Begunkovio-wq doesn't use ->cancel_seq, it's only important to io_uring and should be stored there.
--- io_uring/cancel.c | 4 ++-- io_uring/io-wq.h | 1 - io_uring/io_uring.c | 2 +- io_uring/io_uring_types.h | 1 + io_uring/poll.c | 10 +++++----- io_uring/timeout.c | 4 ++-- 6 files changed, 11 insertions(+), 11 deletions(-)
io_uring/cancel.c +2 -2
@@ -44,9 +44,9 @@ static bool io_cancel_cb(struct io_wq_work *work, void *data)return false; }if (cd->flags & (IORING_ASYNC_CANCEL_ALL |IORING_ASYNC_CANCEL_ANY )) { -if (cd->seq == req->work.cancel_seq) +if (cd->seq == req->cancel_seq)return false; - req->work.cancel_seq = cd->seq; + req->cancel_seq = cd->seq; }return true; }
io_uring/io-wq.h +0 -1
@@ -155,7 +155,6 @@ struct io_wq_work_node *wq_stack_extract(struct io_wq_work_node *stack)struct io_wq_work {struct io_wq_work_node list;unsigned flags; -int cancel_seq; };static inline struct io_wq_work *wq_next_work (struct io_wq_work *work)
io_uring/io_uring.c +1 -1
@@ -849,7 +849,7 @@ static void io_prep_async_work(struct io_kiocb *req) req->work.list.next =NULL ; req->work.flags =0 ; - req->work.cancel_seq =atomic_read (&ctx->cancel_seq); + req->cancel_seq =atomic_read (&ctx->cancel_seq);if (req->flags &REQ_F_FORCE_ASYNC ) req->work.flags |=IO_WQ_WORK_CONCURRENT ;
io_uring/io_uring_types.h +1 -0
@@ -486,6 +486,7 @@ struct io_kiocb {/* custom credentials, valid IFF REQ_F_CREDS is set */ const struct cred *creds;struct io_wq_work work; +int cancel_seq; };struct io_cancel_data {
io_uring/poll.c +5 -5
@@ -405,7 +405,7 @@ static int __io_arm_poll_handler(struct io_kiocb *req,int v;INIT_HLIST_NODE (&req->hash_node); - req->work.cancel_seq =atomic_read (&ctx->cancel_seq); + req->cancel_seq =atomic_read (&ctx->cancel_seq);io_init_poll_iocb (poll, mask, io_poll_wake); poll->file = req->file;
@@ -565,9 +565,9 @@ static struct io_kiocb *io_poll_find(struct io_ring_ctx *ctx, bool poll_only,if (poll_only && req->opcode !=IORING_OP_POLL_ADD )continue ;if (cd->flags &IORING_ASYNC_CANCEL_ALL ) { -if (cd->seq == req->work.cancel_seq) +if (cd->seq == req->cancel_seq)continue ; - req->work.cancel_seq = cd->seq; + req->cancel_seq = cd->seq; }return req; }
@@ -589,9 +589,9 @@ static struct io_kiocb *io_poll_file_find(struct io_ring_ctx *ctx,if (!(cd->flags &IORING_ASYNC_CANCEL_ANY ) && req->file != cd->file)continue ; -if (cd->seq == req->work.cancel_seq) +if (cd->seq == req->cancel_seq)continue ; - req->work.cancel_seq = cd->seq; + req->cancel_seq = cd->seq;return req; } }
io_uring/timeout.c +2 -2
@@ -227,9 +227,9 @@ static struct io_kiocb *io_timeout_extract(struct io_ring_ctx *ctx, cd->data != tmp->cqe.user_data)continue ;if (cd->flags & (IORING_ASYNC_CANCEL_ALL |IORING_ASYNC_CANCEL_ANY )) { -if (cd->seq == tmp->work.cancel_seq) +if (cd->seq == tmp->cancel_seq)continue ; - tmp->work.cancel_seq = cd->seq; + tmp->cancel_seq = cd->seq; } req = tmp;break ;
Signature · 1 line
2.36.1
Jens Axboe
maintainerRe: [PATCH for-next 05/25] io_uring: move cancel_seq out of io-wq
Quoted 2 lines · Pavel Begunkov
io-wq doesn't use ->cancel_seq, it's only important to
io_uringand should be stored there.
It isn't there because it's io-wq only, but to save space. This adds 8 bytes to io_kiocb, as far as I can tell?
Signature · 1 line
Jens Axboe
Re: [PATCH for-next 05/25] io_uring: move cancel_seq out of io-wq
Quoted 6 lines · Jens Axboe
Quoted 2 lines · Pavel Begunkov
io-wq doesn't use ->cancel_seq, it's only important to
io_uringand should be stored there.It isn't there because it's io-wq only, but to save space. This adds 8 bytes to
io_kiocb, as far as I can tell?
Ah ok, makes sense. It's worth a comment though
Signature · 1 line
Pavel Begunkov
Jens Axboe
maintainerRe: [PATCH for-next 05/25] io_uring: move cancel_seq out of io-wq
Quoted 9 lines · Pavel Begunkov
Quoted 6 lines · Jens Axboe
Quoted 2 lines · Pavel Begunkov
io-wq doesn't use ->cancel_seq, it's only important to
io_uringand should be stored there.It isn't there because it's io-wq only, but to save space. This adds 8 bytes to
io_kiocb, as far as I can tell?Ah ok, makes sense. It's worth a comment though
Definitely, not sure why I didn't add one... When you respin the series, maybe change this patch to add that comment instead?
Signature · 1 line
Jens Axboe
PATCH 6/25 io_uring: move small helpers to headers
In reply to Pavel BegunkovThere is a bunch of inline helpers that will be useful not only to the core of io_uring, move them to headers.
--- io_uring/io_uring.c | 22 ---------------------- io_uring/io_uring.h | 22 ++++++++++++++++++++++ 2 files changed, 22 insertions(+), 22 deletions(-)
io_uring/io_uring.c +0 -22
@@ -616,14 +616,6 @@ struct sock *io_uring_get_socket(struct file *file) }EXPORT_SYMBOL (io_uring_get_socket); -static inline void io_tw_lock (struct io_ring_ctx *ctx,bool *locked) -{ -if (!*locked) { -mutex_lock (&ctx->uring_lock); - *locked = true; - } -} -static inline void io_submit_flush_completions (struct io_ring_ctx *ctx) {if (!wq_list_empty (&ctx->submit_state.compl_reqs))
@@ -879,15 +871,6 @@ static void io_prep_async_link(struct io_kiocb *req) } } -static inline void io_req_add_compl_list (struct io_kiocb *req) -{ -struct io_submit_state *state = &req->ctx->submit_state; - -if (!(req->flags &REQ_F_CQE_SKIP )) - state->flush_cqes = true; -wq_list_add_tail (&req->comp_list, &state->compl_reqs); -} -void io_queue_iowq (struct io_kiocb *req,bool *dont_use) {struct io_kiocb *link =io_prep_linked_timeout (req);
@@ -1293,11 +1276,6 @@ static void io_req_complete_post32(struct io_kiocb *req, u64 extra1, u64 extra2)io_cqring_ev_posted (ctx); } -static inline void io_req_complete_state (struct io_kiocb *req) -{ - req->flags |=REQ_F_COMPLETE_INLINE ; -} -inline void __io_req_complete (struct io_kiocb *req,unsigned issue_flags) {if (issue_flags &IO_URING_F_COMPLETE_DEFER )
io_uring/io_uring.h +22 -0
@@ -193,6 +193,28 @@ static inline bool io_run_task_work(void)return false; } +static inline void io_req_complete_state (struct io_kiocb *req) +{ + req->flags |=REQ_F_COMPLETE_INLINE ; +} + +static inline void io_tw_lock (struct io_ring_ctx *ctx,bool *locked) +{ +if (!*locked) { +mutex_lock (&ctx->uring_lock); + *locked = true; + } +} + +static inline void io_req_add_compl_list (struct io_kiocb *req) +{ +struct io_submit_state *state = &req->ctx->submit_state; + +if (!(req->flags &REQ_F_CQE_SKIP )) + state->flush_cqes = true; +wq_list_add_tail (&req->comp_list, &state->compl_reqs); +} +int io_run_task_work_sig (void );void io_req_complete_failed (struct io_kiocb *req,s32 res); void __io_req_complete32(struct io_kiocb *req, unsigned int issue_flags,
Signature · 1 line
2.36.1
PATCH 7/25 io_uring: inline ->registered_rings
In reply to Pavel BegunkovThere can be only 16 registered rings, no need to allocate an array for them separately but store it in tctx.
--- io_uring/io_uring.c | 1 - io_uring/tctx.c | 9 --------- io_uring/tctx.h | 3 ++- 3 files changed, 2 insertions(+), 11 deletions(-)
io_uring/io_uring.c +0 -1
@@ -2840,7 +2840,6 @@ void __io_uring_free(struct task_struct *tsk)WARN_ON_ONCE (tctx->io_wq);WARN_ON_ONCE (tctx->cached_refs); -kfree (tctx->registered_rings);percpu_counter_destroy (&tctx->inflight);kfree (tctx); tsk->io_uring =NULL ;
io_uring/tctx.c +0 -9
@@ -55,16 +55,8 @@ __cold int io_uring_alloc_task_context(struct task_struct *task,if (unlikely (!tctx))return -ENOMEM ; - tctx->registered_rings =kcalloc (IO_RINGFD_REG_MAX , -sizeof (struct file *),GFP_KERNEL ); -if (unlikely (!tctx->registered_rings)) { -kfree (tctx); -return -ENOMEM ; - } - ret =percpu_counter_init (&tctx->inflight,0 ,GFP_KERNEL );if (unlikely (ret)) { -kfree (tctx->registered_rings);kfree (tctx);return ret; }
@@ -73,7 +65,6 @@ __cold int io_uring_alloc_task_context(struct task_struct *task,if (IS_ERR (tctx->io_wq)) { ret =PTR_ERR (tctx->io_wq);percpu_counter_destroy (&tctx->inflight); -kfree (tctx->registered_rings);kfree (tctx);return ret; }
io_uring/tctx.h +2 -1
@@ -20,8 +20,9 @@ struct io_uring_task {struct io_wq_work_list task_list;struct io_wq_work_list prio_task_list;struct callback_head task_work; -struct file **registered_rings;bool task_running; + +struct file *registered_rings[IO_RINGFD_REG_MAX ]; };struct io_tctx_node {
Signature · 1 line
2.36.1
PATCH 8/25 io_uring: don't set REQ_F_COMPLETE_INLINE in tw
In reply to Pavel Begunkovio_req_task_complete() enqueues requests for state completion itself, no need for REQ_F_COMPLETE_INLINE, which is only serve the purpose of not bloating the kernel.
--- io_uring/io_uring.c | 1 - 1 file changed, 1 deletion(-)
io_uring/io_uring.c +0 -1
@@ -1864,7 +1864,6 @@ inline void io_req_task_complete(struct io_kiocb *req, bool *locked) {if (*locked) { req->cqe.flags |=io_put_kbuf (req,0 ); -io_req_complete_state (req);io_req_add_compl_list (req); }else { req->cqe.flags |=io_put_kbuf (req,IO_URING_F_UNLOCKED );
Signature · 1 line
2.36.1
PATCH 9/25 io_uring: never defer-complete multi-apoll
In reply to Pavel BegunkovLuckily, nnobody completes multi-apoll requests outside the polling functions, but don't set IO_URING_F_COMPLETE_DEFER in any case as there is nobody who is catching REQ_F_COMPLETE_INLINE, and so will leak requests if used.
--- io_uring/io_uring.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
io_uring/io_uring.c +1 -1
@@ -2149,7 +2149,7 @@ int io_poll_issue(struct io_kiocb *req, bool *locked)io_tw_lock (req->ctx, locked);if (unlikely (req->task->flags &PF_EXITING ))return -EFAULT ; -return io_issue_sqe (req,IO_URING_F_NONBLOCK |IO_URING_F_COMPLETE_DEFER ); +return io_issue_sqe (req,IO_URING_F_NONBLOCK ); }struct io_wq_work *io_wq_free_work (struct io_wq_work *work)
Signature · 1 line
2.36.1
PATCH 10/25 io_uring: kill REQ_F_COMPLETE_INLINE
In reply to Pavel BegunkovREQ_F_COMPLETE_INLINE is only needed to delay queueing into the completion list to io_queue_sqe() as __io_req_complete() is inlined and we don't want to bloat the kernel.
As now we complete in a more centralised fashion in io_issue_sqe() we can get rid of the flag and queue to the list directly.
--- io_uring/io_uring.c | 20 ++++++++------------ io_uring/io_uring.h | 5 ----- io_uring/io_uring_types.h | 3 --- 3 files changed, 8 insertions(+), 20 deletions(-)
io_uring/io_uring.c +8 -12
@@ -1278,17 +1278,14 @@ static void io_req_complete_post32(struct io_kiocb *req, u64 extra1, u64 extra2)inline void __io_req_complete (struct io_kiocb *req,unsigned issue_flags) { -if (issue_flags &IO_URING_F_COMPLETE_DEFER ) -io_req_complete_state (req); -else -io_req_complete_post (req); +io_req_complete_post (req); }void __io_req_complete32 (struct io_kiocb *req,unsigned int issue_flags,u64 extra1,u64 extra2) {if (issue_flags &IO_URING_F_COMPLETE_DEFER ) { -io_req_complete_state (req); +io_req_add_compl_list (req); req->extra1 = extra1; req->extra2 = extra2; }else {
@@ -2132,9 +2129,12 @@ static int io_issue_sqe(struct io_kiocb *req, unsigned int issue_flags)if (creds)revert_creds (creds); -if (ret ==IOU_OK ) -__io_req_complete (req, issue_flags); -else if (ret !=IOU_ISSUE_SKIP_COMPLETE ) +if (ret ==IOU_OK ) { +if (issue_flags &IO_URING_F_COMPLETE_DEFER ) +io_req_add_compl_list (req); +else +io_req_complete_post (req); + }else if (ret !=IOU_ISSUE_SKIP_COMPLETE )return ret;/* If the op doesn't have a file, we're not polling for it */
@@ -2299,10 +2299,6 @@ static inline void io_queue_sqe(struct io_kiocb *req) ret =io_issue_sqe (req,IO_URING_F_NONBLOCK |IO_URING_F_COMPLETE_DEFER ); -if (req->flags &REQ_F_COMPLETE_INLINE ) { -io_req_add_compl_list (req); -return ; - }/* * We async punt it if the file wasn't marked NOWAIT, or if the file * doesn't support non-blocking read/write attempts
io_uring/io_uring.h +0 -5
@@ -193,11 +193,6 @@ static inline bool io_run_task_work(void)return false; } -static inline void io_req_complete_state (struct io_kiocb *req) -{ - req->flags |=REQ_F_COMPLETE_INLINE ; -} -static inline void io_tw_lock (struct io_ring_ctx *ctx,bool *locked) {if (!*locked) {
io_uring/io_uring_types.h +0 -3
@@ -299,7 +299,6 @@ enum {REQ_F_POLLED_BIT ,REQ_F_BUFFER_SELECTED_BIT ,REQ_F_BUFFER_RING_BIT , -REQ_F_COMPLETE_INLINE_BIT ,REQ_F_REISSUE_BIT ,REQ_F_CREDS_BIT , REQ_F_REFCOUNT_BIT,
@@ -353,8 +352,6 @@ enum {REQ_F_BUFFER_SELECTED =BIT (REQ_F_BUFFER_SELECTED_BIT ),/* buffer selected from ring, needs commit */ REQ_F_BUFFER_RING =BIT (REQ_F_BUFFER_RING_BIT ), -/* completion is deferred through io_comp_state */ -REQ_F_COMPLETE_INLINE =BIT (REQ_F_COMPLETE_INLINE_BIT ),/* caller should reissue async */ REQ_F_REISSUE = BIT(REQ_F_REISSUE_BIT),/* supports async reads/writes */
Signature · 1 line
2.36.1
PATCH 11/25 io_uring: refactor io_req_task_complete()
In reply to Pavel BegunkovClean up io_req_task_complete() and deduplicate io_put_kbuf() calls.
--- io_uring/io_uring.c | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-)
io_uring/io_uring.c +10 -6
@@ -1857,15 +1857,19 @@ static int io_iopoll_check(struct io_ring_ctx *ctx, long min)return ret; } -inline void io_req_task_complete (struct io_kiocb *req,bool *locked) + +void io_req_task_complete (struct io_kiocb *req,bool *locked) { -if (*locked) { - req->cqe.flags |=io_put_kbuf (req,0 ); +if (req->flags & (REQ_F_BUFFER_SELECTED |REQ_F_BUFFER_RING )) { +unsigned issue_flags = *locked ?IO_URING_F_UNLOCKED :0 ; + + req->cqe.flags |=io_put_kbuf (req, issue_flags); + } + +if (*locked)io_req_add_compl_list (req); - }else { - req->cqe.flags |=io_put_kbuf (req,IO_URING_F_UNLOCKED ); +else io_req_complete_post (req); - } }/*
Signature · 1 line
2.36.1
PATCH 12/25 io_uring: don't inline io_put_kbuf
In reply to Pavel Begunkovio_put_kbuf() is huge, don't bloat the kernel with inlining.
--- io_uring/kbuf.c | 33 +++++++++++++++++++++++++++++++++ io_uring/kbuf.h | 38 ++++++-------------------------------- 2 files changed, 39 insertions(+), 32 deletions(-)
io_uring/kbuf.c +33 -0
@@ -81,6 +81,39 @@ static int io_buffer_add_list(struct io_ring_ctx *ctx,return xa_err (xa_store (&ctx->io_bl_xa, bgid, bl,GFP_KERNEL )); } +unsigned int __io_put_kbuf (struct io_kiocb *req,unsigned issue_flags) +{ +unsigned int cflags; + +/* +* We can add this buffer back to two lists: +* +* 1) The io_buffers_cache list. This one is protected by the +* ctx->uring_lock. If we already hold this lock, add back to this +* list as we can grab it from issue as well. +* 2) The io_buffers_comp list. This one is protected by the +* ctx->completion_lock. +* +* We migrate buffers from the comp_list to the issue cache list +* when we need one. +*/ +if (req->flags &REQ_F_BUFFER_RING ) { +/* no buffers to recycle for this case */ + cflags =__io_put_kbuf_list (req,NULL ); + }else if (issue_flags &IO_URING_F_UNLOCKED ) { +struct io_ring_ctx *ctx = req->ctx; + +spin_lock (&ctx->completion_lock); + cflags =__io_put_kbuf_list (req, &ctx->io_buffers_comp); +spin_unlock (&ctx->completion_lock); + }else { +lockdep_assert_held (&req->ctx->uring_lock); + + cflags =__io_put_kbuf_list (req, &req->ctx->io_buffers_cache); + } +return cflags; +} +static void __user *io_provided_buffer_select (struct io_kiocb *req,size_t *len,struct io_buffer_list *bl) {
io_uring/kbuf.h +6 -32
@@ -47,6 +47,8 @@ int io_provide_buffers(struct io_kiocb *req, unsigned int issue_flags);int io_register_pbuf_ring (struct io_ring_ctx *ctx,void __user *arg);int io_unregister_pbuf_ring (struct io_ring_ctx *ctx,void __user *arg); +unsigned int __io_put_kbuf (struct io_kiocb *req,unsigned issue_flags); +static inline bool io_do_buffer_select (struct io_kiocb *req) {if (!(req->flags &REQ_F_BUFFER_SELECT ))
@@ -70,7 +72,8 @@ static inline void io_kbuf_recycle(struct io_kiocb *req, unsigned issue_flags)__io_kbuf_recycle (req, issue_flags); } -static unsigned int __io_put_kbuf (struct io_kiocb *req,struct list_head *list) +static inline unsigned int __io_put_kbuf_list (struct io_kiocb *req, +struct list_head *list) {if (req->flags &REQ_F_BUFFER_RING ) {if (req->buf_list)
@@ -90,44 +93,15 @@ static inline unsigned int io_put_kbuf_comp(struct io_kiocb *req)if (!(req->flags & (REQ_F_BUFFER_SELECTED |REQ_F_BUFFER_RING )))return 0 ; -return __io_put_kbuf (req, &req->ctx->io_buffers_comp); + return __io_put_kbuf_list(req, &req->ctx->io_buffers_comp); }static inline unsigned int io_put_kbuf (struct io_kiocb *req,unsigned issue_flags) { -unsigned int cflags;if (!(req->flags & (REQ_F_BUFFER_SELECTED |REQ_F_BUFFER_RING )))return 0 ; - -/* -* We can add this buffer back to two lists: -* -* 1) The io_buffers_cache list. This one is protected by the -* ctx->uring_lock. If we already hold this lock, add back to this -* list as we can grab it from issue as well. -* 2) The io_buffers_comp list. This one is protected by the -* ctx->completion_lock. -* -* We migrate buffers from the comp_list to the issue cache list -* when we need one. -*/ -if (req->flags &REQ_F_BUFFER_RING ) { -/* no buffers to recycle for this case */ - cflags =__io_put_kbuf (req,NULL ); - }else if (issue_flags &IO_URING_F_UNLOCKED ) { -struct io_ring_ctx *ctx = req->ctx; - -spin_lock (&ctx->completion_lock); - cflags =__io_put_kbuf (req, &ctx->io_buffers_comp); -spin_unlock (&ctx->completion_lock); - }else { -lockdep_assert_held (&req->ctx->uring_lock); - - cflags =__io_put_kbuf (req, &req->ctx->io_buffers_cache); - } - -return cflags; +return __io_put_kbuf (req, issue_flags); }#endif
Signature · 1 line
2.36.1
PATCH 13/25 io_uring: remove check_cq checking from hot paths
In reply to Pavel BegunkovAll ctx->check_cq events are slow path, don't test every single flag one by one in the hot path, but add a common guarding if.
--- io_uring/io_uring.c | 34 +++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 15 deletions(-)
io_uring/io_uring.c +19 -15
@@ -1807,24 +1807,25 @@ static int io_iopoll_check(struct io_ring_ctx *ctx, long min)int ret =0 ;unsigned long check_cq; + check_cq =READ_ONCE (ctx->check_cq); +if (unlikely (check_cq)) { +if (check_cq &BIT (IO_CHECK_CQ_OVERFLOW_BIT )) +__io_cqring_overflow_flush (ctx, false); +/* +* Similarly do not spin if we have not informed the user of any +* dropped CQE. +*/ +if (check_cq &BIT (IO_CHECK_CQ_DROPPED_BIT )) +return -EBADR ; + }/* * Don't enter poll loop if we already have events pending. * If we do, we can potentially be spinning for commands that * already triggered a CQE (eg in error). */ - check_cq =READ_ONCE (ctx->check_cq); -if (check_cq &BIT (IO_CHECK_CQ_OVERFLOW_BIT )) -__io_cqring_overflow_flush (ctx, false);if (io_cqring_events (ctx))return 0 ; -/* -* Similarly do not spin if we have not informed the user of any -* dropped CQE. -*/ -if (unlikely (check_cq &BIT (IO_CHECK_CQ_DROPPED_BIT ))) -return -EBADR ; - do { /* * If a submit got punted to a workqueue, we can have the
@@ -2752,12 +2753,15 @@ static inline int io_cqring_wait_schedule(struct io_ring_ctx *ctx, ret =io_run_task_work_sig ();if (ret ||io_should_wake (iowq))return ret; + check_cq =READ_ONCE (ctx->check_cq); -/* let the caller flush overflows, retry */ -if (check_cq &BIT (IO_CHECK_CQ_OVERFLOW_BIT )) -return 1 ; -if (unlikely (check_cq &BIT (IO_CHECK_CQ_DROPPED_BIT ))) -return -EBADR ; +if (unlikely (check_cq)) { +/* let the caller flush overflows, retry */ +if (check_cq &BIT (IO_CHECK_CQ_OVERFLOW_BIT )) +return 1 ; +if (check_cq &BIT (IO_CHECK_CQ_DROPPED_BIT )) +return -EBADR ; + }if (!schedule_hrtimeout (&timeout,HRTIMER_MODE_ABS ))return -ETIME ;return 1 ;
Signature · 1 line
2.36.1
PATCH 14/25 io_uring: poll: remove unnecessary req->ref set
In reply to Pavel BegunkovFrom: Hao Xu <[email protected]>
We now don't need to set req->refcount for poll requests since the reworked poll code ensures no request release race.
--- io_uring/poll.c | 1 - 1 file changed, 1 deletion(-)
io_uring/poll.c +0 -1
@@ -683,7 +683,6 @@ int io_poll_add_prep(struct io_kiocb *req, const struct io_uring_sqe *sqe)if ((flags &IORING_POLL_ADD_MULTI ) && (req->flags &REQ_F_CQE_SKIP ))return -EINVAL ; -io_req_set_refcount (req); req->apoll_events = poll->events =io_poll_parse_events (sqe, flags);return 0 ; }
Signature · 1 line
2.36.1
PATCH 15/25 io_uring: switch cancel_hash to use per entry spinlock
In reply to Pavel BegunkovFrom: Hao Xu <[email protected]>
Add a new io_hash_bucket structure so that each bucket in cancel_hash has separate spinlock. Use per entry lock for cancel_hash, this removes some completion lock invocation and remove contension between different cancel_hash entries.
--- io_uring/cancel.c | 14 ++++++- io_uring/cancel.h | 6 +++ io_uring/fdinfo.c | 9 +++-- io_uring/io_uring.c | 8 ++-- io_uring/io_uring_types.h | 2 +- io_uring/poll.c | 80 ++++++++++++++++++++++++--------------- 6 files changed, 79 insertions(+), 40 deletions(-)
io_uring/cancel.c +12 -2
@@ -93,14 +93,14 @@ int io_try_cancel(struct io_kiocb *req, struct io_cancel_data *cd)if (!ret)return 0 ; -spin_lock (&ctx->completion_lock); ret =io_poll_cancel (ctx, cd);if (ret != -ENOENT ) goto out; +spin_lock (&ctx->completion_lock);if (!(cd->flags &IORING_ASYNC_CANCEL_FD )) ret =io_timeout_cancel (ctx, cd); -out:spin_unlock (&ctx->completion_lock); +out:return ret; }
@@ -192,3 +192,13 @@ int io_async_cancel(struct io_kiocb *req, unsigned int issue_flags)io_req_set_res (req, ret,0 );return IOU_OK ; } + +void init_hash_table (struct io_hash_bucket *hash_table,unsigned size) +{ +unsigned int i; + +for (i =0 ; i < size; i++) { +spin_lock_init (&hash_table[i].lock); +INIT_HLIST_HEAD (&hash_table[i].list); + } +}
io_uring/cancel.h +6 -0
@@ -4,3 +4,9 @@ int io_async_cancel_prep(struct io_kiocb *req, const struct io_uring_sqe *sqe);int io_async_cancel (struct io_kiocb *req,unsigned int issue_flags);int io_try_cancel (struct io_kiocb *req,struct io_cancel_data *cd); +void init_hash_table (struct io_hash_bucket *hash_table,unsigned size); + +struct io_hash_bucket { +spinlock_t lock; +struct hlist_head list; +}____cacheline_aligned_in_smp ;
io_uring/fdinfo.c +6 -3
@@ -13,6 +13,7 @@#include "io_uring.h" #include "sqpoll.h" #include "fdinfo.h" +#include "cancel.h" #ifdef CONFIG_PROC_FS static __cold int io_uring_show_cred(struct seq_file *m, unsigned int id,
@@ -157,17 +158,19 @@ static __cold void __io_uring_show_fdinfo(struct io_ring_ctx *ctx,mutex_unlock (&ctx->uring_lock);seq_puts (m,"PollList:\n" ); -spin_lock (&ctx->completion_lock);for (i =0 ; i < (1U << ctx->cancel_hash_bits); i++) { -struct hlist_head *list = &ctx->cancel_hash[i]; +struct io_hash_bucket *hb = &ctx->cancel_hash[i];struct io_kiocb *req; -hlist_for_each_entry (req, list, hash_node) +spin_lock (&hb->lock); +hlist_for_each_entry (req, &hb->list, hash_node)seq_printf (m," op=%d, task_works=%d\n" , req->opcode,task_work_pending (req->task)); +spin_unlock (&hb->lock); }seq_puts (m,"CqOverflowList:\n" ); +spin_lock (&ctx->completion_lock);list_for_each_entry (ocqe, &ctx->cq_overflow_list, list) {struct io_uring_cqe *cqe = &ocqe->cqe;
io_uring/io_uring.c +5 -3
@@ -717,11 +717,13 @@ static __cold struct io_ring_ctx *io_ring_ctx_alloc(struct io_uring_params *p)if (hash_bits <=0 ) hash_bits =1 ; ctx->cancel_hash_bits = hash_bits; - ctx->cancel_hash =kmalloc ((1U << hash_bits) *sizeof (struct hlist_head ), -GFP_KERNEL ); + ctx->cancel_hash = +kmalloc ((1U << hash_bits) *sizeof (struct io_hash_bucket ), +GFP_KERNEL );if (!ctx->cancel_hash) goto err; -__hash_init (ctx->cancel_hash,1U << hash_bits); + +init_hash_table (ctx->cancel_hash,1U << hash_bits); ctx->dummy_ubuf =kzalloc (sizeof (*ctx->dummy_ubuf),GFP_KERNEL );if (!ctx->dummy_ubuf)
io_uring/io_uring_types.h +1 -1
@@ -222,7 +222,7 @@ struct io_ring_ctx {* manipulate the list, hence no extra locking is needed there. */ struct io_wq_work_list iopoll_list; -struct hlist_head *cancel_hash; +struct io_hash_bucket *cancel_hash;unsigned cancel_hash_bits;bool poll_multi_queue;
io_uring/poll.c +49 -31
198 lines not on this page · raw message
Signature · 1 line
2.36.1
PATCH 16/25 io_uring: pass poll_find lock back
In reply to Pavel BegunkovInstead of using implicit knowledge of what is locked or not after io_poll_find() and co returns, pass back a pointer to the locked bucket if any. If set the user must to unlock the spinlock.
--- io_uring/poll.c | 46 ++++++++++++++++++++++++++-------------------- 1 file changed, 26 insertions(+), 20 deletions(-)
io_uring/poll.c +26 -20
@@ -559,12 +559,15 @@ __cold bool io_poll_remove_all(struct io_ring_ctx *ctx, struct task_struct *tsk, }static struct io_kiocb *io_poll_find (struct io_ring_ctx *ctx,bool poll_only, -struct io_cancel_data *cd) +struct io_cancel_data *cd, +struct io_hash_bucket **out_bucket) {struct io_kiocb *req;u32 index =hash_long (cd->data, ctx->cancel_hash_bits);struct io_hash_bucket *hb = &ctx->cancel_hash[index]; + *out_bucket =NULL ; +spin_lock (&hb->lock);hlist_for_each_entry (req, &hb->list, hash_node) {if (cd->data != req->cqe.user_data)
@@ -576,6 +579,7 @@ static struct io_kiocb *io_poll_find(struct io_ring_ctx *ctx, bool poll_only,continue ; req->cancel_seq = cd->seq; } + *out_bucket = hb;return req; }spin_unlock (&hb->lock);
@@ -583,11 +587,14 @@ static struct io_kiocb *io_poll_find(struct io_ring_ctx *ctx, bool poll_only, }static struct io_kiocb *io_poll_file_find (struct io_ring_ctx *ctx, -struct io_cancel_data *cd) +struct io_cancel_data *cd, +struct io_hash_bucket **out_bucket) {struct io_kiocb *req;int i; + *out_bucket =NULL ; +for (i =0 ; i < (1U << ctx->cancel_hash_bits); i++) {struct io_hash_bucket *hb = &ctx->cancel_hash[i];
@@ -599,6 +606,7 @@ static struct io_kiocb *io_poll_file_find(struct io_ring_ctx *ctx,if (cd->seq == req->cancel_seq)continue ; req->cancel_seq = cd->seq; + *out_bucket = hb;return req; }spin_unlock (&hb->lock);
@@ -617,23 +625,19 @@ static bool io_poll_disarm(struct io_kiocb *req)int io_poll_cancel (struct io_ring_ctx *ctx,struct io_cancel_data *cd) { +struct io_hash_bucket *bucket;struct io_kiocb *req; -u32 index; -spinlock_t *lock;if (cd->flags & (IORING_ASYNC_CANCEL_FD |IORING_ASYNC_CANCEL_ANY )) - req =io_poll_file_find (ctx, cd); + req =io_poll_file_find (ctx, cd, &bucket);else - req =io_poll_find (ctx, false, cd); -if (!req) { -return -ENOENT ; - }else { - index =hash_long (req->cqe.user_data, ctx->cancel_hash_bits); - lock = &ctx->cancel_hash[index].lock; - } -io_poll_cancel_req (req); -spin_unlock (lock); -return 0 ; + req =io_poll_find (ctx, false, cd, &bucket); + +if (req) +io_poll_cancel_req (req); +if (bucket) +spin_unlock (&bucket->lock); +return req ?0 : -ENOENT ; } static __poll_t io_poll_parse_events(const struct io_uring_sqe *sqe,
@@ -726,19 +730,21 @@ int io_poll_remove(struct io_kiocb *req, unsigned int issue_flags)struct io_poll_update *poll_update =io_kiocb_to_cmd (req);struct io_cancel_data cd = { .data = poll_update->old_user_data, };struct io_ring_ctx *ctx = req->ctx; -u32 index =hash_long (cd.data, ctx->cancel_hash_bits); -spinlock_t *lock = &ctx->cancel_hash[index].lock; +struct io_hash_bucket *bucket;struct io_kiocb *preq;int ret2, ret =0 ;bool locked; - preq =io_poll_find (ctx, true, &cd); + preq =io_poll_find (ctx, true, &cd, &bucket); +if (preq) + ret2 =io_poll_disarm (preq); +if (bucket) +spin_unlock (&bucket->lock); +if (!preq) { ret = -ENOENT ; goto out; } - ret2 =io_poll_disarm (preq); -spin_unlock (lock);if (!ret2) { ret = -EALREADY ; goto out;
Signature · 1 line
2.36.1
PATCH 17/25 io_uring: clean up io_try_cancel
In reply to Pavel BegunkovGet rid of an unnecessary extra goto in io_try_cancel() and simplify the function.
--- io_uring/cancel.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-)
io_uring/cancel.c +2 -2
@@ -95,12 +95,12 @@ int io_try_cancel(struct io_kiocb *req, struct io_cancel_data *cd) ret =io_poll_cancel (ctx, cd);if (ret != -ENOENT ) - goto out; +return ret; +spin_lock (&ctx->completion_lock);if (!(cd->flags &IORING_ASYNC_CANCEL_FD )) ret =io_timeout_cancel (ctx, cd);spin_unlock (&ctx->completion_lock); -out:return ret; }
Signature · 1 line
2.36.1
PATCH 18/25 io_uring: limit number hash buckets
In reply to Pavel BegunkovDon't allocate to many hash/cancellation buckets, there might be too many, clamp it to 8 bits, or 256 * 64B = 16KB. We don't usually have too many requests, and 256 buckets should be enough, especially since we do hash search only in the cancellation path.
--- io_uring/io_uring.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-)
io_uring/io_uring.c +5 -5
@@ -710,12 +710,12 @@ static __cold struct io_ring_ctx *io_ring_ctx_alloc(struct io_uring_params *p)/* * Use 5 bits less than the max cq entries, that should give us around -* 32 entries per hash list if totally full and uniformly spread. +* 32 entries per hash list if totally full and uniformly spread, but +* don't keep too many buckets to not overconsume memory. */ - hash_bits =ilog2 (p->cq_entries); - hash_bits -=5 ; -if (hash_bits <=0 ) - hash_bits =1 ; + hash_bits =ilog2 (p->cq_entries) -5 ; + hash_bits =clamp (hash_bits,1 ,8 ); + ctx->cancel_hash_bits = hash_bits; ctx->cancel_hash = kmalloc((1U << hash_bits) * sizeof(struct io_hash_bucket),
Signature · 1 line
2.36.1
PATCH 19/25 io_uring: clean up io_ring_ctx_alloc
In reply to Pavel BegunkovAdd a variable for the number of hash buckets in io_ring_ctx_alloc(), makes it more readable.
--- io_uring/io_uring.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-)
io_uring/io_uring.c +6 -4
@@ -700,6 +700,8 @@ static __cold void io_fallback_req_func(struct work_struct *work)static __cold struct io_ring_ctx *io_ring_ctx_alloc (struct io_uring_params *p) {struct io_ring_ctx *ctx; +unsigned hash_buckets; +size_t hash_size;int hash_bits; ctx =kzalloc (sizeof (*ctx),GFP_KERNEL );
@@ -715,15 +717,15 @@ static __cold struct io_ring_ctx *io_ring_ctx_alloc(struct io_uring_params *p)*/ hash_bits =ilog2 (p->cq_entries) -5 ; hash_bits =clamp (hash_bits,1 ,8 ); + hash_buckets =1U << hash_bits; + hash_size = hash_buckets *sizeof (struct io_hash_bucket ); ctx->cancel_hash_bits = hash_bits; - ctx->cancel_hash = -kmalloc ((1U << hash_bits) *sizeof (struct io_hash_bucket ), -GFP_KERNEL ); + ctx->cancel_hash =kmalloc (hash_size,GFP_KERNEL );if (!ctx->cancel_hash) goto err; -init_hash_table (ctx->cancel_hash,1U << hash_bits); +init_hash_table (ctx->cancel_hash, hash_buckets); ctx->dummy_ubuf =kzalloc (sizeof (*ctx->dummy_ubuf),GFP_KERNEL );if (!ctx->dummy_ubuf)
Signature · 1 line
2.36.1
PATCH 20/25 io_uring: use state completion infra for poll reqs
In reply to Pavel BegunkovUse io_req_task_complete() for poll request completions, so it can utilise state completions and save lots of unnecessary locking.
--- io_uring/poll.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-)
io_uring/poll.c +2 -6
@@ -234,12 +234,8 @@ static void io_poll_task_func(struct io_kiocb *req, bool *locked)io_poll_remove_entries (req);io_poll_req_delete (req, ctx); -spin_lock (&ctx->completion_lock); - req->cqe.flags =0 ; -__io_req_complete_post (req); -io_commit_cqring (ctx); -spin_unlock (&ctx->completion_lock); -io_cqring_ev_posted (ctx); +io_req_set_res (req, req->cqe.res,0 ); +io_req_task_complete (req, locked); }static void io_apoll_task_func (struct io_kiocb *req,bool *locked)
Signature · 1 line
2.36.1
PATCH 21/25 io_uring: add IORING_SETUP_SINGLE_ISSUER
In reply to Pavel BegunkovAdd a new IORING_SETUP_SINGLE_ISSUER flag and the userspace visible part of it, i.e. put limitations of submitters. Also, don't allow it together with IOPOLL as we're not going to put it to good use.
--- include/uapi/linux/io_uring.h | 5 ++++- io_uring/io_uring.c | 9 ++++++--- io_uring/io_uring_types.h | 1 + io_uring/tctx.c | 25 +++++++++++++++++++++++-- io_uring/tctx.h | 4 ++-- 5 files changed, 36 insertions(+), 8 deletions(-)
include/uapi/linux/io_uring.h +4 -1
@@ -138,9 +138,12 @@ enum {* IORING_SQ_TASKRUN in the sq ring flags. Not valid with COOP_TASKRUN. */ #define IORING_SETUP_TASKRUN_FLAG (1U << 9) -#define IORING_SETUP_SQE128 (1U << 10)/* SQEs are 128 byte */ #define IORING_SETUP_CQE32 (1U << 11)/* CQEs are 32 byte */ +/* +* Only one task is allowed to submit requests +*/ +#define IORING_SETUP_SINGLE_ISSUER (1U << 12)enum io_uring_op {IORING_OP_NOP ,
io_uring/io_uring.c +6 -3
@@ -3020,6 +3020,8 @@ static __cold void io_ring_ctx_free(struct io_ring_ctx *ctx)io_destroy_buffers (ctx);if (ctx->sq_creds)put_cred (ctx->sq_creds); +if (ctx->submitter_task) +put_task_struct (ctx->submitter_task);/* there are no registered resources left, nobody uses it */ if (ctx->rsrc_node)
@@ -3615,7 +3617,7 @@ SYSCALL_DEFINE6(io_uring_enter, unsigned int, fd, u32, to_submit, } ret = to_submit; }else if (to_submit) { - ret =io_uring_add_tctx_node (ctx); + ret =__io_uring_add_tctx_node (ctx, false);if (unlikely (ret)) goto out;
@@ -3752,7 +3754,7 @@ static int io_uring_install_fd(struct io_ring_ctx *ctx, struct file *file)if (fd <0 )return fd; - ret =io_uring_add_tctx_node (ctx); + ret =__io_uring_add_tctx_node (ctx, false);if (ret) {put_unused_fd (fd);return ret;
@@ -3972,7 +3974,8 @@ static long io_uring_setup(u32 entries, struct io_uring_params __user *params)IORING_SETUP_CLAMP |IORING_SETUP_ATTACH_WQ |IORING_SETUP_R_DISABLED |IORING_SETUP_SUBMIT_ALL |IORING_SETUP_COOP_TASKRUN |IORING_SETUP_TASKRUN_FLAG | - IORING_SETUP_SQE128 | IORING_SETUP_CQE32)) +IORING_SETUP_SQE128 |IORING_SETUP_CQE32 | + IORING_SETUP_SINGLE_ISSUER)) return -EINVAL;return io_uring_create (entries, &p, params);
io_uring/io_uring_types.h +1 -0
@@ -241,6 +241,7 @@ struct io_ring_ctx {/* Keep this last, we don't need it for the fast path */ struct io_restriction restrictions; +struct task_struct *submitter_task;/* slow path rsrc auxilary data, used by update/register */ struct io_rsrc_node *rsrc_backup_node;
io_uring/tctx.c +23 -2
@@ -81,12 +81,32 @@ __cold int io_uring_alloc_task_context(struct task_struct *task,return 0 ; } -int __io_uring_add_tctx_node (struct io_ring_ctx *ctx) +static int io_register_submitter (struct io_ring_ctx *ctx) +{ +int ret =0 ; + +mutex_lock (&ctx->uring_lock); +if (!ctx->submitter_task) + ctx->submitter_task =get_task_struct (current); +else if (ctx->submitter_task != current) + ret = -EEXIST ; +mutex_unlock (&ctx->uring_lock); + +return ret; +} + +int __io_uring_add_tctx_node (struct io_ring_ctx *ctx,bool submitter) {struct io_uring_task *tctx = current->io_uring;struct io_tctx_node *node;int ret; +if ((ctx->flags &IORING_SETUP_SINGLE_ISSUER ) && submitter) { + ret =io_register_submitter (ctx); +if (ret) +return ret; + } +if (unlikely (!tctx)) { ret =io_uring_alloc_task_context (current, ctx);if (unlikely (ret))
@@ -120,7 +140,8 @@ int __io_uring_add_tctx_node(struct io_ring_ctx *ctx)list_add (&node->ctx_node, &ctx->tctx_list);mutex_unlock (&ctx->uring_lock); } - tctx->last = ctx; +if (submitter) + tctx->last = ctx;return 0 ; }
io_uring/tctx.h +2 -2
@@ -34,7 +34,7 @@ struct io_tctx_node {int io_uring_alloc_task_context (struct task_struct *task,struct io_ring_ctx *ctx);void io_uring_del_tctx_node (unsigned long index); -int __io_uring_add_tctx_node (struct io_ring_ctx *ctx); +int __io_uring_add_tctx_node (struct io_ring_ctx *ctx,bool submitter);void io_uring_clean_tctx (struct io_uring_task *tctx);void io_uring_unreg_ringfd (void );
@@ -52,5 +52,5 @@ static inline int io_uring_add_tctx_node(struct io_ring_ctx *ctx)if (likely (tctx && tctx->last == ctx))return 0 ; -return __io_uring_add_tctx_node (ctx); +return __io_uring_add_tctx_node (ctx, true); }
Signature · 1 line
2.36.1
Re: [PATCH for-next 21/25] io_uring: add IORING_SETUP_SINGLE_ISSUER
Quoted 3 lines · Pavel Begunkov
Add a new
IORING_SETUP_SINGLE_ISSUERflag and the userspace visible part of it, i.e. put limitations of submitters. Also, don't allow it together with IOPOLL as we're not going to put it to good use.
Something goes wrong after rebase, need to change this one and will send out my tests for it later.
Quoted 148 lines
Signed-off-by Pavel Begunkov <[email protected]>--- include/uapi/linux/io_uring.h | 5 ++++- io_uring/io_uring.c | 9 ++++++--- io_uring/io_uring_types.h | 1 + io_uring/tctx.c | 25 +++++++++++++++++++++++-- io_uring/tctx.h | 4 ++-- 5 files changed, 36 insertions(+), 8 deletions(-)include/uapi/linux/io_uring.h +4
-1@@ -138,9 +138,12 @@ enum {* IORING_SQ_TASKRUN in the sq ring flags. Not valid with COOP_TASKRUN. */ #define IORING_SETUP_TASKRUN_FLAG (1U << 9) -#define IORING_SETUP_SQE128 (1U << 10)/* SQEs are 128 byte */ #define IORING_SETUP_CQE32 (1U << 11)/* CQEs are 32 byte */ +/* +* Only one task is allowed to submit requests +*/ +#define IORING_SETUP_SINGLE_ISSUER (1U << 12)enum io_uring_op {IORING_OP_NOP ,io_uring/io_uring.c +6
-3@@ -3020,6 +3020,8 @@ static __cold void io_ring_ctx_free(struct io_ring_ctx *ctx)io_destroy_buffers (ctx);if (ctx->sq_creds)put_cred (ctx->sq_creds); +if (ctx->submitter_task) +put_task_struct (ctx->submitter_task);/* there are no registered resources left, nobody uses it */ if (ctx->rsrc_node)@@ -3615,7 +3617,7 @@ SYSCALL_DEFINE6(io_uring_enter, unsigned int, fd, u32, to_submit, } ret = to_submit; }else if (to_submit) { - ret =io_uring_add_tctx_node (ctx); + ret =__io_uring_add_tctx_node (ctx, false);if (unlikely (ret)) goto out;@@ -3752,7 +3754,7 @@ static int io_uring_install_fd(struct io_ring_ctx *ctx, struct file *file)if (fd <0 )return fd; - ret =io_uring_add_tctx_node (ctx); + ret =__io_uring_add_tctx_node (ctx, false);if (ret) {put_unused_fd (fd);return ret;@@ -3972,7 +3974,8 @@ static long io_uring_setup(u32 entries, struct io_uring_params __user *params)IORING_SETUP_CLAMP |IORING_SETUP_ATTACH_WQ |IORING_SETUP_R_DISABLED |IORING_SETUP_SUBMIT_ALL |IORING_SETUP_COOP_TASKRUN |IORING_SETUP_TASKRUN_FLAG | - IORING_SETUP_SQE128 | IORING_SETUP_CQE32)) +IORING_SETUP_SQE128 |IORING_SETUP_CQE32 | + IORING_SETUP_SINGLE_ISSUER)) return -EINVAL;return io_uring_create (entries, &p, params);io_uring/io_uring_types.h +1
-0@@ -241,6 +241,7 @@ struct io_ring_ctx {/* Keep this last, we don't need it for the fast path */ struct io_restriction restrictions; +struct task_struct *submitter_task;/* slow path rsrc auxilary data, used by update/register */ struct io_rsrc_node *rsrc_backup_node;io_uring/tctx.c +23
-2@@ -81,12 +81,32 @@ __cold int io_uring_alloc_task_context(struct task_struct *task,return 0 ; } -int __io_uring_add_tctx_node (struct io_ring_ctx *ctx) +static int io_register_submitter (struct io_ring_ctx *ctx) +{ +int ret =0 ; + +mutex_lock (&ctx->uring_lock); +if (!ctx->submitter_task) + ctx->submitter_task =get_task_struct (current); +else if (ctx->submitter_task != current) + ret = -EEXIST ; +mutex_unlock (&ctx->uring_lock); + +return ret; +} + +int __io_uring_add_tctx_node (struct io_ring_ctx *ctx,bool submitter) {struct io_uring_task *tctx = current->io_uring;struct io_tctx_node *node;int ret; +if ((ctx->flags &IORING_SETUP_SINGLE_ISSUER ) && submitter) { + ret =io_register_submitter (ctx); +if (ret) +return ret; + } +if (unlikely (!tctx)) { ret =io_uring_alloc_task_context (current, ctx);if (unlikely (ret))@@ -120,7 +140,8 @@ int __io_uring_add_tctx_node(struct io_ring_ctx *ctx)list_add (&node->ctx_node, &ctx->tctx_list);mutex_unlock (&ctx->uring_lock); } - tctx->last = ctx; +if (submitter) + tctx->last = ctx;return 0 ; }io_uring/tctx.h +2
-2@@ -34,7 +34,7 @@ struct io_tctx_node {int io_uring_alloc_task_context (struct task_struct *task,struct io_ring_ctx *ctx);void io_uring_del_tctx_node (unsigned long index); -int __io_uring_add_tctx_node (struct io_ring_ctx *ctx); +int __io_uring_add_tctx_node (struct io_ring_ctx *ctx,bool submitter);void io_uring_clean_tctx (struct io_uring_task *tctx);void io_uring_unreg_ringfd (void );@@ -52,5 +52,5 @@ static inline int io_uring_add_tctx_node(struct io_ring_ctx *ctx)if (likely (tctx && tctx->last == ctx))return 0 ; -return __io_uring_add_tctx_node (ctx); +return __io_uring_add_tctx_node (ctx, true); }
Signature · 1 line
Pavel Begunkov
PATCH 22/25 io_uring: pass hash table into poll_find
In reply to Pavel BegunkovIn preparation for having multiple cancellation hash tables, pass a table pointer into io_poll_find() and other poll cancel functions.
--- io_uring/poll.c | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-)
io_uring/poll.c +14 -6
@@ -556,11 +556,12 @@ __cold bool io_poll_remove_all(struct io_ring_ctx *ctx, struct task_struct *tsk,static struct io_kiocb *io_poll_find (struct io_ring_ctx *ctx,bool poll_only,struct io_cancel_data *cd, +struct io_hash_bucket hash_table[],struct io_hash_bucket **out_bucket) {struct io_kiocb *req;u32 index =hash_long (cd->data, ctx->cancel_hash_bits); -struct io_hash_bucket *hb = &ctx->cancel_hash[index]; +struct io_hash_bucket *hb = &hash_table[index]; *out_bucket =NULL ;
@@ -584,6 +585,7 @@ static struct io_kiocb *io_poll_find(struct io_ring_ctx *ctx, bool poll_only,static struct io_kiocb *io_poll_file_find (struct io_ring_ctx *ctx,struct io_cancel_data *cd, +struct io_hash_bucket hash_table[],struct io_hash_bucket **out_bucket) {struct io_kiocb *req;
@@ -592,7 +594,7 @@ static struct io_kiocb *io_poll_file_find(struct io_ring_ctx *ctx, *out_bucket =NULL ;for (i =0 ; i < (1U << ctx->cancel_hash_bits); i++) { -struct io_hash_bucket *hb = &ctx->cancel_hash[i]; +struct io_hash_bucket *hb = &hash_table[i];spin_lock (&hb->lock);hlist_for_each_entry (req, &hb->list, hash_node) {
@@ -619,15 +621,16 @@ static bool io_poll_disarm(struct io_kiocb *req)return true; } -int io_poll_cancel (struct io_ring_ctx *ctx,struct io_cancel_data *cd) +static int __io_poll_cancel (struct io_ring_ctx *ctx,struct io_cancel_data *cd, +struct io_hash_bucket hash_table[]) {struct io_hash_bucket *bucket;struct io_kiocb *req;if (cd->flags & (IORING_ASYNC_CANCEL_FD |IORING_ASYNC_CANCEL_ANY )) - req =io_poll_file_find (ctx, cd, &bucket); + req =io_poll_file_find (ctx, cd, ctx->cancel_hash, &bucket);else - req =io_poll_find (ctx, false, cd, &bucket); + req =io_poll_find (ctx, false, cd, ctx->cancel_hash, &bucket);if (req)io_poll_cancel_req (req);
@@ -636,6 +639,11 @@ int io_poll_cancel(struct io_ring_ctx *ctx, struct io_cancel_data *cd)return req ?0 : -ENOENT ; } +int io_poll_cancel (struct io_ring_ctx *ctx,struct io_cancel_data *cd) +{ +return __io_poll_cancel (ctx, cd, ctx->cancel_hash); +} +static __poll_t io_poll_parse_events (const struct io_uring_sqe *sqe,unsigned int flags) {
@@ -731,7 +739,7 @@ int io_poll_remove(struct io_kiocb *req, unsigned int issue_flags)int ret2, ret =0 ;bool locked; - preq =io_poll_find (ctx, true, &cd, &bucket); + preq =io_poll_find (ctx, true, &cd, ctx->cancel_hash, &bucket);if (preq) ret2 =io_poll_disarm (preq);if (bucket)
Signature · 1 line
2.36.1
PATCH 23/25 io_uring: introduce a struct for hash table
In reply to Pavel BegunkovInstead of passing around a pointer to hash buckets, add a bit of type safety and wrap it into a structure.
--- io_uring/cancel.c | 6 +++--- io_uring/cancel.h | 7 +------ io_uring/fdinfo.c | 4 ++-- io_uring/io_uring.c | 29 ++++++++++++++++------------ io_uring/io_uring_types.h | 13 +++++++++++-- io_uring/poll.c | 40 +++++++++++++++++++++------------------ 6 files changed, 56 insertions(+), 43 deletions(-)
io_uring/cancel.c +3 -3
@@ -193,12 +193,12 @@ int io_async_cancel(struct io_kiocb *req, unsigned int issue_flags)return IOU_OK ; } -void init_hash_table (struct io_hash_bucket *hash_table,unsigned size) +void init_hash_table (struct io_hash_table *table,unsigned size) {unsigned int i;for (i =0 ; i < size; i++) { -spin_lock_init (&hash_table[i].lock); -INIT_HLIST_HEAD (&hash_table[i].list); +spin_lock_init (&table->hbs[i].lock); +INIT_HLIST_HEAD (&table->hbs[i].list); } }
io_uring/cancel.h +1 -6
@@ -4,9 +4,4 @@ int io_async_cancel_prep(struct io_kiocb *req, const struct io_uring_sqe *sqe);int io_async_cancel (struct io_kiocb *req,unsigned int issue_flags);int io_try_cancel (struct io_kiocb *req,struct io_cancel_data *cd); -void init_hash_table (struct io_hash_bucket *hash_table,unsigned size); - -struct io_hash_bucket { -spinlock_t lock; -struct hlist_head list; -}____cacheline_aligned_in_smp ; +void init_hash_table (struct io_hash_table *table,unsigned size);
io_uring/fdinfo.c +2 -2
@@ -158,8 +158,8 @@ static __cold void __io_uring_show_fdinfo(struct io_ring_ctx *ctx,mutex_unlock (&ctx->uring_lock);seq_puts (m,"PollList:\n" ); -for (i =0 ; i < (1U << ctx->cancel_hash_bits); i++) { -struct io_hash_bucket *hb = &ctx->cancel_hash[i]; +for (i =0 ; i < (1U << ctx->cancel_table.hash_bits); i++) { +struct io_hash_bucket *hb = &ctx->cancel_table.hbs[i];struct io_kiocb *req;spin_lock (&hb->lock);
io_uring/io_uring.c +17 -12
@@ -697,11 +697,23 @@ static __cold void io_fallback_req_func(struct work_struct *work)percpu_ref_put (&ctx->refs); } +static int io_alloc_hash_table (struct io_hash_table *table,unsigned bits) +{ +unsigned hash_buckets =1U << bits; +size_t hash_size = hash_buckets *sizeof (table->hbs[0 ]); + + table->hbs =kmalloc (hash_size,GFP_KERNEL ); +if (!table->hbs) +return -ENOMEM ; + + table->hash_bits = bits; +init_hash_table (table, hash_buckets); +return 0 ; +} +static __cold struct io_ring_ctx *io_ring_ctx_alloc (struct io_uring_params *p) {struct io_ring_ctx *ctx; -unsigned hash_buckets; -size_t hash_size;int hash_bits; ctx =kzalloc (sizeof (*ctx),GFP_KERNEL );
@@ -717,16 +729,9 @@ static __cold struct io_ring_ctx *io_ring_ctx_alloc(struct io_uring_params *p)*/ hash_bits =ilog2 (p->cq_entries) -5 ; hash_bits =clamp (hash_bits,1 ,8 ); - hash_buckets =1U << hash_bits; - hash_size = hash_buckets *sizeof (struct io_hash_bucket ); - - ctx->cancel_hash_bits = hash_bits; - ctx->cancel_hash =kmalloc (hash_size,GFP_KERNEL ); -if (!ctx->cancel_hash) +if (io_alloc_hash_table (&ctx->cancel_table, hash_bits)) goto err; -init_hash_table (ctx->cancel_hash, hash_buckets); - ctx->dummy_ubuf =kzalloc (sizeof (*ctx->dummy_ubuf),GFP_KERNEL );if (!ctx->dummy_ubuf) goto err;
@@ -767,7 +772,7 @@ static __cold struct io_ring_ctx *io_ring_ctx_alloc(struct io_uring_params *p)return ctx; err:kfree (ctx->dummy_ubuf); -kfree (ctx->cancel_hash); +kfree (ctx->cancel_table.hbs);kfree (ctx->io_bl);xa_destroy (&ctx->io_bl_xa);kfree (ctx);
@@ -3050,7 +3055,7 @@ static __cold void io_ring_ctx_free(struct io_ring_ctx *ctx)io_req_caches_free (ctx);if (ctx->hash_map)io_wq_put_hash (ctx->hash_map); -kfree (ctx->cancel_hash); +kfree (ctx->cancel_table.hbs);kfree (ctx->dummy_ubuf);kfree (ctx->io_bl);xa_destroy (&ctx->io_bl_xa);
io_uring/io_uring_types.h +11 -2
@@ -7,6 +7,16 @@#include "io-wq.h" #include "filetable.h" +struct io_hash_bucket { +spinlock_t lock; +struct hlist_head list; +}____cacheline_aligned_in_smp ; + +struct io_hash_table { +struct io_hash_bucket *hbs; +unsigned hash_bits; +}; +struct io_uring {u32 head____cacheline_aligned_in_smp ;u32 tail____cacheline_aligned_in_smp ;
@@ -222,8 +232,7 @@ struct io_ring_ctx {* manipulate the list, hence no extra locking is needed there. */ struct io_wq_work_list iopoll_list; -struct io_hash_bucket *cancel_hash; -unsigned cancel_hash_bits; +struct io_hash_table cancel_table;bool poll_multi_queue;struct list_head io_buffers_comp;
io_uring/poll.c +22 -18
@@ -73,9 +73,9 @@ static struct io_poll *io_poll_get_single(struct io_kiocb *req)static void io_poll_req_insert (struct io_kiocb *req) { -struct io_ring_ctx *ctx = req->ctx; -u32 index =hash_long (req->cqe.user_data, ctx->cancel_hash_bits); -struct io_hash_bucket *hb = &ctx->cancel_hash[index]; +struct io_hash_table *table = &req->ctx->cancel_table; +u32 index =hash_long (req->cqe.user_data, table->hash_bits); +struct io_hash_bucket *hb = &table->hbs[index];spin_lock (&hb->lock);hlist_add_head (&req->hash_node, &hb->list);
@@ -84,8 +84,9 @@ static void io_poll_req_insert(struct io_kiocb *req)static void io_poll_req_delete (struct io_kiocb *req,struct io_ring_ctx *ctx) { -u32 index =hash_long (req->cqe.user_data, ctx->cancel_hash_bits); -spinlock_t *lock = &ctx->cancel_hash[index].lock; +struct io_hash_table *table = &req->ctx->cancel_table; +u32 index =hash_long (req->cqe.user_data, table->hash_bits); +spinlock_t *lock = &table->hbs[index].lock;spin_lock (lock);hash_del (&req->hash_node);
@@ -533,13 +534,15 @@ int io_arm_poll_handler(struct io_kiocb *req, unsigned issue_flags)__cold bool io_poll_remove_all (struct io_ring_ctx *ctx,struct task_struct *tsk,bool cancel_all) { +struct io_hash_table *table = &ctx->cancel_table; +unsigned nr_buckets =1U << table->hash_bits;struct hlist_node *tmp;struct io_kiocb *req;bool found = false;int i; -for (i =0 ; i < (1U << ctx->cancel_hash_bits); i++) { -struct io_hash_bucket *hb = &ctx->cancel_hash[i]; +for (i =0 ; i < nr_buckets; i++) { +struct io_hash_bucket *hb = &table->hbs[i];spin_lock (&hb->lock);hlist_for_each_entry_safe (req, tmp, &hb->list, hash_node) {
@@ -556,12 +559,12 @@ __cold bool io_poll_remove_all(struct io_ring_ctx *ctx, struct task_struct *tsk,static struct io_kiocb *io_poll_find (struct io_ring_ctx *ctx,bool poll_only,struct io_cancel_data *cd, -struct io_hash_bucket hash_table[], +struct io_hash_table *table,struct io_hash_bucket **out_bucket) {struct io_kiocb *req; -u32 index =hash_long (cd->data, ctx->cancel_hash_bits); -struct io_hash_bucket *hb = &hash_table[index]; +u32 index =hash_long (cd->data, table->hash_bits); +struct io_hash_bucket *hb = &table->hbs[index]; *out_bucket =NULL ;
@@ -585,16 +588,17 @@ static struct io_kiocb *io_poll_find(struct io_ring_ctx *ctx, bool poll_only,static struct io_kiocb *io_poll_file_find (struct io_ring_ctx *ctx,struct io_cancel_data *cd, -struct io_hash_bucket hash_table[], +struct io_hash_table *table,struct io_hash_bucket **out_bucket) { +unsigned nr_buckets =1U << table->hash_bits;struct io_kiocb *req;int i; *out_bucket =NULL ; -for (i =0 ; i < (1U << ctx->cancel_hash_bits); i++) { -struct io_hash_bucket *hb = &hash_table[i]; +for (i =0 ; i < nr_buckets; i++) { +struct io_hash_bucket *hb = &table->hbs[i];spin_lock (&hb->lock);hlist_for_each_entry (req, &hb->list, hash_node) {
@@ -622,15 +626,15 @@ static bool io_poll_disarm(struct io_kiocb *req) }static int __io_poll_cancel (struct io_ring_ctx *ctx,struct io_cancel_data *cd, -struct io_hash_bucket hash_table[]) +struct io_hash_table *table) {struct io_hash_bucket *bucket;struct io_kiocb *req;if (cd->flags & (IORING_ASYNC_CANCEL_FD |IORING_ASYNC_CANCEL_ANY )) - req =io_poll_file_find (ctx, cd, ctx->cancel_hash, &bucket); + req =io_poll_file_find (ctx, cd, table, &bucket);else - req =io_poll_find (ctx, false, cd, ctx->cancel_hash, &bucket); + req =io_poll_find (ctx, false, cd, table, &bucket);if (req)io_poll_cancel_req (req);
@@ -641,7 +645,7 @@ static int __io_poll_cancel(struct io_ring_ctx *ctx, struct io_cancel_data *cd,int io_poll_cancel (struct io_ring_ctx *ctx,struct io_cancel_data *cd) { -return __io_poll_cancel (ctx, cd, ctx->cancel_hash); +return __io_poll_cancel (ctx, cd, &ctx->cancel_table); } static __poll_t io_poll_parse_events(const struct io_uring_sqe *sqe,
@@ -739,7 +743,7 @@ int io_poll_remove(struct io_kiocb *req, unsigned int issue_flags)int ret2, ret =0 ;bool locked; - preq =io_poll_find (ctx, true, &cd, ctx->cancel_hash, &bucket); + preq =io_poll_find (ctx, true, &cd, &ctx->cancel_table, &bucket);if (preq) ret2 =io_poll_disarm (preq);if (bucket)
Signature · 1 line
2.36.1
PATCH 24/25 io_uring: propagate locking state to poll cancel
In reply to Pavel BegunkovPoll cancellation will be soon need to grab ->uring_lock inside, pass the locking state, i.e. issue_flags, inside the cancellation functions.
--- io_uring/cancel.c | 7 ++++--- io_uring/cancel.h | 3 ++- io_uring/poll.c | 3 ++- io_uring/poll.h | 3 ++- io_uring/timeout.c | 3 ++- 5 files changed, 12 insertions(+), 7 deletions(-)
io_uring/cancel.c +4 -3
@@ -78,7 +78,8 @@ static int io_async_cancel_one(struct io_uring_task *tctx,return ret; } -int io_try_cancel (struct io_kiocb *req,struct io_cancel_data *cd) +int io_try_cancel (struct io_kiocb *req,struct io_cancel_data *cd, +unsigned issue_flags) {struct io_ring_ctx *ctx = req->ctx;int ret;
@@ -93,7 +94,7 @@ int io_try_cancel(struct io_kiocb *req, struct io_cancel_data *cd)if (!ret)return 0 ; - ret =io_poll_cancel (ctx, cd); + ret =io_poll_cancel (ctx, cd, issue_flags);if (ret != -ENOENT )return ret;
@@ -136,7 +137,7 @@ static int __io_async_cancel(struct io_cancel_data *cd, struct io_kiocb *req,int ret, nr =0 ; do { - ret = io_try_cancel(req, cd); + ret = io_try_cancel(req, cd, issue_flags); if (ret == -ENOENT) break; if (!all)
io_uring/cancel.h +2 -1
@@ -3,5 +3,6 @@int io_async_cancel_prep (struct io_kiocb *req,const struct io_uring_sqe *sqe);int io_async_cancel (struct io_kiocb *req,unsigned int issue_flags); -int io_try_cancel (struct io_kiocb *req,struct io_cancel_data *cd); +int io_try_cancel (struct io_kiocb *req,struct io_cancel_data *cd, +unsigned int issue_flags);void init_hash_table (struct io_hash_table *table,unsigned size);
io_uring/poll.c +2 -1
@@ -643,7 +643,8 @@ static int __io_poll_cancel(struct io_ring_ctx *ctx, struct io_cancel_data *cd,return req ?0 : -ENOENT ; } -int io_poll_cancel (struct io_ring_ctx *ctx,struct io_cancel_data *cd) +int io_poll_cancel (struct io_ring_ctx *ctx,struct io_cancel_data *cd, +unsigned issue_flags) {return __io_poll_cancel (ctx, cd, &ctx->cancel_table); }
io_uring/poll.h +2 -1
@@ -24,7 +24,8 @@ int io_poll_add(struct io_kiocb *req, unsigned int issue_flags);int io_poll_remove_prep (struct io_kiocb *req,const struct io_uring_sqe *sqe);int io_poll_remove (struct io_kiocb *req,unsigned int issue_flags); -int io_poll_cancel (struct io_ring_ctx *ctx,struct io_cancel_data *cd); +int io_poll_cancel (struct io_ring_ctx *ctx,struct io_cancel_data *cd, +unsigned issue_flags);int io_arm_poll_handler (struct io_kiocb *req,unsigned issue_flags);bool io_poll_remove_all (struct io_ring_ctx *ctx,struct task_struct *tsk,bool cancel_all);
io_uring/timeout.c +2 -1
@@ -262,6 +262,7 @@ int io_timeout_cancel(struct io_ring_ctx *ctx, struct io_cancel_data *cd)static void io_req_task_link_timeout (struct io_kiocb *req,bool *locked) { +unsigned issue_flags = *locked ?0 :IO_URING_F_UNLOCKED ;struct io_timeout *timeout =io_kiocb_to_cmd (req);struct io_kiocb *prev = timeout->prev;int ret = -ENOENT ;
@@ -273,7 +274,7 @@ static void io_req_task_link_timeout(struct io_kiocb *req, bool *locked) .data = prev->cqe.user_data, }; - ret =io_try_cancel (req, &cd); + ret =io_try_cancel (req, &cd, issue_flags); }io_req_set_res (req, ret ?: -ETIME ,0 );io_req_complete_post (req);
Signature · 1 line
2.36.1
PATCH 25/25 io_uring: mutex locked poll hashing
In reply to Pavel BegunkovCurrently we do two extra spin lock/unlock pairs to add a poll/apoll request to the cancellation hash table and remove it from there.
On the submission side we often already hold ->uring_lock and tw completion is likely to hold it as well. Add a second cancellation hash table protected by ->uring_lock. In concerns for latency because of a need to have the mutex locked on the completion side, use the new table only in following cases:
1) IORING_SETUP_SINGLE_ISSUER: only one task grabs uring_lock, so there is no contention and so the main tw hander will always end up grabbing it before calling into callbacks.
2) IORING_SETUP_SQPOLL: same as with single issuer, only one task is using ->uring_lock.
3) apoll: we normally grab the lock on the completion side anyway to execute the request, so it's free.
--- io_uring/io_uring.c | 9 +++- io_uring/io_uring_types.h | 4 ++ io_uring/poll.c | 111 ++++++++++++++++++++++++++++++-------- 3 files changed, 102 insertions(+), 22 deletions(-)
io_uring/io_uring.c +8 -1
@@ -731,6 +731,8 @@ static __cold struct io_ring_ctx *io_ring_ctx_alloc(struct io_uring_params *p) hash_bits =clamp (hash_bits,1 ,8 );if (io_alloc_hash_table (&ctx->cancel_table, hash_bits)) goto err; +if (io_alloc_hash_table (&ctx->cancel_table_locked, hash_bits)) + goto err; ctx->dummy_ubuf =kzalloc (sizeof (*ctx->dummy_ubuf),GFP_KERNEL );if (!ctx->dummy_ubuf)
@@ -773,6 +775,7 @@ static __cold struct io_ring_ctx *io_ring_ctx_alloc(struct io_uring_params *p) err:kfree (ctx->dummy_ubuf);kfree (ctx->cancel_table.hbs); +kfree (ctx->cancel_table_locked.hbs);kfree (ctx->io_bl);xa_destroy (&ctx->io_bl_xa);kfree (ctx);
@@ -3056,6 +3059,7 @@ static __cold void io_ring_ctx_free(struct io_ring_ctx *ctx)if (ctx->hash_map)io_wq_put_hash (ctx->hash_map);kfree (ctx->cancel_table.hbs); +kfree (ctx->cancel_table_locked.hbs);kfree (ctx->dummy_ubuf);kfree (ctx->io_bl);xa_destroy (&ctx->io_bl_xa);
@@ -3217,12 +3221,13 @@ static __cold void io_ring_ctx_wait_and_kill(struct io_ring_ctx *ctx)__io_cqring_overflow_flush (ctx, true);xa_for_each (&ctx->personalities, index, creds)io_unregister_personality (ctx, index); +if (ctx->rings) +io_poll_remove_all (ctx,NULL , true);mutex_unlock (&ctx->uring_lock);/* failed during ring init, it couldn't have issued any requests */ if (ctx->rings) {io_kill_timeouts (ctx,NULL , true); -io_poll_remove_all (ctx,NULL , true);/* if we failed setting up the ctx, we might not have any rings */ io_iopoll_try_reap_events (ctx); }
@@ -3347,7 +3352,9 @@ static __cold void io_uring_try_cancel_requests(struct io_ring_ctx *ctx, } ret |=io_cancel_defer_files (ctx, task, cancel_all); +mutex_lock (&ctx->uring_lock); ret |=io_poll_remove_all (ctx, task, cancel_all); +mutex_unlock (&ctx->uring_lock); ret |=io_kill_timeouts (ctx, task, cancel_all);if (task) ret |=io_run_task_work ();
io_uring/io_uring_types.h +4 -0
@@ -189,6 +189,7 @@ struct io_ring_ctx {struct xarray io_bl_xa;struct list_head io_buffers_cache; +struct io_hash_table cancel_table_locked;struct list_head cq_overflow_list;struct list_head apoll_cache;struct xarray personalities;
@@ -323,6 +324,7 @@ enum {/* keep async read/write and isreg together and in order */ REQ_F_SUPPORT_NOWAIT_BIT ,REQ_F_ISREG_BIT , +REQ_F_HASH_LOCKED_BIT ,/* not a real bit, just to check we're not overflowing the space */ __REQ_F_LAST_BIT,
@@ -388,6 +390,8 @@ enum {REQ_F_APOLL_MULTISHOT =BIT (REQ_F_APOLL_MULTISHOT_BIT ),/* recvmsg special flag, clear EPOLLIN */ REQ_F_CLEAR_POLLIN =BIT (REQ_F_CLEAR_POLLIN_BIT ), +/* hashed into ->cancel_hash_locked */ + REQ_F_HASH_LOCKED = BIT(REQ_F_HASH_LOCKED_BIT), };typedef void (*io_req_tw_func_t )(struct io_kiocb *req,bool *locked);
io_uring/poll.c +90 -21
195 lines not on this page · raw message
Signature · 1 line
2.36.1