]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
io_uring: get rid of alloc cache init_once handling
authorJens Axboe <axboe@kernel.dk>
Thu, 23 Jan 2025 03:00:57 +0000 (20:00 -0700)
committerJens Axboe <axboe@kernel.dk>
Thu, 23 Jan 2025 18:32:28 +0000 (11:32 -0700)
init_once is called when an object doesn't come from the cache, and
hence needs initial clearing of certain members. While the whole
struct could get cleared by memset() in that case, a few of the cache
members are large enough that this may cause unnecessary overhead if
the caches used aren't large enough to satisfy the workload. For those
cases, some churn of kmalloc+kfree is to be expected.

Ensure that the 3 users that need clearing put the members they need
cleared at the start of the struct, and wrap the rest of the struct in
a struct group so the offset is known.

While at it, improve the interaction with KASAN such that when/if
KASAN writes to members inside the struct that should be retained over
caching, it won't trip over itself. For rw and net, the retaining of
the iovec over caching is disabled if KASAN is enabled. A helper will
free and clear those members in that case.

Signed-off-by: Jens Axboe <axboe@kernel.dk>
12 files changed:
include/linux/io_uring/cmd.h
include/linux/io_uring_types.h
io_uring/alloc_cache.h
io_uring/futex.c
io_uring/io_uring.c
io_uring/io_uring.h
io_uring/net.c
io_uring/net.h
io_uring/poll.c
io_uring/rw.c
io_uring/rw.h
io_uring/uring_cmd.c

index a3ce553413de823f6ab68f15634d26f1d05b864b..abd0c8bd950ba5083c2bccd9415dd5cede2a8565 100644 (file)
@@ -19,8 +19,8 @@ struct io_uring_cmd {
 };
 
 struct io_uring_cmd_data {
-       struct io_uring_sqe     sqes[2];
        void                    *op_data;
+       struct io_uring_sqe     sqes[2];
 };
 
 static inline const void *io_uring_sqe_cmd(const struct io_uring_sqe *sqe)
index 623d8e798a11a5d535ed1b0bb5bab350c89fad01..3def525a1da3753de262cad8ade7d59fb67d7139 100644 (file)
@@ -222,7 +222,8 @@ struct io_alloc_cache {
        void                    **entries;
        unsigned int            nr_cached;
        unsigned int            max_cached;
-       size_t                  elem_size;
+       unsigned int            elem_size;
+       unsigned int            init_clear;
 };
 
 struct io_ring_ctx {
index a3a8cfec32ce1760c3b5a73338f9f92e8a36adde..cca96aff3277eb262bc904b2c6f0fb85cc753af7 100644 (file)
@@ -6,6 +6,19 @@
  */
 #define IO_ALLOC_CACHE_MAX     128
 
+#if defined(CONFIG_KASAN)
+static inline void io_alloc_cache_kasan(struct iovec **iov, int *nr)
+{
+       kfree(*iov);
+       *iov = NULL;
+       *nr = 0;
+}
+#else
+static inline void io_alloc_cache_kasan(struct iovec **iov, int *nr)
+{
+}
+#endif
+
 static inline bool io_alloc_cache_put(struct io_alloc_cache *cache,
                                      void *entry)
 {
@@ -23,35 +36,47 @@ static inline void *io_alloc_cache_get(struct io_alloc_cache *cache)
        if (cache->nr_cached) {
                void *entry = cache->entries[--cache->nr_cached];
 
+               /*
+                * If KASAN is enabled, always clear the initial bytes that
+                * must be zeroed post alloc, in case any of them overlap
+                * with KASAN storage.
+                */
+#if defined(CONFIG_KASAN)
                kasan_mempool_unpoison_object(entry, cache->elem_size);
+               if (cache->init_clear)
+                       memset(entry, 0, cache->init_clear);
+#endif
                return entry;
        }
 
        return NULL;
 }
 
-static inline void *io_cache_alloc(struct io_alloc_cache *cache, gfp_t gfp,
-                                  void (*init_once)(void *obj))
+static inline void *io_cache_alloc(struct io_alloc_cache *cache, gfp_t gfp)
 {
-       if (unlikely(!cache->nr_cached)) {
-               void *obj = kmalloc(cache->elem_size, gfp);
+       void *obj;
 
-               if (obj && init_once)
-                       init_once(obj);
+       obj = io_alloc_cache_get(cache);
+       if (obj)
                return obj;
-       }
-       return io_alloc_cache_get(cache);
+
+       obj = kmalloc(cache->elem_size, gfp);
+       if (obj && cache->init_clear)
+               memset(obj, 0, cache->init_clear);
+       return obj;
 }
 
 /* returns false if the cache was initialized properly */
 static inline bool io_alloc_cache_init(struct io_alloc_cache *cache,
-                                      unsigned max_nr, size_t size)
+                                      unsigned max_nr, unsigned int size,
+                                      unsigned int init_bytes)
 {
        cache->entries = kvmalloc_array(max_nr, sizeof(void *), GFP_KERNEL);
        if (cache->entries) {
                cache->nr_cached = 0;
                cache->max_cached = max_nr;
                cache->elem_size = size;
+               cache->init_clear = init_bytes;
                return false;
        }
        return true;
index 30139cc150f2280569cc6bb8830b4f6ac5a06620..3159a2b7eeca138f2fe5ec04221283fa0ea65579 100644 (file)
@@ -36,7 +36,7 @@ struct io_futex_data {
 bool io_futex_cache_init(struct io_ring_ctx *ctx)
 {
        return io_alloc_cache_init(&ctx->futex_cache, IO_FUTEX_ALLOC_CACHE_MAX,
-                               sizeof(struct io_futex_data));
+                               sizeof(struct io_futex_data), 0);
 }
 
 void io_futex_cache_free(struct io_ring_ctx *ctx)
@@ -320,7 +320,7 @@ int io_futex_wait(struct io_kiocb *req, unsigned int issue_flags)
        }
 
        io_ring_submit_lock(ctx, issue_flags);
-       ifd = io_cache_alloc(&ctx->futex_cache, GFP_NOWAIT, NULL);
+       ifd = io_cache_alloc(&ctx->futex_cache, GFP_NOWAIT);
        if (!ifd) {
                ret = -ENOMEM;
                goto done_unlock;
index 7bfbc7c223677148deb5d40326486980f5ad1770..263e504be4a8be4d0ae8f604e279b2cefc49dca8 100644 (file)
@@ -315,16 +315,18 @@ static __cold struct io_ring_ctx *io_ring_ctx_alloc(struct io_uring_params *p)
        INIT_LIST_HEAD(&ctx->cq_overflow_list);
        INIT_LIST_HEAD(&ctx->io_buffers_cache);
        ret = io_alloc_cache_init(&ctx->apoll_cache, IO_POLL_ALLOC_CACHE_MAX,
-                           sizeof(struct async_poll));
+                           sizeof(struct async_poll), 0);
        ret |= io_alloc_cache_init(&ctx->netmsg_cache, IO_ALLOC_CACHE_MAX,
-                           sizeof(struct io_async_msghdr));
+                           sizeof(struct io_async_msghdr),
+                           offsetof(struct io_async_msghdr, clear));
        ret |= io_alloc_cache_init(&ctx->rw_cache, IO_ALLOC_CACHE_MAX,
-                           sizeof(struct io_async_rw));
+                           sizeof(struct io_async_rw),
+                           offsetof(struct io_async_rw, clear));
        ret |= io_alloc_cache_init(&ctx->uring_cache, IO_ALLOC_CACHE_MAX,
-                           sizeof(struct io_uring_cmd_data));
+                           sizeof(struct io_uring_cmd_data), 0);
        spin_lock_init(&ctx->msg_lock);
        ret |= io_alloc_cache_init(&ctx->msg_cache, IO_ALLOC_CACHE_MAX,
-                           sizeof(struct io_kiocb));
+                           sizeof(struct io_kiocb), 0);
        ret |= io_futex_cache_init(ctx);
        if (ret)
                goto free_ref;
index f65e3f3ede517d1c39b895af6e65dd0d7aecd321..67adbb3c1bf58c88c6783f90581c2c5bdf7b1a8c 100644 (file)
@@ -226,10 +226,9 @@ static inline void io_req_set_res(struct io_kiocb *req, s32 res, u32 cflags)
 }
 
 static inline void *io_uring_alloc_async_data(struct io_alloc_cache *cache,
-                                             struct io_kiocb *req,
-                                             void (*init_once)(void *obj))
+                                             struct io_kiocb *req)
 {
-       req->async_data = io_cache_alloc(cache, GFP_KERNEL, init_once);
+       req->async_data = io_cache_alloc(cache, GFP_KERNEL);
        if (req->async_data)
                req->flags |= REQ_F_ASYNC_DATA;
        return req->async_data;
index 85f55fbc25c94ec11b0327fbfccd16636ce0a8a6..41eef286f8b9a4d3f7a6320110bc947c156fa0c2 100644 (file)
@@ -137,7 +137,6 @@ static void io_netmsg_iovec_free(struct io_async_msghdr *kmsg)
 static void io_netmsg_recycle(struct io_kiocb *req, unsigned int issue_flags)
 {
        struct io_async_msghdr *hdr = req->async_data;
-       struct iovec *iov;
 
        /* can't recycle, ensure we free the iovec if we have one */
        if (unlikely(issue_flags & IO_URING_F_UNLOCKED)) {
@@ -146,39 +145,25 @@ static void io_netmsg_recycle(struct io_kiocb *req, unsigned int issue_flags)
        }
 
        /* Let normal cleanup path reap it if we fail adding to the cache */
-       iov = hdr->free_iov;
+       io_alloc_cache_kasan(&hdr->free_iov, &hdr->free_iov_nr);
        if (io_alloc_cache_put(&req->ctx->netmsg_cache, hdr)) {
-               if (iov)
-                       kasan_mempool_poison_object(iov);
                req->async_data = NULL;
                req->flags &= ~REQ_F_ASYNC_DATA;
        }
 }
 
-static void io_msg_async_data_init(void *obj)
-{
-       struct io_async_msghdr *hdr = (struct io_async_msghdr *)obj;
-
-       hdr->free_iov = NULL;
-       hdr->free_iov_nr = 0;
-}
-
 static struct io_async_msghdr *io_msg_alloc_async(struct io_kiocb *req)
 {
        struct io_ring_ctx *ctx = req->ctx;
        struct io_async_msghdr *hdr;
 
-       hdr = io_uring_alloc_async_data(&ctx->netmsg_cache, req,
-                                       io_msg_async_data_init);
+       hdr = io_uring_alloc_async_data(&ctx->netmsg_cache, req);
        if (!hdr)
                return NULL;
 
        /* If the async data was cached, we might have an iov cached inside. */
-       if (hdr->free_iov) {
-               kasan_mempool_unpoison_object(hdr->free_iov,
-                                             hdr->free_iov_nr * sizeof(struct iovec));
+       if (hdr->free_iov)
                req->flags |= REQ_F_NEED_CLEANUP;
-       }
        return hdr;
 }
 
@@ -1813,11 +1798,10 @@ void io_netmsg_cache_free(const void *entry)
 {
        struct io_async_msghdr *kmsg = (struct io_async_msghdr *) entry;
 
-       if (kmsg->free_iov) {
-               kasan_mempool_unpoison_object(kmsg->free_iov,
-                               kmsg->free_iov_nr * sizeof(struct iovec));
+#if !defined(CONFIG_KASAN)
+       if (kmsg->free_iov)
                io_netmsg_iovec_free(kmsg);
-       }
+#endif
        kfree(kmsg);
 }
 #endif
index 52bfee05f06a14ea3df09b94ab0b26f2afad4e20..b804c2b36e605ccd026bdd4075190077822e3549 100644 (file)
@@ -5,16 +5,20 @@
 
 struct io_async_msghdr {
 #if defined(CONFIG_NET)
-       struct iovec                    fast_iov;
-       /* points to an allocated iov, if NULL we use fast_iov instead */
        struct iovec                    *free_iov;
+       /* points to an allocated iov, if NULL we use fast_iov instead */
        int                             free_iov_nr;
-       int                             namelen;
-       __kernel_size_t                 controllen;
-       __kernel_size_t                 payloadlen;
-       struct sockaddr __user          *uaddr;
-       struct msghdr                   msg;
-       struct sockaddr_storage         addr;
+       struct_group(clear,
+               int                             namelen;
+               struct iovec                    fast_iov;
+               __kernel_size_t                 controllen;
+               __kernel_size_t                 payloadlen;
+               struct sockaddr __user          *uaddr;
+               struct msghdr                   msg;
+               struct sockaddr_storage         addr;
+       );
+#else
+       struct_group(clear);
 #endif
 };
 
index cc01c40b43d31b8be8aca048bb689c6a1c0d25e2..356474c66f324dd1c7eb2379854a375c7ba2b21a 100644 (file)
@@ -650,7 +650,7 @@ static struct async_poll *io_req_alloc_apoll(struct io_kiocb *req,
                kfree(apoll->double_poll);
        } else {
                if (!(issue_flags & IO_URING_F_UNLOCKED))
-                       apoll = io_cache_alloc(&ctx->apoll_cache, GFP_ATOMIC, NULL);
+                       apoll = io_cache_alloc(&ctx->apoll_cache, GFP_ATOMIC);
                else
                        apoll = kmalloc(sizeof(*apoll), GFP_ATOMIC);
                if (!apoll)
index a9a2733be842059cb25e316f1d2d060947eab411..991ecfbea88e30acea0a35f594eb3f46ed2d5d6a 100644 (file)
@@ -158,16 +158,13 @@ static void io_rw_iovec_free(struct io_async_rw *rw)
 static void io_rw_recycle(struct io_kiocb *req, unsigned int issue_flags)
 {
        struct io_async_rw *rw = req->async_data;
-       struct iovec *iov;
 
        if (unlikely(issue_flags & IO_URING_F_UNLOCKED)) {
                io_rw_iovec_free(rw);
                return;
        }
-       iov = rw->free_iovec;
+       io_alloc_cache_kasan(&rw->free_iovec, &rw->free_iov_nr);
        if (io_alloc_cache_put(&req->ctx->rw_cache, rw)) {
-               if (iov)
-                       kasan_mempool_poison_object(iov);
                req->async_data = NULL;
                req->flags &= ~REQ_F_ASYNC_DATA;
        }
@@ -208,27 +205,16 @@ static void io_req_rw_cleanup(struct io_kiocb *req, unsigned int issue_flags)
        }
 }
 
-static void io_rw_async_data_init(void *obj)
-{
-       struct io_async_rw *rw = (struct io_async_rw *)obj;
-
-       rw->free_iovec = NULL;
-       rw->bytes_done = 0;
-}
-
 static int io_rw_alloc_async(struct io_kiocb *req)
 {
        struct io_ring_ctx *ctx = req->ctx;
        struct io_async_rw *rw;
 
-       rw = io_uring_alloc_async_data(&ctx->rw_cache, req, io_rw_async_data_init);
+       rw = io_uring_alloc_async_data(&ctx->rw_cache, req);
        if (!rw)
                return -ENOMEM;
-       if (rw->free_iovec) {
-               kasan_mempool_unpoison_object(rw->free_iovec,
-                                             rw->free_iov_nr * sizeof(struct iovec));
+       if (rw->free_iovec)
                req->flags |= REQ_F_NEED_CLEANUP;
-       }
        rw->bytes_done = 0;
        return 0;
 }
@@ -1323,10 +1309,9 @@ void io_rw_cache_free(const void *entry)
 {
        struct io_async_rw *rw = (struct io_async_rw *) entry;
 
-       if (rw->free_iovec) {
-               kasan_mempool_unpoison_object(rw->free_iovec,
-                               rw->free_iov_nr * sizeof(struct iovec));
+#if !defined(CONFIG_KASAN)
+       if (rw->free_iovec)
                io_rw_iovec_free(rw);
-       }
+#endif
        kfree(rw);
 }
index 2d7656bd268d64b1f1e818752f1fddda82f2387c..eaa59bd64870973429a42be644c286eec6e3dd34 100644 (file)
@@ -9,19 +9,24 @@ struct io_meta_state {
 
 struct io_async_rw {
        size_t                          bytes_done;
-       struct iov_iter                 iter;
-       struct iov_iter_state           iter_state;
-       struct iovec                    fast_iov;
        struct iovec                    *free_iovec;
-       int                             free_iov_nr;
-       /* wpq is for buffered io, while meta fields are used with direct io */
-       union {
-               struct wait_page_queue          wpq;
-               struct {
-                       struct uio_meta                 meta;
-                       struct io_meta_state            meta_state;
+       struct_group(clear,
+               struct iov_iter                 iter;
+               struct iov_iter_state           iter_state;
+               struct iovec                    fast_iov;
+               int                             free_iov_nr;
+               /*
+                * wpq is for buffered io, while meta fields are used with
+                * direct io
+                */
+               union {
+                       struct wait_page_queue          wpq;
+                       struct {
+                               struct uio_meta                 meta;
+                               struct io_meta_state            meta_state;
+                       };
                };
-       };
+       );
 };
 
 int io_prep_read_fixed(struct io_kiocb *req, const struct io_uring_sqe *sqe);
index 6a63ec4b544566e4e41c45ff27f3bf3358c1d24d..1f6a82128b475f29017a1f77c175fd555405855a 100644 (file)
@@ -168,23 +168,16 @@ void io_uring_cmd_done(struct io_uring_cmd *ioucmd, ssize_t ret, u64 res2,
 }
 EXPORT_SYMBOL_GPL(io_uring_cmd_done);
 
-static void io_uring_cmd_init_once(void *obj)
-{
-       struct io_uring_cmd_data *data = obj;
-
-       data->op_data = NULL;
-}      
-
 static int io_uring_cmd_prep_setup(struct io_kiocb *req,
                                   const struct io_uring_sqe *sqe)
 {
        struct io_uring_cmd *ioucmd = io_kiocb_to_cmd(req, struct io_uring_cmd);
        struct io_uring_cmd_data *cache;
 
-       cache = io_uring_alloc_async_data(&req->ctx->uring_cache, req,
-                       io_uring_cmd_init_once);
+       cache = io_uring_alloc_async_data(&req->ctx->uring_cache, req);
        if (!cache)
                return -ENOMEM;
+       cache->op_data = NULL;
 
        if (!(req->flags & REQ_F_FORCE_ASYNC)) {
                /* defer memcpy until we need it */