io_uring zerocopy send
Subsystems (7)
v3 cover io_uring zerocopy send
The patchset implements io_uring zerocopy send. It works with both registered and normal buffers, mixing is allowed but not recommended. Apart from usual request completions, just as with MSG_ZEROCOPY, io_uring separately notifies the userspace when buffers are freed and can be reused (see API design below), which is delivered into io_uring's Completion Queue. Those "buffer-free" notifications are not necessarily per request, but the userspace has control over it and should explicitly attaching a number of requests to a single notification. The series also adds some internal optimisations when used with registered buffers like removing page referencing.
From the kernel networking perspective there are two main changes. The first one is passing ubuf_info into the network layer from io_uring (inside of an in kernel struct msghdr). This allows extra optimisations, e.g. ubuf_info caching on the io_uring side, but also helps to avoid cross-referencing and synchronisation problems. The second part is an optional optimisation removing page referencing for requests with registered buffers.
Benchmarking with an optimised version of the selftest (see [1]), which in a loop sends a bunch of requests and then waits for their completions. "+ flush" column posts one additional "buffer-free" notification per request, and just "zc" doesn't post buffer notifications at all.
NIC (requests / second):
IO size | non-zc | zc | zc + flush 4000 | 495134 | 606420 (+22%) | 558971 (+12%) 1500 | 551808 | 577116 (+4.5%) | 565803 (+2.5%) 1000 | 584677 | 592088 (+1.2%) | 560885 (-4%) 600 | 596292 | 598550 (+0.4%) | 555366 (-6.7%)
dummy (requests / second):
IO size | non-zc | zc | zc + flush 8000 | 1299916 | 2396600 (+84%) | 2224219 (+71%) 4000 | 1869230 | 2344146 (+25%) | 2170069 (+16%) 1200 | 2071617 | 2361960 (+14%) | 2203052 (+6%) 600 | 2106794 | 2381527 (+13%) | 2195295 (+4%)
Previously it also brought a massive performance speedup compared to the msg_zerocopy tool (see [3]), which is probably not super interesting.
There is an additional bunch of refcounting optimisations that was omitted from the series for simplicity and as they don't change the picture drastically, they will be sent as follow up, as well as flushing optimisations closing the performance gap b/w two last columns.
Note: the series is based on net-next + for-5.20/io_uring, but as vanilla net-next fails for me the repo (see [2]) is on top of for-5.20/io_uring.
Links:
liburing (benchmark + some tests):
[1] https://github.com/isilence/liburing/tree/zc_v3
kernel repo:
[2] https://github.com/isilence/linux/tree/zc_v3
RFC v1:
[3] https://lore.kernel.org/io-uring/[email protected]/
RFC v2:
https://lore.kernel.org/io-uring/[email protected]/
API design overview:
The series introduces an io_uring concept of notifactors. From the userspace perspective it's an entity to which it can bind one or more requests and then requesting to flush it. Flushing a notifier makes it impossible to attach new requests to it, and instructs the notifier to post a completion once all requests attached to it are completed and the kernel doesn't need the buffers anymore.
Notifications are stored in notification slots, which should be registered as an array in io_uring. Each slot stores only one notifier at any particular moment. Flushing removes it from the slot and the slot automatically replaces it with a new notifier. All operations with notifiers are done by specifying an index of a slot it's currently in.
When registering a notification the userspace specifies a u64 tag for each slot, which will be copied in notification completion entries as cqe::user_data. cqe::res is 0 and cqe::flags is equal to wrap around u32 sequence number counting notifiers of a slot.
Changelog:
RFC v2 -> v3:
mem accounting for non-registered buffers allow mixing registered and normal requests per notifier notification flushing via IORING_OP_RSRC_UPDATE TCP support
fix buffer indexing
fix io-wq ->uring_lock locking
fix bugs when mixing with MSG_ZEROCOPY
fix managed refs bugs in skbuff.c
RFC -> RFC v2:
remove additional overhead for non-zc from skb_release_data() avoid msg propagation, hide extra bits of non-zc overhead task_work based "buffer free" notifications
improve io_uring's notification refcounting
added 5/19, (no pfmemalloc tracking)
added 8/19 and 9/19 preventing small copies with zc misc small changes
Pavel Begunkov (25):
ipv4: avoid partial copy for zc
ipv6: avoid partial copy for zc
skbuff: add SKBFL_DONT_ORPHAN flag
skbuff: carry external ubuf_info in msghdr
net: bvec specific path in zerocopy_sg_from_iter
net: optimise bvec-based zc page referencing
net: don't track pfmemalloc for managed frags
skbuff: don't mix ubuf_info of different types ipv4/udp: support zc with managed data ipv6/udp: support zc with managed data
tcp: support zc with managed data
io_uring: add zc notification infrastructure
io_uring: export task put
io_uring: cache struct io_notif
io_uring: complete notifiers in tw
io_uring: add notification slot registration
io_uring: wire send zc request type
io_uring: account locked pages for non-fixed zc
io_uring: allow to pass addr into sendzc
io_uring: add rsrc referencing for notifiers
io_uring: sendzc with fixed buffers
io_uring: flush notifiers after sendzc
io_uring: rename IORING_OP_FILES_UPDATE
io_uring: add zc notification flush requests selftests/io_uring: test zerocopy send
include/linux/io_uring_types.h | 37 ++ include/linux/skbuff.h | 59 +- include/linux/socket.h | 7 + include/uapi/linux/io_uring.h | 43 +- io_uring/Makefile | 2 +- io_uring/io_uring.c | 40 +- io_uring/io_uring.h | 21 + io_uring/net.c | 134 ++++ io_uring/net.h | 4 + io_uring/notif.c | 215 +++++++ io_uring/notif.h | 87 +++ io_uring/opdef.c | 24 +- io_uring/rsrc.c | 55 +- io_uring/rsrc.h | 16 +- io_uring/tctx.h | 26 - net/compat.c | 2 + net/core/datagram.c | 53 +- net/core/skbuff.c | 35 +- net/ipv4/ip_output.c | 63 +- net/ipv4/tcp.c | 52 +- net/ipv6/ip6_output.c | 62 +- net/socket.c | 6 + tools/testing/selftests/net/Makefile | 1 + .../selftests/net/io_uring_zerocopy_tx.c | 605 ++++++++++++++++++ .../selftests/net/io_uring_zerocopy_tx.sh | 131 ++++ 25 files changed, 1652 insertions(+), 128 deletions(-) create mode 100644 io_uring/notif.c create mode 100644 io_uring/notif.h create mode 100644 tools/testing/selftests/net/io_uring_zerocopy_tx.c create mode 100755 tools/testing/selftests/net/io_uring_zerocopy_tx.sh
Signature · 1 line
2.36.1
PATCH v3 1/25 ipv4: avoid partial copy for zc
Even when zerocopy transmission is requested and possible, __ip_append_data() will still copy a small chunk of data just because it allocated some extra linear space (e.g. 148 bytes). It wastes CPU cycles on copy and iter manipulations and also misalignes potentially aligned data. Avoid such coies. And as a bonus we can allocate smaller skb.
--- net/ipv4/ip_output.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-)
net/ipv4/ip_output.c +6 -2
@@ -969,7 +969,6 @@ static int __ip_append_data(struct sock *sk,struct inet_sock *inet =inet_sk (sk);struct ubuf_info *uarg =NULL ;struct sk_buff *skb; -struct ip_options *opt = cork->opt;int hh_len;int exthdrlen;
@@ -977,6 +976,7 @@ static int __ip_append_data(struct sock *sk,int copy;int err;int offset =0 ; +bool zc = false;unsigned int maxfraglen, fragheaderlen, maxnonfragsize;int csummode =CHECKSUM_NONE ;struct rtable *rt = (struct rtable *)cork->dst;
@@ -1025,6 +1025,7 @@ static int __ip_append_data(struct sock *sk,if (rt->dst.dev->features &NETIF_F_SG && csummode ==CHECKSUM_PARTIAL ) { paged = true; + zc = true; }else { uarg->zerocopy =0 ;skb_zcopy_set (skb, uarg, &extra_uref);
@@ -1091,9 +1092,12 @@ static int __ip_append_data(struct sock *sk, (fraglen + alloc_extra <SKB_MAX_ALLOC || !(rt->dst.dev->features & NETIF_F_SG))) alloclen = fraglen; - else { + else if (!zc) { alloclen =min_t (int, fraglen,MAX_HEADER ); pagedlen = fraglen - alloclen; + } else { + alloclen = fragheaderlen + transhdrlen; + pagedlen = datalen - transhdrlen; } alloclen += alloc_extra;
Signature · 1 line
2.36.1
PATCH v3 2/25 ipv6: avoid partial copy for zc
In reply to Pavel BegunkovEven when zerocopy transmission is requested and possible, __ip_append_data() will still copy a small chunk of data just because it allocated some extra linear space (e.g. 128 bytes). It wastes CPU cycles on copy and iter manipulations and also misalignes potentially aligned data. Avoid such coies. And as a bonus we can allocate smaller skb.
--- net/ipv6/ip6_output.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-)
net/ipv6/ip6_output.c +6 -1
@@ -1464,6 +1464,7 @@ static int __ip6_append_data(struct sock *sk,int copy;int err;int offset =0 ; +bool zc = false;u32 tskey =0 ;struct rt6_info *rt = (struct rt6_info *)cork->dst;struct ipv6_txoptions *opt = v6_cork->opt;
@@ -1549,6 +1550,7 @@ static int __ip6_append_data(struct sock *sk,if (rt->dst.dev->features &NETIF_F_SG && csummode ==CHECKSUM_PARTIAL ) { paged = true; + zc = true; }else { uarg->zerocopy =0 ;skb_zcopy_set (skb, uarg, &extra_uref);
@@ -1630,9 +1632,12 @@ static int __ip6_append_data(struct sock *sk, (fraglen + alloc_extra <SKB_MAX_ALLOC || !(rt->dst.dev->features & NETIF_F_SG))) alloclen = fraglen; - else { + else if (!zc) { alloclen =min_t (int, fraglen,MAX_HEADER ); pagedlen = fraglen - alloclen; + } else { + alloclen = fragheaderlen + transhdrlen; + pagedlen = datalen - transhdrlen; } alloclen += alloc_extra;
Signature · 1 line
2.36.1
PATCH v3 3/25 skbuff: add SKBFL_DONT_ORPHAN flag
In reply to Pavel BegunkovWe don't want to list every single ubuf_info callback in skb_orphan_frags(), add a flag controlling the behaviour.
--- include/linux/skbuff.h | 8 +++++--- net/core/skbuff.c | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-)
include/linux/skbuff.h +5 -3
@@ -686,10 +686,13 @@ enum {* charged to the kernel memory. */ SKBFL_PURE_ZEROCOPY =BIT (2 ), + + SKBFL_DONT_ORPHAN = BIT(3), };#define SKBFL_ZEROCOPY_FRAG (SKBFL_ZEROCOPY_ENABLE | SKBFL_SHARED_FRAG) -#define SKBFL_ALL_ZEROCOPY (SKBFL_ZEROCOPY_FRAG | SKBFL_PURE_ZEROCOPY) +#define SKBFL_ALL_ZEROCOPY (SKBFL_ZEROCOPY_FRAG | SKBFL_PURE_ZEROCOPY | \ + SKBFL_DONT_ORPHAN)/* * The callback notifies userspace to release buffers when skb DMA is done in
@@ -3182,8 +3185,7 @@ static inline int skb_orphan_frags(struct sk_buff *skb, gfp_t gfp_mask) {if (likely (!skb_zcopy (skb)))return 0 ; -if (!skb_zcopy_is_nouarg (skb) && -skb_uarg (skb)->callback == msg_zerocopy_callback) +if (skb_shinfo (skb)->flags &SKBFL_DONT_ORPHAN )return 0 ;return skb_copy_ubufs (skb, gfp_mask); }
net/core/skbuff.c +1 -1
@@ -1193,7 +1193,7 @@ static struct ubuf_info *msg_zerocopy_alloc(struct sock *sk, size_t size) uarg->len =1 ; uarg->bytelen = size; uarg->zerocopy =1 ; - uarg->flags =SKBFL_ZEROCOPY_FRAG ; + uarg->flags =SKBFL_ZEROCOPY_FRAG |SKBFL_DONT_ORPHAN ;refcount_set (&uarg->refcnt,1 );sock_hold (sk);
Signature · 1 line
2.36.1
PATCH v3 4/25 skbuff: carry external ubuf_info in msghdr
In reply to Pavel BegunkovMake possible for network in-kernel callers like io_uring to pass in a custom ubuf_info by setting it in a new field of struct msghdr.
--- include/linux/socket.h | 7 +++++++ io_uring/net.c | 4 ++++ net/compat.c | 2 ++ net/socket.c | 6 ++++++ 4 files changed, 19 insertions(+)
include/linux/socket.h +7 -0
@@ -66,9 +66,16 @@ struct msghdr { }; bool msg_control_is_user : 1; bool msg_get_inq : 1;/* return INQ after receive */ +/* +* The data pages are pinned and won't be released before ->msg_ubuf +* is released. ->msg_iter should point to a bvec and ->msg_ubuf has +* to be non-NULL. +*/ + bool msg_managed_data : 1;unsigned int msg_flags;/* flags on received message */ __kernel_size_t msg_controllen;/* ancillary data buffer length */ struct kiocb *msg_iocb;/* ptr to iocb for async requests */ +struct ubuf_info *msg_ubuf; };struct user_msghdr {
io_uring/net.c +4 -0
@@ -255,6 +255,8 @@ int io_send(struct io_kiocb *req, unsigned int issue_flags) msg.msg_control =NULL ; msg.msg_controllen =0 ; msg.msg_namelen =0 ; + msg.msg_ubuf =NULL ; + msg.msg_managed_data = false; flags = sr->msg_flags;if (issue_flags &IO_URING_F_NONBLOCK )
@@ -525,6 +527,8 @@ int io_recv(struct io_kiocb *req, unsigned int issue_flags) msg.msg_flags =0 ; msg.msg_controllen =0 ; msg.msg_iocb =NULL ; + msg.msg_ubuf =NULL ; + msg.msg_managed_data = false; flags = sr->msg_flags;if (force_nonblock)
net/compat.c +2 -0
@@ -80,6 +80,8 @@ int __get_compat_msghdr(struct msghdr *kmsg,return -EMSGSIZE ; kmsg->msg_iocb =NULL ; + kmsg->msg_ubuf =NULL ; + kmsg->msg_managed_data = false; *ptr = msg.msg_iov; *len = msg.msg_iovlen;return 0 ;
net/socket.c +6 -0
@@ -2106,6 +2106,8 @@ int __sys_sendto(int fd, void __user *buff, size_t len, unsigned int flags, msg.msg_control =NULL ; msg.msg_controllen =0 ; msg.msg_namelen =0 ; + msg.msg_ubuf =NULL ; + msg.msg_managed_data = false;if (addr) { err =move_addr_to_kernel (addr, addr_len, &address);if (err <0 )
@@ -2171,6 +2173,8 @@ int __sys_recvfrom(int fd, void __user *ubuf, size_t size, unsigned int flags, msg.msg_namelen =0 ; msg.msg_iocb =NULL ; msg.msg_flags =0 ; + msg.msg_ubuf =NULL ; + msg.msg_managed_data = false;if (sock->file->f_flags &O_NONBLOCK ) flags |=MSG_DONTWAIT ; err =sock_recvmsg (sock, &msg, flags);
@@ -2409,6 +2413,8 @@ int __copy_msghdr_from_user(struct msghdr *kmsg,return -EMSGSIZE ; kmsg->msg_iocb =NULL ; + kmsg->msg_ubuf =NULL ; + kmsg->msg_managed_data = false; *uiov = msg.msg_iov; *nsegs = msg.msg_iovlen;return 0 ;
Signature · 1 line
2.36.1
PATCH v3 5/25 net: bvec specific path in zerocopy_sg_from_iter
In reply to Pavel BegunkovAdd an bvec specialised and optimised path in zerocopy_sg_from_iter. It'll be used later for {get,put}_page() optimisations.
--- net/core/datagram.c | 47 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+)
net/core/datagram.c +47 -0
@@ -613,11 +613,58 @@ int skb_copy_datagram_from_iter(struct sk_buff *skb, int offset, }EXPORT_SYMBOL (skb_copy_datagram_from_iter); +static int __zerocopy_sg_from_bvec (struct sock *sk,struct sk_buff *skb, +struct iov_iter *from,size_t length) +{ +int frag =skb_shinfo (skb)->nr_frags; +int ret =0 ; +struct bvec_iter bi; +ssize_t copied =0 ; +unsigned long truesize =0 ; + + bi.bi_size =min (from->count, length); + bi.bi_bvec_done = from->iov_offset; + bi.bi_idx =0 ; + +while (bi.bi_size && frag <MAX_SKB_FRAGS ) { +struct bio_vec v =mp_bvec_iter_bvec (from->bvec, bi); + + copied += v.bv_len; + truesize +=PAGE_ALIGN (v.bv_len + v.bv_offset); +get_page (v.bv_page); +skb_fill_page_desc (skb, frag++, v.bv_page, v.bv_offset, v.bv_len); +bvec_iter_advance_single (from->bvec, &bi, v.bv_len); + } +if (bi.bi_size) + ret = -EMSGSIZE ; + + from->bvec += bi.bi_idx; + from->nr_segs -= bi.bi_idx; + from->count = bi.bi_size; + from->iov_offset = bi.bi_bvec_done; + + skb->data_len += copied; + skb->len += copied; + skb->truesize += truesize; + +if (sk && sk->sk_type ==SOCK_STREAM ) { +sk_wmem_queued_add (sk, truesize); +if (!skb_zcopy_pure (skb)) +sk_mem_charge (sk, truesize); + }else { +refcount_add (truesize, &skb->sk->sk_wmem_alloc); + } +return ret; +} +int __zerocopy_sg_from_iter (struct sock *sk,struct sk_buff *skb,struct iov_iter *from,size_t length) {int frag =skb_shinfo (skb)->nr_frags; +if (iov_iter_is_bvec (from)) +return __zerocopy_sg_from_bvec (sk, skb, from, length); +while (length &&iov_iter_count (from)) {struct page *pages[MAX_SKB_FRAGS ];struct page *last_head =NULL ;
Signature · 1 line
2.36.1
PATCH v3 6/25 net: optimise bvec-based zc page referencing
In reply to Pavel BegunkovSome users like io_uring can pass a bvec iterator to send and also can implement page pinning more efficiently. Add a ->msg_managed_data toogle in msghdr. When set, data pages are "managed" by upper layers, i.e. refcounted and pinned by the caller and will live at least until ->msg_ubuf is released. msghdr has to have non-NULL ->msg_ubuf and ->msg_iter should point to a bvec.
Protocols supporting the feature will propagate it by setting SKBFL_MANAGED_FRAG_REFS, which means that the skb doesn't hold refs to its frag pages and only rely on ubuf_info lifetime gurantees. It should only be used with zerocopy skbs with ubuf_info set.
It's allowed to convert skbs from managed to normal by calling skb_zcopy_downgrade_managed(). The function will take all needed page references and clear the flag.
--- include/linux/skbuff.h | 25 +++++++++++++++++++++++-- net/core/datagram.c | 7 ++++--- net/core/skbuff.c | 29 +++++++++++++++++++++++++++-- 3 files changed, 54 insertions(+), 7 deletions(-)
include/linux/skbuff.h +23 -2
@@ -688,11 +688,16 @@ enum {SKBFL_PURE_ZEROCOPY =BIT (2 ),SKBFL_DONT_ORPHAN =BIT (3 ), + +/* page references are managed by the ubuf_info, so it's safe to +* use frags only up until ubuf_info is released +*/ + SKBFL_MANAGED_FRAG_REFS = BIT(4), };#define SKBFL_ZEROCOPY_FRAG (SKBFL_ZEROCOPY_ENABLE | SKBFL_SHARED_FRAG)#define SKBFL_ALL_ZEROCOPY (SKBFL_ZEROCOPY_FRAG | SKBFL_PURE_ZEROCOPY | \ - SKBFL_DONT_ORPHAN) + SKBFL_DONT_ORPHAN | SKBFL_MANAGED_FRAG_REFS)/* * The callback notifies userspace to release buffers when skb DMA is done in
@@ -1809,6 +1814,11 @@ static inline bool skb_zcopy_pure(const struct sk_buff *skb)return skb_shinfo (skb)->flags &SKBFL_PURE_ZEROCOPY ; } +static inline bool skb_zcopy_managed (const struct sk_buff *skb) +{ +return skb_shinfo (skb)->flags &SKBFL_MANAGED_FRAG_REFS ; +} +static inline bool skb_pure_zcopy_same (const struct sk_buff *skb1,const struct sk_buff *skb2) {
@@ -1883,6 +1893,14 @@ static inline void skb_zcopy_clear(struct sk_buff *skb, bool zerocopy_success) } } +void __skb_zcopy_downgrade_managed (struct sk_buff *skb); + +static inline void skb_zcopy_downgrade_managed (struct sk_buff *skb) +{ +if (unlikely (skb_zcopy_managed (skb))) +__skb_zcopy_downgrade_managed (skb); +} +static inline void skb_mark_not_on_list (struct sk_buff *skb) { skb->next =NULL ;
@@ -3498,7 +3516,10 @@ static inline void __skb_frag_unref(skb_frag_t *frag, bool recycle)*/ static inline void skb_frag_unref (struct sk_buff *skb,int f) { -__skb_frag_unref (&skb_shinfo (skb)->frags[f], skb->pp_recycle); +struct skb_shared_info *shinfo =skb_shinfo (skb); + +if (!skb_zcopy_managed (skb)) +__skb_frag_unref (&shinfo->frags[f], skb->pp_recycle); }/**
net/core/datagram.c +4 -3
@@ -631,7 +631,6 @@ static int __zerocopy_sg_from_bvec(struct sock *sk, struct sk_buff *skb, copied += v.bv_len; truesize +=PAGE_ALIGN (v.bv_len + v.bv_offset); -get_page (v.bv_page);skb_fill_page_desc (skb, frag++, v.bv_page, v.bv_offset, v.bv_len);bvec_iter_advance_single (from->bvec, &bi, v.bv_len); }
@@ -660,11 +659,13 @@ static int __zerocopy_sg_from_bvec(struct sock *sk, struct sk_buff *skb,int __zerocopy_sg_from_iter (struct sock *sk,struct sk_buff *skb,struct iov_iter *from,size_t length) { -int frag =skb_shinfo (skb)->nr_frags; +int frag; -if (iov_iter_is_bvec (from)) +if (skb_zcopy_managed (skb))return __zerocopy_sg_from_bvec (sk, skb, from, length); + frag =skb_shinfo (skb)->nr_frags; +while (length &&iov_iter_count (from)) {struct page *pages[MAX_SKB_FRAGS ];struct page *last_head =NULL ;
net/core/skbuff.c +27 -2
@@ -666,11 +666,18 @@ static void skb_release_data(struct sk_buff *skb) &shinfo->dataref)) goto exit; -skb_zcopy_clear (skb, true); +if (skb_zcopy (skb)) { +bool skip_unref = shinfo->flags &SKBFL_MANAGED_FRAG_REFS ; + +skb_zcopy_clear (skb, true); +if (skip_unref) + goto free_head; + }for (i =0 ; i < shinfo->nr_frags; i++)__skb_frag_unref (&shinfo->frags[i], skb->pp_recycle); +free_head:if (shinfo->frag_list)kfree_skb_list (shinfo->frag_list);
@@ -895,7 +902,10 @@ EXPORT_SYMBOL(skb_dump);*/ void skb_tx_error (struct sk_buff *skb) { -skb_zcopy_clear (skb, true); +if (skb) { +skb_zcopy_downgrade_managed (skb); +skb_zcopy_clear (skb, true); + } }EXPORT_SYMBOL (skb_tx_error);
@@ -1371,6 +1381,16 @@ int skb_zerocopy_iter_stream(struct sock *sk, struct sk_buff *skb, }EXPORT_SYMBOL_GPL (skb_zerocopy_iter_stream); +void __skb_zcopy_downgrade_managed (struct sk_buff *skb) +{ +int i; + +skb_shinfo (skb)->flags &= ~SKBFL_MANAGED_FRAG_REFS ; +for (i =0 ; i <skb_shinfo (skb)->nr_frags; i++) +skb_frag_ref (skb, i); +} +EXPORT_SYMBOL_GPL (__skb_zcopy_downgrade_managed); +static int skb_zerocopy_clone (struct sk_buff *nskb,struct sk_buff *orig,gfp_t gfp_mask) {
@@ -1688,6 +1708,8 @@ int pskb_expand_head(struct sk_buff *skb, int nhead, int ntail,BUG_ON (skb_shared (skb)); +skb_zcopy_downgrade_managed (skb); + size =SKB_DATA_ALIGN (size);if (skb_pfmemalloc (skb))
@@ -3484,6 +3506,8 @@ void skb_split(struct sk_buff *skb, struct sk_buff *skb1, const u32 len)int pos =skb_headlen (skb);const int zc_flags =SKBFL_SHARED_FRAG |SKBFL_PURE_ZEROCOPY ; +skb_zcopy_downgrade_managed (skb); +skb_shinfo (skb1)->flags |=skb_shinfo (skb)->flags & zc_flags;skb_zerocopy_clone (skb1, skb,0 );if (len < pos)/* Split line is inside header. */
@@ -3837,6 +3861,7 @@ int skb_append_pagefrags(struct sk_buff *skb, struct page *page,if (skb_can_coalesce (skb, i, page, offset)) {skb_frag_size_add (&skb_shinfo (skb)->frags[i -1 ], size); }else if (i <MAX_SKB_FRAGS ) { +skb_zcopy_downgrade_managed (skb);get_page (page);skb_fill_page_desc (skb, i, page, offset, size); }else {
Signature · 1 line
2.36.1
PATCH v3 7/25 net: don't track pfmemalloc for managed frags
In reply to Pavel BegunkovManaged pages contain pinned userspace pages and controlled by upper layers, there is no need in tracking skb->pfmemalloc for them.
--- include/linux/skbuff.h | 28 +++++++++++++++++----------- net/core/datagram.c | 7 +++++-- 2 files changed, 22 insertions(+), 13 deletions(-)
include/linux/skbuff.h +17 -11
@@ -2549,6 +2549,22 @@ static inline unsigned int skb_pagelen(const struct sk_buff *skb)return skb_headlen (skb) +__skb_pagelen (skb); } +static inline void __skb_fill_page_desc_noacc (struct skb_shared_info *shinfo, +int i,struct page *page, +int off,int size) +{ +skb_frag_t *frag = &shinfo->frags[i]; + +/* +* Propagate page pfmemalloc to the skb if we can. The problem is +* that not all callers have unique ownership of the page but rely +* on page_is_pfmemalloc doing the right thing(tm). +*/ + frag->bv_page = page; + frag->bv_offset = off; +skb_frag_size_set (frag, size); +} +/** * __skb_fill_page_desc - initialise a paged fragment in an skb * @skb: buffer containing fragment to be initialised
@@ -2565,17 +2581,7 @@ static inline unsigned int skb_pagelen(const struct sk_buff *skb)static inline void __skb_fill_page_desc (struct sk_buff *skb,int i,struct page *page,int off,int size) { -skb_frag_t *frag = &skb_shinfo (skb)->frags[i]; - -/* -* Propagate page pfmemalloc to the skb if we can. The problem is -* that not all callers have unique ownership of the page but rely -* on page_is_pfmemalloc doing the right thing(tm). -*/ - frag->bv_page = page; - frag->bv_offset = off; -skb_frag_size_set (frag, size); - +__skb_fill_page_desc_noacc (skb_shinfo (skb), i, page, off, size); page =compound_head (page);if (page_is_pfmemalloc (page)) skb->pfmemalloc = true;
net/core/datagram.c +5 -2
@@ -616,7 +616,8 @@ EXPORT_SYMBOL(skb_copy_datagram_from_iter);static int __zerocopy_sg_from_bvec (struct sock *sk,struct sk_buff *skb,struct iov_iter *from,size_t length) { -int frag =skb_shinfo (skb)->nr_frags; +struct skb_shared_info *shinfo =skb_shinfo (skb); +int frag = shinfo->nr_frags;int ret =0 ;struct bvec_iter bi;ssize_t copied =0 ;
@@ -631,12 +632,14 @@ static int __zerocopy_sg_from_bvec(struct sock *sk, struct sk_buff *skb, copied += v.bv_len; truesize +=PAGE_ALIGN (v.bv_len + v.bv_offset); -skb_fill_page_desc (skb, frag++, v.bv_page, v.bv_offset, v.bv_len); +__skb_fill_page_desc_noacc (shinfo, frag++, v.bv_page, + v.bv_offset, v.bv_len);bvec_iter_advance_single (from->bvec, &bi, v.bv_len); }if (bi.bi_size) ret = -EMSGSIZE ; + shinfo->nr_frags = frag; from->bvec += bi.bi_idx; from->nr_segs -= bi.bi_idx; from->count = bi.bi_size;
Signature · 1 line
2.36.1
PATCH v3 8/25 skbuff: don't mix ubuf_info of different types
In reply to Pavel BegunkovWe should not append MSG_ZEROCOPY requests to skbuff with non MSG_ZEROCOPY ubuf_info, they are not compatible.
--- net/core/skbuff.c | 4 ++++ 1 file changed, 4 insertions(+)
net/core/skbuff.c +4 -0
@@ -1222,6 +1222,10 @@ struct ubuf_info *msg_zerocopy_realloc(struct sock *sk, size_t size,const u32 byte_limit =1 <<19 ;/* limit to a few TSO */ u32 bytelen, next; +/* there might be non MSG_ZEROCOPY users */ +if (uarg->callback != msg_zerocopy_callback) +return NULL ; +/* realloc only when socket is locked (TCP, UDP cork), * so uarg->len and sk_zckey access is serialized */
Signature · 1 line
2.36.1
PATCH v3 9/25 ipv4/udp: support zc with managed data
In reply to Pavel BegunkovTeach ipv4/udp about managed data. Make it recognise and use msg->msg_ubuf, and also set/propagate SKBFL_MANAGED_FRAG_REFS down to skb_zerocopy_iter_dgram().
--- net/ipv4/ip_output.c | 57 +++++++++++++++++++++++++++++++++----------- 1 file changed, 43 insertions(+), 14 deletions(-)
net/ipv4/ip_output.c +43 -14
@@ -1017,18 +1017,35 @@ static int __ip_append_data(struct sock *sk, (!exthdrlen || (rt->dst.dev->features & NETIF_F_HW_ESP_TX_CSUM))) csummode =CHECKSUM_PARTIAL ; -if (flags &MSG_ZEROCOPY && length &&sock_flag (sk,SOCK_ZEROCOPY )) { - uarg =msg_zerocopy_realloc (sk, length,skb_zcopy (skb)); -if (!uarg) -return -ENOBUFS ; - extra_uref = !skb_zcopy (skb);/* only ref on new uarg */ -if (rt->dst.dev->features &NETIF_F_SG && - csummode ==CHECKSUM_PARTIAL ) { - paged = true; - zc = true; - }else { - uarg->zerocopy =0 ; -skb_zcopy_set (skb, uarg, &extra_uref); +if ((flags &MSG_ZEROCOPY ) && length) { +struct msghdr *msg = from; + +if (getfrag == ip_generic_getfrag && msg->msg_ubuf) { +if (skb_zcopy (skb) && msg->msg_ubuf !=skb_zcopy (skb)) +return -EINVAL ; + +/* Leave uarg NULL if can't zerocopy, callers should +* be able to handle it. +*/ +if ((rt->dst.dev->features &NETIF_F_SG ) && + csummode ==CHECKSUM_PARTIAL ) { + paged = true; + zc = true; + uarg = msg->msg_ubuf; + } + }else if (sock_flag (sk,SOCK_ZEROCOPY )) { + uarg =msg_zerocopy_realloc (sk, length,skb_zcopy (skb)); +if (!uarg) +return -ENOBUFS ; + extra_uref = !skb_zcopy (skb);/* only ref on new uarg */ +if (rt->dst.dev->features &NETIF_F_SG && + csummode ==CHECKSUM_PARTIAL ) { + paged = true; + zc = true; + }else { + uarg->zerocopy =0 ; +skb_zcopy_set (skb, uarg, &extra_uref); + } } }
@@ -1192,13 +1209,14 @@ static int __ip_append_data(struct sock *sk, err = -EFAULT ; goto error; } - }else if (!uarg || !uarg->zerocopy) { + }else if (!zc) {int i =skb_shinfo (skb)->nr_frags; err = -ENOMEM ;if (!sk_page_frag_refill (sk, pfrag)) goto error; +skb_zcopy_downgrade_managed (skb);if (!skb_can_coalesce (skb, i, pfrag->page, pfrag->offset)) { err = -EMSGSIZE ;
@@ -1223,7 +1241,18 @@ static int __ip_append_data(struct sock *sk, skb->truesize += copy; wmem_alloc_delta += copy; }else { - err =skb_zerocopy_iter_dgram (skb, from, copy); +struct msghdr *msg = from; + +if (!skb_shinfo (skb)->nr_frags) { +if (msg->msg_managed_data) +skb_shinfo (skb)->flags |=SKBFL_MANAGED_FRAG_REFS ; + }else { +/* appending, don't mix managed and unmanaged */ +if (!msg->msg_managed_data) +skb_zcopy_downgrade_managed (skb); + } + + err =skb_zerocopy_iter_dgram (skb, msg, copy);if (err <0 ) goto error; }
Signature · 1 line
2.36.1
PATCH v3 10/25 ipv6/udp: support zc with managed data
In reply to Pavel BegunkovJust as with ipv4/udp make ipv6/udp to take advantage of managed data and propagate SKBFL_MANAGED_FRAG_REFS to skb_zerocopy_iter_dgram().
--- net/ipv6/ip6_output.c | 57 ++++++++++++++++++++++++++++++++----------- 1 file changed, 43 insertions(+), 14 deletions(-)
net/ipv6/ip6_output.c +43 -14
@@ -1542,18 +1542,35 @@ static int __ip6_append_data(struct sock *sk, rt->dst.dev->features & (NETIF_F_IPV6_CSUM | NETIF_F_HW_CSUM)) csummode =CHECKSUM_PARTIAL ; -if (flags &MSG_ZEROCOPY && length &&sock_flag (sk,SOCK_ZEROCOPY )) { - uarg =msg_zerocopy_realloc (sk, length,skb_zcopy (skb)); -if (!uarg) -return -ENOBUFS ; - extra_uref = !skb_zcopy (skb);/* only ref on new uarg */ -if (rt->dst.dev->features &NETIF_F_SG && - csummode ==CHECKSUM_PARTIAL ) { - paged = true; - zc = true; - }else { - uarg->zerocopy =0 ; -skb_zcopy_set (skb, uarg, &extra_uref); +if ((flags &MSG_ZEROCOPY ) && length) { +struct msghdr *msg = from; + +if (getfrag == ip_generic_getfrag && msg->msg_ubuf) { +if (skb_zcopy (skb) && msg->msg_ubuf !=skb_zcopy (skb)) +return -EINVAL ; + +/* Leave uarg NULL if can't zerocopy, callers should +* be able to handle it. +*/ +if ((rt->dst.dev->features &NETIF_F_SG ) && + csummode ==CHECKSUM_PARTIAL ) { + paged = true; + zc = true; + uarg = msg->msg_ubuf; + } + }else if (sock_flag (sk,SOCK_ZEROCOPY )) { + uarg =msg_zerocopy_realloc (sk, length,skb_zcopy (skb)); +if (!uarg) +return -ENOBUFS ; + extra_uref = !skb_zcopy (skb);/* only ref on new uarg */ +if (rt->dst.dev->features &NETIF_F_SG && + csummode ==CHECKSUM_PARTIAL ) { + paged = true; + zc = true; + }else { + uarg->zerocopy =0 ; +skb_zcopy_set (skb, uarg, &extra_uref); + } } }
@@ -1747,13 +1764,14 @@ static int __ip6_append_data(struct sock *sk, err = -EFAULT ; goto error; } - }else if (!uarg || !uarg->zerocopy) { + }else if (!zc) {int i =skb_shinfo (skb)->nr_frags; err = -ENOMEM ;if (!sk_page_frag_refill (sk, pfrag)) goto error; +skb_zcopy_downgrade_managed (skb);if (!skb_can_coalesce (skb, i, pfrag->page, pfrag->offset)) { err = -EMSGSIZE ;
@@ -1778,7 +1796,18 @@ static int __ip6_append_data(struct sock *sk, skb->truesize += copy; wmem_alloc_delta += copy; }else { - err =skb_zerocopy_iter_dgram (skb, from, copy); +struct msghdr *msg = from; + +if (!skb_shinfo (skb)->nr_frags) { +if (msg->msg_managed_data) +skb_shinfo (skb)->flags |=SKBFL_MANAGED_FRAG_REFS ; + }else { +/* appending, don't mix managed and unmanaged */ +if (!msg->msg_managed_data) +skb_zcopy_downgrade_managed (skb); + } + + err =skb_zerocopy_iter_dgram (skb, msg, copy);if (err <0 ) goto error; }
Signature · 1 line
2.36.1
PATCH v3 11/25 tcp: support zc with managed data
In reply to Pavel BegunkovAlso make tcp to use managed data and propagate SKBFL_MANAGED_FRAG_REFS to optimise frag pages referencing.
--- net/ipv4/tcp.c | 52 ++++++++++++++++++++++++++++++++------------------ 1 file changed, 33 insertions(+), 19 deletions(-)
net/ipv4/tcp.c +33 -19
@@ -1223,17 +1223,23 @@ int tcp_sendmsg_locked(struct sock *sk, struct msghdr *msg, size_t size) flags = msg->msg_flags; -if (flags &MSG_ZEROCOPY && size &&sock_flag (sk,SOCK_ZEROCOPY )) { +if ((flags &MSG_ZEROCOPY ) && size) { skb =tcp_write_queue_tail (sk); - uarg =msg_zerocopy_realloc (sk, size,skb_zcopy (skb)); -if (!uarg) { - err = -ENOBUFS ; - goto out_err; - } - zc = sk->sk_route_caps &NETIF_F_SG ; -if (!zc) - uarg->zerocopy =0 ; +if (msg->msg_ubuf) { + uarg = msg->msg_ubuf; +net_zcopy_get (uarg); + zc = sk->sk_route_caps &NETIF_F_SG ; + }else if (sock_flag (sk,SOCK_ZEROCOPY )) { + uarg =msg_zerocopy_realloc (sk, size,skb_zcopy (skb)); +if (!uarg) { + err = -ENOBUFS ; + goto out_err; + } + zc = sk->sk_route_caps &NETIF_F_SG ; +if (!zc) + uarg->zerocopy =0 ; + } } if (unlikely(flags & MSG_FASTOPEN || inet_sk(sk)->defer_connect) &&
@@ -1356,9 +1362,11 @@ int tcp_sendmsg_locked(struct sock *sk, struct msghdr *msg, size_t size) copy =min_t (int, copy, pfrag->size - pfrag->offset); -if (tcp_downgrade_zcopy_pure (sk, skb)) - goto wait_for_space; - +if (unlikely (skb_zcopy_pure (skb) ||skb_zcopy_managed (skb))) { +if (tcp_downgrade_zcopy_pure (sk, skb)) + goto wait_for_space; +skb_zcopy_downgrade_managed (skb); + } copy =tcp_wmem_schedule (sk, copy);if (!copy) goto wait_for_space;
@@ -1381,15 +1389,21 @@ int tcp_sendmsg_locked(struct sock *sk, struct msghdr *msg, size_t size) pfrag->offset += copy; }else {/* First append to a fragless skb builds initial -* pure zerocopy skb +* zerocopy skb */ -if (!skb->len) +if (!skb->len) { +if (msg->msg_managed_data) +skb_shinfo (skb)->flags |=SKBFL_MANAGED_FRAG_REFS ;skb_shinfo (skb)->flags |=SKBFL_PURE_ZEROCOPY ; - -if (!skb_zcopy_pure (skb)) { - copy =tcp_wmem_schedule (sk, copy); -if (!copy) - goto wait_for_space; + }else { +/* appending, don't mix managed and unmanaged */ +if (!msg->msg_managed_data) +skb_zcopy_downgrade_managed (skb); +if (!skb_zcopy_pure (skb)) { + copy =tcp_wmem_schedule (sk, copy); +if (!copy) + goto wait_for_space; + } } err =skb_zerocopy_iter_stream (sk, skb, msg, copy, uarg);
Signature · 1 line
2.36.1
PATCH v3 12/25 io_uring: add zc notification infrastructure
In reply to Pavel BegunkovAdd internal part of send zerocopy notifications. There are two main structures, the first one is struct io_notif, which carries inside struct ubuf_info and maps 1:1 to it. io_uring will be binding a number of zerocopy send requests to it and ask to complete (aka flush) it. When flushed and all attached requests and skbs complete, it'll generate one and only one CQE. There are intended to be passed into the network layer as struct msghdr::msg_ubuf.
The second concept is notification slots. The userspace will be able to register an array of slots and subsequently addressing them by the index in the array. Slots are independent of each other. Each slot can have only one notifier at a time (called active notifier) but many notifiers during the lifetime. When active, a notifier not going to post any completion but the userspace can attach requests to it by specifying the corresponding slot while issueing send zc requests. Eventually, the userspace will want to "flush" the notifier losing any way to attach new requests to it, however it can use the next atomatically added notifier of this slot or of any other slot.
When the network layer is done with all enqueued skbs attached to a notifier and doesn't need the specified in them user data, the flushed notifier will post a CQE.
--- include/linux/io_uring_types.h | 5 ++ io_uring/Makefile | 2 +- io_uring/io_uring.c | 6 +- io_uring/io_uring.h | 1 + io_uring/notif.c | 102 +++++++++++++++++++++++++++++++++ io_uring/notif.h | 64 +++++++++++++++++++++ 6 files changed, 177 insertions(+), 3 deletions(-) create mode 100644 io_uring/notif.c create mode 100644 io_uring/notif.h
include/linux/io_uring_types.h +5 -0
@@ -33,6 +33,9 @@ struct io_file_table {unsigned int alloc_hint; }; +struct io_notif ; +struct io_notif_slot ; +struct io_hash_bucket {spinlock_t lock;struct hlist_head list;
@@ -207,6 +210,8 @@ struct io_ring_ctx {unsigned nr_user_files;unsigned nr_user_bufs;struct io_mapped_ubuf **user_bufs; +struct io_notif_slot *notif_slots; +unsigned nr_notif_slots;struct io_submit_state submit_state;
io_uring/Makefile +1 -1
@@ -7,5 +7,5 @@ obj-$(CONFIG_IO_URING) += io_uring.o xattr.o nop.o fs.o splice.o \ openclose.o uring_cmd.o epoll.o \ statx.o net.o msg_ring.o timeout.o \ sqpoll.o fdinfo.o tctx.o poll.o \ - cancel.o kbuf.o rsrc.o rw.o opdef.o + cancel.o kbuf.o rsrc.o rw.o opdef.o notif.o obj-$(CONFIG_IO_WQ) += io-wq.o
io_uring/io_uring.c +4 -2
@@ -89,6 +89,7 @@#include "kbuf.h" #include "rsrc.h" #include "cancel.h" +#include "notif.h" #include "timeout.h" #include "poll.h"
@@ -735,8 +736,7 @@ struct io_uring_cqe *__io_get_cqe(struct io_ring_ctx *ctx)return &rings->cqes[off]; } -static bool io_fill_cqe_aux (struct io_ring_ctx *ctx, -u64 user_data,s32 res,u32 cflags) +bool io_fill_cqe_aux (struct io_ring_ctx *ctx,u64 user_data,s32 res,u32 cflags) {struct io_uring_cqe *cqe;
@@ -2498,6 +2498,7 @@ static __cold void io_ring_ctx_free(struct io_ring_ctx *ctx) }#endif WARN_ON_ONCE (!list_empty (&ctx->ltimeout_list)); +WARN_ON_ONCE (ctx->notif_slots || ctx->nr_notif_slots);io_mem_free (ctx->rings);io_mem_free (ctx->sq_sqes);
@@ -2674,6 +2675,7 @@ static __cold void io_ring_ctx_wait_and_kill(struct io_ring_ctx *ctx)io_unregister_personality (ctx, index);if (ctx->rings)io_poll_remove_all (ctx,NULL , true); +io_notif_unregister (ctx);mutex_unlock (&ctx->uring_lock);/* failed during ring init, it couldn't have issued any requests */
io_uring/io_uring.h +1 -0
@@ -24,6 +24,7 @@ void io_req_complete_failed(struct io_kiocb *req, s32 res);void __io_req_complete (struct io_kiocb *req,unsigned issue_flags);void io_req_complete_post (struct io_kiocb *req);void __io_req_complete_post (struct io_kiocb *req); +bool io_fill_cqe_aux (struct io_ring_ctx *ctx,u64 user_data,s32 res,u32 cflags);bool io_post_aux_cqe (struct io_ring_ctx *ctx,u64 user_data,s32 res,u32 cflags);void __io_commit_cqring_flush (struct io_ring_ctx *ctx);
io_uring/notif.c new file +102 -0
103 lines not on this page · raw message
io_uring/notif.h new file +64 -0
64 lines not on this page · raw message
Signature · 1 line
2.36.1
PATCH v3 13/25 io_uring: export task put
In reply to Pavel Begunkov--- include/linux/io_uring_types.h | 25 +++++++++++++++++++++++++ io_uring/io_uring.c | 11 +---------- io_uring/io_uring.h | 10 ++++++++++ io_uring/tctx.h | 26 -------------------------- 4 files changed, 36 insertions(+), 36 deletions(-)
include/linux/io_uring_types.h +25 -0
@@ -4,6 +4,7 @@#include <linux/blkdev.h> #include <linux/task_work.h> #include <linux/bitmap.h> +#include <linux/llist.h> #include <uapi/linux/io_uring.h> struct io_wq_work_node {
@@ -46,6 +47,30 @@ struct io_hash_table {unsigned hash_bits; }; +/* +* Arbitrary limit, can be raised if need be +*/ +#define IO_RINGFD_REG_MAX 16 + +struct io_uring_task { +/* submission side */ +int cached_refs; +const struct io_ring_ctx *last; +struct io_wq *io_wq; +struct file *registered_rings[IO_RINGFD_REG_MAX ]; + +struct xarray xa; +struct wait_queue_head wait; +atomic_t in_idle; +atomic_t inflight_tracked; +struct percpu_counter inflight; + +struct {/* task_work */ +struct llist_head task_list; +struct callback_head task_work; + }____cacheline_aligned_in_smp ; +}; +struct io_uring {u32 head____cacheline_aligned_in_smp ;u32 tail____cacheline_aligned_in_smp ;
io_uring/io_uring.c +1 -10
@@ -603,7 +603,7 @@ static bool io_cqring_overflow_flush(struct io_ring_ctx *ctx)return ret; } -static void __io_put_task (struct task_struct *task,int nr) +void __io_put_task (struct task_struct *task,int nr) {struct io_uring_task *tctx = task->io_uring;
@@ -613,15 +613,6 @@ static void __io_put_task(struct task_struct *task, int nr)put_task_struct_many (task, nr); } -/* must to be called somewhat shortly after putting a request */ -static inline void io_put_task (struct task_struct *task,int nr) -{ -if (likely (task == current)) - task->io_uring->cached_refs += nr; -else -__io_put_task (task, nr); -} -static void io_task_refs_refill (struct io_uring_task *tctx) {unsigned int refill = -tctx->cached_refs +IO_TCTX_REFS_CACHE_NR ;
io_uring/io_uring.h +10 -0
@@ -59,6 +59,7 @@ void io_wq_submit_work(struct io_wq_work *work);void io_free_req (struct io_kiocb *req);void io_queue_next (struct io_kiocb *req); +void __io_put_task (struct task_struct *task,int nr);bool io_match_task_safe (struct io_kiocb *head,struct task_struct *task,bool cancel_all);
@@ -244,4 +245,13 @@ static inline void io_commit_cqring_flush(struct io_ring_ctx *ctx) __io_commit_cqring_flush(ctx); } +/* must to be called somewhat shortly after putting a request */ +static inline void io_put_task (struct task_struct *task,int nr) +{ +if (likely (task == current)) + task->io_uring->cached_refs += nr; +else +__io_put_task (task, nr); +} +#endif
io_uring/tctx.h +0 -26
@@ -1,31 +1,5 @@// SPDX-License-Identifier: GPL-2.0 -#include <linux/llist.h> - -/* -* Arbitrary limit, can be raised if need be -*/ -#define IO_RINGFD_REG_MAX 16 - -struct io_uring_task { -/* submission side */ -int cached_refs; -const struct io_ring_ctx *last; -struct io_wq *io_wq; -struct file *registered_rings[IO_RINGFD_REG_MAX ]; - -struct xarray xa; -struct wait_queue_head wait; -atomic_t in_idle; -atomic_t inflight_tracked; -struct percpu_counter inflight; - -struct {/* task_work */ -struct llist_head task_list; -struct callback_head task_work; - }____cacheline_aligned_in_smp ; -}; -struct io_tctx_node {struct list_head ctx_node;struct task_struct *task;
Signature · 1 line
2.36.1
PATCH v3 14/25 io_uring: cache struct io_notif
In reply to Pavel Begunkovkmalloc'ing struct io_notif is too expensive when done frequently, cache them as many other resources in io_uring. Keep two list, the first one is from where we're getting notifiers, it's protected by ->uring_lock. The second is protected by ->completion_lock, to which we queue released notifiers. Then we splice one list into another when needed.
--- include/linux/io_uring_types.h | 7 +++++ io_uring/io_uring.c | 3 ++ io_uring/notif.c | 57 +++++++++++++++++++++++++++++----- io_uring/notif.h | 5 +++ 4 files changed, 65 insertions(+), 7 deletions(-)
include/linux/io_uring_types.h +7 -0
@@ -244,6 +244,9 @@ struct io_ring_ctx {struct xarray io_bl_xa;struct list_head io_buffers_cache; +/* struct io_notif cache, protected by uring_lock */ +struct list_head notif_list; +struct io_hash_table cancel_table_locked;struct list_head cq_overflow_list;struct list_head apoll_cache;
@@ -255,6 +258,10 @@ struct io_ring_ctx {struct io_wq_work_list locked_free_list;unsigned int locked_free_nr; +/* struct io_notif cache protected by completion_lock */ +struct list_head notif_list_locked; +unsigned int notif_locked_nr; +const struct cred *sq_creds;/* cred used for __io_sq_thread() */ struct io_sq_data *sq_data;/* if using sq thread polling */
io_uring/io_uring.c +3 -0
@@ -318,6 +318,8 @@ static __cold struct io_ring_ctx *io_ring_ctx_alloc(struct io_uring_params *p)INIT_WQ_LIST (&ctx->locked_free_list);INIT_DELAYED_WORK (&ctx->fallback_work, io_fallback_req_func);INIT_WQ_LIST (&ctx->submit_state.compl_reqs); +INIT_LIST_HEAD (&ctx->notif_list); +INIT_LIST_HEAD (&ctx->notif_list_locked);return ctx; err:kfree (ctx->dummy_ubuf);
@@ -2491,6 +2493,7 @@ static __cold void io_ring_ctx_free(struct io_ring_ctx *ctx)WARN_ON_ONCE (!list_empty (&ctx->ltimeout_list));WARN_ON_ONCE (ctx->notif_slots || ctx->nr_notif_slots); +io_notif_cache_purge (ctx);io_mem_free (ctx->rings);io_mem_free (ctx->sq_sqes);
io_uring/notif.c +50 -7
81 lines not on this page · raw message
io_uring/notif.h +5 -0
@@ -5,6 +5,8 @@#include <net/sock.h> #include <linux/nospec.h> +#define IO_NOTIF_SPLICE_BATCH 32 +struct io_notif {struct ubuf_info uarg;struct io_ring_ctx *ctx;
@@ -13,6 +15,8 @@ struct io_notif {u64 tag;/* see struct io_notif_slot::seq */ u32 seq; +/* hook into ctx->notif_list and ctx->notif_list_locked */ +struct list_head cache_node;union {struct callback_head task_work;
@@ -41,6 +45,7 @@ struct io_notif_slot { };int io_notif_unregister (struct io_ring_ctx *ctx); +void io_notif_cache_purge (struct io_ring_ctx *ctx);struct io_notif *io_alloc_notif (struct io_ring_ctx *ctx,struct io_notif_slot *slot);
Signature · 1 line
2.36.1
PATCH v3 15/25 io_uring: complete notifiers in tw
In reply to Pavel BegunkovWe need a task context to post CQEs but using wq is too expensive. Try to complete notifiers using task_work and fall back to wq if fails.
--- io_uring/notif.c | 22 +++++++++++++++++++--- io_uring/notif.h | 3 +++ 2 files changed, 22 insertions(+), 3 deletions(-)
io_uring/notif.c +19 -3
@@ -13,6 +13,11 @@ static void __io_notif_complete_tw(struct callback_head *cb) struct io_notif *notif = container_of(cb, struct io_notif, task_work);struct io_ring_ctx *ctx = notif->ctx; +if (likely (notif->task)) { +io_put_task (notif->task,1 ); + notif->task =NULL ; + } +io_cq_lock (ctx);io_fill_cqe_aux (ctx, notif->tag,0 , notif->seq);
@@ -43,6 +48,14 @@ static void io_uring_tx_zerocopy_callback(struct sk_buff *skb,if (!refcount_dec_and_test (&uarg->refcnt))return ; + +if (likely (notif->task)) { +init_task_work (¬if->task_work, __io_notif_complete_tw); +if (likely (!task_work_add (notif->task, ¬if->task_work, +TWA_SIGNAL ))) +return ; + } +INIT_WORK (¬if->commit_work, io_notif_complete_wq);queue_work (system_unbound_wq, ¬if->commit_work); }
@@ -134,12 +147,15 @@ __cold int io_notif_unregister(struct io_ring_ctx *ctx)for (i =0 ; i < ctx->nr_notif_slots; i++) {struct io_notif_slot *slot = &ctx->notif_slots[i]; -if (slot->notif) -io_notif_slot_flush (slot); +if (!slot->notif) +continue ; +if (WARN_ON_ONCE (slot->notif->task)) + slot->notif->task =NULL ; +io_notif_slot_flush (slot); }kvfree (ctx->notif_slots); ctx->notif_slots =NULL ; ctx->nr_notif_slots =0 ;return 0 ; -} +}
io_uring/notif.h +3 -0
@@ -11,6 +11,9 @@ struct io_notif {struct ubuf_info uarg;struct io_ring_ctx *ctx; +/* complete via tw if ->task is non-NULL, fallback to wq otherwise */ +struct task_struct *task; +/* cqe->user_data, io_notif_slot::tag if not overridden */ u64 tag;/* see struct io_notif_slot::seq */
Signature · 1 line
2.36.1
PATCH v3 16/25 io_uring: add notification slot registration
In reply to Pavel BegunkovLet the userspace to register and unregister notification slots.
--- include/uapi/linux/io_uring.h | 17 ++++++++++++++ io_uring/io_uring.c | 9 ++++++++ io_uring/notif.c | 43 +++++++++++++++++++++++++++++++++++ io_uring/notif.h | 3 +++ 4 files changed, 72 insertions(+)
include/uapi/linux/io_uring.h +17 -0
@@ -429,6 +429,10 @@ enum {/* sync cancelation API */ IORING_REGISTER_SYNC_CANCEL =24 , +/* zerocopy notification API */ +IORING_REGISTER_NOTIFIERS =25 , +IORING_UNREGISTER_NOTIFIERS =26 , +/* this goes last */ IORING_REGISTER_LAST };
@@ -475,6 +479,19 @@ struct io_uring_rsrc_update2 {__u32 resv2; }; +struct io_uring_notification_slot { +__u64 tag; +__u64 resv[3 ]; +}; + +struct io_uring_notification_register { +__u32 nr_slots; +__u32 resv; +__u64 resv2; +__u64 data; +__u64 resv3; +}; +/* Skip updating fd indexes set to this value in the fd table */ #define IORING_REGISTER_FILES_SKIP (-2)
io_uring/io_uring.c +9 -0
@@ -3862,6 +3862,15 @@ static int __io_uring_register(struct io_ring_ctx *ctx, unsigned opcode,break ; ret =io_sync_cancel (ctx, arg);break ; +case IORING_REGISTER_NOTIFIERS : + ret =io_notif_register (ctx, arg, nr_args); +break ; +case IORING_UNREGISTER_NOTIFIERS : + ret = -EINVAL ; +if (arg || nr_args) +break ; + ret =io_notif_unregister (ctx); +break ;default : ret = -EINVAL ;break ;
io_uring/notif.c +43 -0
48 lines not on this page · raw message
io_uring/notif.h +3 -0
@@ -6,6 +6,7 @@#include <linux/nospec.h> #define IO_NOTIF_SPLICE_BATCH 32 +#define IORING_MAX_NOTIF_SLOTS (1U << 10)struct io_notif {struct ubuf_info uarg;
@@ -47,6 +48,8 @@ struct io_notif_slot {u32 seq; }; +int io_notif_register (struct io_ring_ctx *ctx, +void __user *arg,unsigned int size);int io_notif_unregister (struct io_ring_ctx *ctx);void io_notif_cache_purge (struct io_ring_ctx *ctx);
Signature · 1 line
2.36.1
PATCH v3 17/25 io_uring: wire send zc request type
In reply to Pavel BegunkovAdd a new io_uring opcode IORING_OP_SENDZC. The main distinction from IORING_OP_SEND is that the user should specify a notification slot index in sqe::notification_idx and the buffers are safe to reuse only when the used notification is flushed and completes.
--- include/uapi/linux/io_uring.h | 5 +++ io_uring/net.c | 84 +++++++++++++++++++++++++++++++++++ io_uring/net.h | 4 ++ io_uring/opdef.c | 15 +++++++ 4 files changed, 108 insertions(+)
include/uapi/linux/io_uring.h +5 -0
@@ -62,6 +62,10 @@ struct io_uring_sqe {union {__s32 splice_fd_in;__u32 file_index; +struct { +__u16 notification_idx; +__u16 __pad; + } __attribute__((packed)); }; union { struct {
@@ -193,6 +197,7 @@ enum io_uring_op {IORING_OP_GETXATTR ,IORING_OP_SOCKET ,IORING_OP_URING_CMD , +IORING_OP_SENDZC ,/* this goes last, obviously */ IORING_OP_LAST,
io_uring/net.c +84 -0
100 lines not on this page · raw message
io_uring/net.h +4 -0
@@ -40,4 +40,8 @@ int io_socket(struct io_kiocb *req, unsigned int issue_flags);int io_connect_prep_async (struct io_kiocb *req);int io_connect_prep (struct io_kiocb *req,const struct io_uring_sqe *sqe);int io_connect (struct io_kiocb *req,unsigned int issue_flags); + +int io_sendzc (struct io_kiocb *req,unsigned int issue_flags); +int io_sendzc_prep (struct io_kiocb *req,const struct io_uring_sqe *sqe); +#endif
io_uring/opdef.c +15 -0
@@ -466,6 +466,21 @@ const struct io_op_def io_op_defs[] = { .issue = io_uring_cmd, .prep_async = io_uring_cmd_prep_async, }, + [IORING_OP_SENDZC] = { + .name = "SENDZC", + .needs_file = 1, + .unbound_nonreg_file = 1, + .pollout = 1, + .audit_skip = 1, + .ioprio = 1, +#if defined(CONFIG_NET ) + .prep = io_sendzc_prep, + .issue = io_sendzc, +#else + .prep = io_eopnotsupp_prep, +#endif + + }, };const char *io_uring_get_opcode (u8 opcode)
Signature · 1 line
2.36.1
PATCH v3 18/25 io_uring: account locked pages for non-fixed zc
In reply to Pavel BegunkovFixed buffers are RLIMIT_MEMLOCK accounted, however it doesn't cover iovec based zerocopy sends. Do the accounting on the io_uring side.
--- io_uring/net.c | 1 + io_uring/notif.c | 6 ++++++ 2 files changed, 7 insertions(+)
io_uring/net.c +1 -0
@@ -834,6 +834,7 @@ int io_sendzc(struct io_kiocb *req, unsigned int issue_flags) ret =import_single_range (WRITE , zc->buf, zc->len, &iov, &msg.msg_iter);if (unlikely (ret))return ret; +mm_account_pinned_pages (¬if->uarg.mmp, zc->len); msg_flags = zc->msg_flags |MSG_ZEROCOPY ;if (issue_flags &IO_URING_F_NONBLOCK )
io_uring/notif.c +6 -0
@@ -12,7 +12,13 @@ static void __io_notif_complete_tw(struct callback_head *cb) { struct io_notif *notif = container_of(cb, struct io_notif, task_work);struct io_ring_ctx *ctx = notif->ctx; +struct mmpin *mmp = ¬if->uarg.mmp; +if (mmp->user) { +atomic_long_sub (mmp->num_pg, &mmp->user->locked_vm); +free_uid (mmp->user); + mmp->user =NULL ; + }if (likely (notif->task)) {io_put_task (notif->task,1 ); notif->task =NULL ;
Signature · 1 line
2.36.1
PATCH v3 19/25 io_uring: allow to pass addr into sendzc
In reply to Pavel BegunkovAllow to specify an address to zerocopy sends making it more like sendto(2).
--- include/uapi/linux/io_uring.h | 2 +- io_uring/net.c | 17 ++++++++++++++++- 2 files changed, 17 insertions(+), 2 deletions(-)
include/uapi/linux/io_uring.h +1 -1
@@ -64,7 +64,7 @@ struct io_uring_sqe {__u32 file_index;struct {__u16 notification_idx; -__u16 __pad; +__u16 addr_len; } __attribute__((packed)); };union {
io_uring/net.c +16 -1
@@ -65,6 +65,8 @@ struct io_sendzc {size_t len;u16 slot_idx;int msg_flags; +int addr_len; +void __user *addr; };#define IO_APOLL_MULTI_POLLED (REQ_F_APOLL_MULTISHOT | REQ_F_POLLED)
@@ -784,7 +786,7 @@ int io_sendzc_prep(struct io_kiocb *req, const struct io_uring_sqe *sqe) {struct io_sendzc *zc =io_kiocb_to_cmd (req); -if (READ_ONCE (sqe->ioprio) ||READ_ONCE (sqe->addr2) ||READ_ONCE (sqe->__pad2[0 ])) +if (READ_ONCE (sqe->ioprio) ||READ_ONCE (sqe->__pad2[0 ]))return -EINVAL ; zc->buf =u64_to_user_ptr (READ_ONCE (sqe->addr));
@@ -793,6 +795,10 @@ int io_sendzc_prep(struct io_kiocb *req, const struct io_uring_sqe *sqe) zc->slot_idx =READ_ONCE (sqe->notification_idx);if (zc->msg_flags &MSG_DONTWAIT ) req->flags |=REQ_F_NOWAIT ; + + zc->addr =u64_to_user_ptr (READ_ONCE (sqe->addr2)); + zc->addr_len =READ_ONCE (sqe->addr_len); +#ifdef CONFIG_COMPAT if (req->ctx->compat) zc->msg_flags |=MSG_CMSG_COMPAT ;
@@ -802,6 +808,7 @@ int io_sendzc_prep(struct io_kiocb *req, const struct io_uring_sqe *sqe)int io_sendzc (struct io_kiocb *req,unsigned int issue_flags) { +struct sockaddr_storage address;struct io_ring_ctx *ctx = req->ctx;struct io_sendzc *zc =io_kiocb_to_cmd (req);struct io_notif_slot *notif_slot;
@@ -836,6 +843,14 @@ int io_sendzc(struct io_kiocb *req, unsigned int issue_flags)return ret;mm_account_pinned_pages (¬if->uarg.mmp, zc->len); +if (zc->addr) { + ret =move_addr_to_kernel (zc->addr, zc->addr_len, &address); +if (unlikely (ret <0 )) +return ret; + msg.msg_name = (struct sockaddr *)&address; + msg.msg_namelen = zc->addr_len; + } + msg_flags = zc->msg_flags |MSG_ZEROCOPY ;if (issue_flags &IO_URING_F_NONBLOCK ) msg_flags |=MSG_DONTWAIT ;
Signature · 1 line
2.36.1
PATCH v3 20/25 io_uring: add rsrc referencing for notifiers
In reply to Pavel BegunkovIn preparation to zerocopy sends with fixed buffers make notifiers to reference the rsrc node to protect the used fixed buffers. We can't just grab it for a send request as notifiers can likely outlive requests that used it.
--- io_uring/notif.c | 5 +++++ io_uring/notif.h | 1 + io_uring/rsrc.h | 12 +++++++++--- 3 files changed, 15 insertions(+), 3 deletions(-)
io_uring/notif.c +5 -0
@@ -7,10 +7,12 @@#include "io_uring.h" #include "notif.h" +#include "rsrc.h" static void __io_notif_complete_tw (struct callback_head *cb) { struct io_notif *notif = container_of(cb, struct io_notif, task_work); +struct io_rsrc_node *rsrc_node = notif->rsrc_node;struct io_ring_ctx *ctx = notif->ctx;struct mmpin *mmp = ¬if->uarg.mmp;
@@ -31,6 +33,7 @@ static void __io_notif_complete_tw(struct callback_head *cb) ctx->notif_locked_nr++;io_cq_unlock_post (ctx); +io_rsrc_put_node (rsrc_node,1 );percpu_ref_put (&ctx->refs); }
@@ -125,6 +128,8 @@ struct io_notif *io_alloc_notif(struct io_ring_ctx *ctx,/* master ref owned by io_notif_slot, will be dropped on flush */ refcount_set (¬if->uarg.refcnt,1 );percpu_ref_get (&ctx->refs); + notif->rsrc_node = ctx->rsrc_node; +io_charge_rsrc_node (ctx);return notif; }
io_uring/notif.h +1 -0
@@ -11,6 +11,7 @@struct io_notif {struct ubuf_info uarg;struct io_ring_ctx *ctx; +struct io_rsrc_node *rsrc_node;/* complete via tw if ->task is non-NULL, fallback to wq otherwise */ struct task_struct *task;
io_uring/rsrc.h +9 -3
@@ -135,6 +135,13 @@ static inline void io_req_put_rsrc_locked(struct io_kiocb *req, } } +static inline void io_charge_rsrc_node (struct io_ring_ctx *ctx) +{ + ctx->rsrc_cached_refs--; +if (unlikely (ctx->rsrc_cached_refs <0 )) +io_rsrc_refs_refill (ctx); +} +static inline void io_req_set_rsrc_node (struct io_kiocb *req,struct io_ring_ctx *ctx,unsigned int issue_flags)
@@ -144,9 +151,8 @@ static inline void io_req_set_rsrc_node(struct io_kiocb *req,if (!(issue_flags &IO_URING_F_UNLOCKED )) {lockdep_assert_held (&ctx->uring_lock); - ctx->rsrc_cached_refs--; -if (unlikely (ctx->rsrc_cached_refs <0 )) -io_rsrc_refs_refill (ctx); + +io_charge_rsrc_node (ctx); }else {percpu_ref_get (&req->rsrc_node->refs); }
Signature · 1 line
2.36.1
PATCH v3 21/25 io_uring: sendzc with fixed buffers
In reply to Pavel BegunkovAllow zerocopy sends to use fixed buffers. There is an optimisation for this case, the network layer don't need to reference the pages, see SKBFL_MANAGED_FRAG_REFS, so io_uring have to ensure validity of fixed buffers until the notifier is released.
--- include/uapi/linux/io_uring.h | 7 ++++++ io_uring/net.c | 40 +++++++++++++++++++++++++++++------ 2 files changed, 41 insertions(+), 6 deletions(-)
include/uapi/linux/io_uring.h +7 -0
@@ -272,6 +272,13 @@ enum io_uring_op {*/ #define IORING_ACCEPT_MULTISHOT (1U << 0) +/* +* IORING_OP_SENDZC flags +*/ +enum { +IORING_SENDZC_FIXED_BUF = (1U <<0 ), +}; +/* * IO completion data structure (Completion Queue Entry) */
io_uring/net.c +34 -6
@@ -14,6 +14,7 @@#include "kbuf.h" #include "net.h" #include "notif.h" +#include "rsrc.h" #if defined(CONFIG_NET ) struct io_shutdown {
@@ -65,6 +66,7 @@ struct io_sendzc {size_t len;u16 slot_idx;int msg_flags; +unsigned zc_flags;int addr_len;void __user *addr; };
@@ -782,11 +784,14 @@ int io_connect(struct io_kiocb *req, unsigned int issue_flags)return IOU_OK ; } +#define IO_SENDZC_VALID_FLAGS IORING_SENDZC_FIXED_BUF +int io_sendzc_prep (struct io_kiocb *req,const struct io_uring_sqe *sqe) {struct io_sendzc *zc =io_kiocb_to_cmd (req); +struct io_ring_ctx *ctx = req->ctx; -if (READ_ONCE (sqe->ioprio) ||READ_ONCE (sqe->__pad2[0 ])) +if (READ_ONCE (sqe->__pad2[0 ]))return -EINVAL ; zc->buf =u64_to_user_ptr (READ_ONCE (sqe->addr));
@@ -799,6 +804,20 @@ int io_sendzc_prep(struct io_kiocb *req, const struct io_uring_sqe *sqe) zc->addr =u64_to_user_ptr (READ_ONCE (sqe->addr2)); zc->addr_len =READ_ONCE (sqe->addr_len); + zc->zc_flags =READ_ONCE (sqe->ioprio); +if (zc->zc_flags & ~IO_SENDZC_VALID_FLAGS ) +return -EINVAL ; + +if (zc->zc_flags &IORING_SENDZC_FIXED_BUF ) { +unsigned idx =READ_ONCE (sqe->buf_index); + +if (unlikely (idx >= ctx->nr_user_bufs)) +return -EFAULT ; + idx =array_index_nospec (idx, ctx->nr_user_bufs); + req->imu =READ_ONCE (ctx->user_bufs[idx]); +io_req_set_rsrc_node (req, ctx,0 ); + } +#ifdef CONFIG_COMPAT if (req->ctx->compat) zc->msg_flags |=MSG_CMSG_COMPAT ;
@@ -836,12 +855,21 @@ int io_sendzc(struct io_kiocb *req, unsigned int issue_flags) msg.msg_control =NULL ; msg.msg_controllen =0 ; msg.msg_namelen =0 ; - msg.msg_managed_data =0 ; + msg.msg_managed_data =1 ; - ret =import_single_range (WRITE , zc->buf, zc->len, &iov, &msg.msg_iter); -if (unlikely (ret)) -return ret; -mm_account_pinned_pages (¬if->uarg.mmp, zc->len); +if (zc->zc_flags &IORING_SENDZC_FIXED_BUF ) { + ret =io_import_fixed (WRITE , &msg.msg_iter, req->imu, + (u64 )zc->buf, zc->len); +if (unlikely (ret)) +return ret; + }else { + msg.msg_managed_data =0 ; + ret =import_single_range (WRITE , zc->buf, zc->len, &iov, + &msg.msg_iter); +if (unlikely (ret)) +return ret; +mm_account_pinned_pages (¬if->uarg.mmp, zc->len); + }if (zc->addr) { ret =move_addr_to_kernel (zc->addr, zc->addr_len, &address);
Signature · 1 line
2.36.1
PATCH v3 22/25 io_uring: flush notifiers after sendzc
In reply to Pavel BegunkovAllow to flush notifiers as a part of sendzc request by setting IORING_SENDZC_FLUSH flag. When the sendzc request succeedes it will flush the used [active] notifier.
--- include/uapi/linux/io_uring.h | 1 + io_uring/io_uring.c | 11 +---------- io_uring/io_uring.h | 10 ++++++++++ io_uring/net.c | 4 +++- io_uring/notif.c | 2 +- io_uring/notif.h | 11 +++++++++++ 6 files changed, 27 insertions(+), 12 deletions(-)
include/uapi/linux/io_uring.h +1 -0
@@ -277,6 +277,7 @@ enum io_uring_op {*/ enum {IORING_SENDZC_FIXED_BUF = (1U <<0 ), +IORING_SENDZC_FLUSH = (1U <<1 ), };/*
io_uring/io_uring.c +1 -10
@@ -615,7 +615,7 @@ void __io_put_task(struct task_struct *task, int nr)put_task_struct_many (task, nr); } -static void io_task_refs_refill (struct io_uring_task *tctx) +void io_task_refs_refill (struct io_uring_task *tctx) {unsigned int refill = -tctx->cached_refs +IO_TCTX_REFS_CACHE_NR ;
@@ -624,15 +624,6 @@ static void io_task_refs_refill(struct io_uring_task *tctx) tctx->cached_refs += refill; } -static inline void io_get_task_refs (int nr) -{ -struct io_uring_task *tctx = current->io_uring; - - tctx->cached_refs -= nr; -if (unlikely (tctx->cached_refs <0 )) -io_task_refs_refill (tctx); -} -static __cold void io_uring_drop_tctx_refs (struct task_struct *task) {struct io_uring_task *tctx = task->io_uring;
io_uring/io_uring.h +10 -0
@@ -60,6 +60,7 @@ void io_wq_submit_work(struct io_wq_work *work);void io_free_req (struct io_kiocb *req);void io_queue_next (struct io_kiocb *req);void __io_put_task (struct task_struct *task,int nr); +void io_task_refs_refill (struct io_uring_task *tctx);bool io_match_task_safe (struct io_kiocb *head,struct task_struct *task,bool cancel_all);
@@ -254,4 +255,13 @@ static inline void io_put_task(struct task_struct *task, int nr) __io_put_task(task, nr); } +static inline void io_get_task_refs (int nr) +{ +struct io_uring_task *tctx = current->io_uring; + + tctx->cached_refs -= nr; +if (unlikely (tctx->cached_refs <0 )) +io_task_refs_refill (tctx); +} +#endif
io_uring/net.c +3 -1
@@ -784,7 +784,7 @@ int io_connect(struct io_kiocb *req, unsigned int issue_flags)return IOU_OK ; } -#define IO_SENDZC_VALID_FLAGS IORING_SENDZC_FIXED_BUF +#define IO_SENDZC_VALID_FLAGS (IORING_SENDZC_FIXED_BUF|IORING_SENDZC_FLUSH)int io_sendzc_prep (struct io_kiocb *req,const struct io_uring_sqe *sqe) {
@@ -895,6 +895,8 @@ int io_sendzc(struct io_kiocb *req, unsigned int issue_flags)return ret == -ERESTARTSYS ? -EINTR : ret; } +if (zc->zc_flags &IORING_SENDZC_FLUSH ) +io_notif_slot_flush_submit (notif_slot,0 );io_req_set_res (req, ret,0 );return IOU_OK ; }
io_uring/notif.c +1 -1
@@ -133,7 +133,7 @@ struct io_notif *io_alloc_notif(struct io_ring_ctx *ctx,return notif; } -static void io_notif_slot_flush (struct io_notif_slot *slot) +void io_notif_slot_flush (struct io_notif_slot *slot) __must_hold(&ctx->uring_lock) {struct io_notif *notif = slot->notif;
io_uring/notif.h +11 -0
@@ -54,6 +54,7 @@ int io_notif_register(struct io_ring_ctx *ctx,int io_notif_unregister (struct io_ring_ctx *ctx);void io_notif_cache_purge (struct io_ring_ctx *ctx); +void io_notif_slot_flush (struct io_notif_slot *slot);struct io_notif *io_alloc_notif (struct io_ring_ctx *ctx,struct io_notif_slot *slot);
@@ -74,3 +75,13 @@ static inline struct io_notif_slot *io_get_notif_slot(struct io_ring_ctx *ctx, idx =array_index_nospec (idx, ctx->nr_notif_slots);return &ctx->notif_slots[idx]; } + +static inline void io_notif_slot_flush_submit (struct io_notif_slot *slot, +unsigned int issue_flags) +{ +if (!(issue_flags &IO_URING_F_UNLOCKED )) { + slot->notif->task = current; +io_get_task_refs (1 ); + } +io_notif_slot_flush (slot); +}
Signature · 1 line
2.36.1
PATCH v3 23/25 io_uring: rename IORING_OP_FILES_UPDATE
In reply to Pavel BegunkovIORING_OP_FILES_UPDATE will be a more generic opcode serving different resource types, rename it into IORING_OP_RSRC_UPDATE and add subtype handling.
--- include/uapi/linux/io_uring.h | 12 +++++++++++- io_uring/opdef.c | 9 +++++---- io_uring/rsrc.c | 17 +++++++++++++++-- io_uring/rsrc.h | 4 ++-- 4 files changed, 33 insertions(+), 9 deletions(-)
include/uapi/linux/io_uring.h +11 -1
@@ -170,7 +170,8 @@ enum io_uring_op {IORING_OP_FALLOCATE ,IORING_OP_OPENAT ,IORING_OP_CLOSE , -IORING_OP_FILES_UPDATE , +IORING_OP_RSRC_UPDATE , +IORING_OP_FILES_UPDATE =IORING_OP_RSRC_UPDATE ,IORING_OP_STATX ,IORING_OP_READ , IORING_OP_WRITE,
@@ -219,6 +220,7 @@ enum io_uring_op {#define IORING_TIMEOUT_ETIME_SUCCESS (1U << 5)#define IORING_TIMEOUT_CLOCK_MASK (IORING_TIMEOUT_BOOTTIME | IORING_TIMEOUT_REALTIME)#define IORING_TIMEOUT_UPDATE_MASK (IORING_TIMEOUT_UPDATE | IORING_LINK_TIMEOUT_UPDATE) +/* * sqe->splice_flags * extends splice(2) flags
@@ -272,6 +274,14 @@ enum io_uring_op {*/ #define IORING_ACCEPT_MULTISHOT (1U << 0) + +/* +* IORING_OP_RSRC_UPDATE flags +*/ +enum { +IORING_RSRC_UPDATE_FILES , +}; +/* * IORING_OP_SENDZC flags */
io_uring/opdef.c +5 -4
@@ -244,12 +244,13 @@ const struct io_op_def io_op_defs[] = { .prep = io_close_prep, .issue = io_close, }, - [IORING_OP_FILES_UPDATE] = { + [IORING_OP_RSRC_UPDATE] = { .audit_skip = 1, .iopoll = 1, - .name = "FILES_UPDATE", - .prep = io_files_update_prep, - .issue = io_files_update, + .name = "RSRC_UPDATE", + .prep = io_rsrc_update_prep, + .issue = io_rsrc_update, + .ioprio = 1, }, [IORING_OP_STATX] = { .audit_skip = 1,
io_uring/rsrc.c +15 -2
@@ -21,6 +21,7 @@ struct io_rsrc_update {u64 arg;u32 nr_args;u32 offset; +int type; }; static int io_sqe_buffer_register(struct io_ring_ctx *ctx, struct iovec *iov,
@@ -658,7 +659,7 @@ __cold int io_register_rsrc(struct io_ring_ctx *ctx, void __user *arg,return -EINVAL ; } -int io_files_update_prep (struct io_kiocb *req,const struct io_uring_sqe *sqe) +int io_rsrc_update_prep (struct io_kiocb *req,const struct io_uring_sqe *sqe) {struct io_rsrc_update *up =io_kiocb_to_cmd (req);
@@ -672,6 +673,7 @@ int io_files_update_prep(struct io_kiocb *req, const struct io_uring_sqe *sqe)if (!up->nr_args)return -EINVAL ; up->arg =READ_ONCE (sqe->addr); + up->type =READ_ONCE (sqe->ioprio);return 0 ; }
@@ -711,7 +713,7 @@ static int io_files_update_with_index_alloc(struct io_kiocb *req,return ret; } -int io_files_update (struct io_kiocb *req,unsigned int issue_flags) +static int io_files_update (struct io_kiocb *req,unsigned int issue_flags) {struct io_rsrc_update *up =io_kiocb_to_cmd (req);struct io_ring_ctx *ctx = req->ctx;
@@ -740,6 +742,17 @@ int io_files_update(struct io_kiocb *req, unsigned int issue_flags)return IOU_OK ; } +int io_rsrc_update (struct io_kiocb *req,unsigned int issue_flags) +{ +struct io_rsrc_update *up =io_kiocb_to_cmd (req); + +switch (up->type) { +case IORING_RSRC_UPDATE_FILES : +return io_files_update (req, issue_flags); + } +return -EINVAL ; +} +int io_queue_rsrc_removal (struct io_rsrc_data *data,unsigned idx,struct io_rsrc_node *node,void *rsrc) {
io_uring/rsrc.h +2 -2
@@ -167,6 +167,6 @@ static inline u64 *io_get_tag_slot(struct io_rsrc_data *data, unsigned int idx) return &data->tags[table_idx][off]; } -int io_files_update (struct io_kiocb *req,unsigned int issue_flags); -int io_files_update_prep (struct io_kiocb *req,const struct io_uring_sqe *sqe); +int io_rsrc_update (struct io_kiocb *req,unsigned int issue_flags); +int io_rsrc_update_prep (struct io_kiocb *req,const struct io_uring_sqe *sqe);#endif
Signature · 1 line
2.36.1
PATCH v3 24/25 io_uring: add zc notification flush requests
In reply to Pavel BegunkovOverlay notification control onto IORING_OP_RSRC_UPDATE (former IORING_OP_FILES_UPDATE). It allows to flush a range of zc notifications from slots with indexes [sqe->off, sqe->off+sqe->len). If sqe->arg is not zero, it also copies sqe->arg as a new tag for all flushed notifications.
Note, it doesn't flush a notification of a slot if there was no requests attached to it (since last flush or registration).
--- include/uapi/linux/io_uring.h | 1 + io_uring/rsrc.c | 38 +++++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+)
include/uapi/linux/io_uring.h +1 -0
@@ -280,6 +280,7 @@ enum io_uring_op {*/ enum {IORING_RSRC_UPDATE_FILES , +IORING_RSRC_UPDATE_NOTIF , };/*
io_uring/rsrc.c +38 -0
@@ -15,6 +15,7 @@#include "io_uring.h" #include "openclose.h" #include "rsrc.h" +#include "notif.h" struct io_rsrc_update {struct file *file;
@@ -742,6 +743,41 @@ static int io_files_update(struct io_kiocb *req, unsigned int issue_flags)return IOU_OK ; } +static int io_notif_update (struct io_kiocb *req,unsigned int issue_flags) +{ +struct io_rsrc_update *up =io_kiocb_to_cmd (req); +struct io_ring_ctx *ctx = req->ctx; +unsigned len = up->nr_args; +unsigned idx_end, idx = up->offset; +int ret =0 ; + +io_ring_submit_lock (ctx, issue_flags); +if (unlikely (check_add_overflow (idx, len, &idx_end))) { + ret = -EOVERFLOW ; + goto out; + } +if (unlikely (idx_end > ctx->nr_notif_slots)) { + ret = -EINVAL ; + goto out; + } + +for (; idx < idx_end; idx++) { +struct io_notif_slot *slot = &ctx->notif_slots[idx]; + +if (!slot->notif) +continue ; +if (up->arg) + slot->tag = up->arg; +io_notif_slot_flush_submit (slot, issue_flags); + } +out: +io_ring_submit_unlock (ctx, issue_flags); +if (ret <0 ) +req_set_fail (req); +io_req_set_res (req, ret,0 ); +return IOU_OK ; +} +int io_rsrc_update (struct io_kiocb *req,unsigned int issue_flags) {struct io_rsrc_update *up =io_kiocb_to_cmd (req);
@@ -749,6 +785,8 @@ int io_rsrc_update(struct io_kiocb *req, unsigned int issue_flags)switch (up->type) {case IORING_RSRC_UPDATE_FILES :return io_files_update (req, issue_flags); +case IORING_RSRC_UPDATE_NOTIF : +return io_notif_update (req, issue_flags); }return -EINVAL ; }
Signature · 1 line
2.36.1
PATCH v3 25/25 selftests/io_uring: test zerocopy send
In reply to Pavel BegunkovAdd selftests for io_uring zerocopy sends and io_uring's notification infrastructure. It's largely influenced by msg_zerocopy and uses it on the receive side.
--- tools/testing/selftests/net/Makefile | 1 + .../selftests/net/io_uring_zerocopy_tx.c | 605 ++++++++++++++++++ .../selftests/net/io_uring_zerocopy_tx.sh | 131 ++++ 3 files changed, 737 insertions(+) create mode 100644 tools/testing/selftests/net/io_uring_zerocopy_tx.c create mode 100755 tools/testing/selftests/net/io_uring_zerocopy_tx.sh
tools/testing/selftests/net/Makefile +1 -0
@@ -59,6 +59,7 @@ TEST_GEN_FILES += toeplitz TEST_GEN_FILES += cmsg_sender TEST_GEN_FILES += stress_reuseport_listen TEST_PROGS += test_vxlan_vnifiltering.sh +TEST_GEN_FILES += io_uring_zerocopy_tx TEST_FILES := settings
tools/testing/selftests/net/io_uring_zerocopy_tx.c new file +605 -0
605 lines not on this page · raw message
tools/testing/selftests/net/io_uring_zerocopy_tx.sh new file +131 -0
131 lines not on this page · raw message
Signature · 1 line
2.36.1
On 7/5/22 16:01, Pavel Begunkov wrote:
NOTE: This is not be picked directly due to cross-subsystem merge problems. After finding a consensus and getting necessary acks, I'll work out merging with Jakub and Jens.
Quoted 160 lines
The patchset implements
io_uringzerocopy send. It works with both registered and normal buffers, mixing is allowed but not recommended. Apart from usual request completions, just as withMSG_ZEROCOPY,io_uringseparately notifies the userspace when buffers are freed and can be reused (see API design below), which is delivered into io_uring's Completion Queue. Those "buffer-free" notifications are not necessarily per request, but the userspace has control over it and should explicitly attaching a number of requests to a single notification. The series also adds some internal optimisations when used with registered buffers like removing page referencing.From the kernel networking perspective there are two main changes. The first
one is passingubuf_infointo the network layer fromio_uring(inside of an in kernel struct msghdr). This allows extra optimisations, e.g.ubuf_infocaching on theio_uringside, but also helps to avoid cross-referencing and synchronisation problems. The second part is an optional optimisation removing page referencing for requests with registered buffers.Benchmarking with an optimised version of the selftest (see [1]), which in a loop sends a bunch of requests and then waits for their completions. "+ flush" column posts one additional "buffer-free" notification per request, and just "zc" doesn't post buffer notifications at all.
NIC (requests / second):
IO size | non-zc | zc | zc + flush 4000 | 495134 | 606420 (+22%) | 558971 (+12%) 1500 | 551808 | 577116 (+4.5%) | 565803 (+2.5%) 1000 | 584677 | 592088 (+1.2%) | 560885 (-4%) 600 | 596292 | 598550 (+0.4%) | 555366 (-6.7%)dummy (requests / second):
IO size | non-zc | zc | zc + flush 8000 | 1299916 | 2396600 (+84%) | 2224219 (+71%) 4000 | 1869230 | 2344146 (+25%) | 2170069 (+16%) 1200 | 2071617 | 2361960 (+14%) | 2203052 (+6%) 600 | 2106794 | 2381527 (+13%) | 2195295 (+4%)Previously it also brought a massive performance speedup compared to the
msg_zerocopytool (see [3]), which is probably not super interesting.There is an additional bunch of refcounting optimisations that was omitted from the series for simplicity and as they don't change the picture drastically, they will be sent as follow up, as well as flushing optimisations closing the performance gap b/w two last columns.
Note: the series is based on net-next + for-5.20/io_uring, but as vanilla net-next fails for me the repo (see [2]) is on top of for-5.20/io_uring.
Links:
liburing (benchmark + some tests): [1] https://github.com/isilence/liburing/tree/zc_v3 kernel repo: [2] https://github.com/isilence/linux/tree/zc_v3 RFC v1: [3] https://lore.kernel.org/io-uring/[email protected]/ RFC v2: https://lore.kernel.org/io-uring/[email protected]/API design overview:
The series introduces an
io_uringconcept of notifactors. From the userspace perspective it's an entity to which it can bind one or more requests and then requesting to flush it. Flushing a notifier makes it impossible to attach new requests to it, and instructs the notifier to post a completion once all requests attached to it are completed and the kernel doesn't need the buffers anymore.Notifications are stored in notification slots, which should be registered as an array in
io_uring. Each slot stores only one notifier at any particular moment. Flushing removes it from the slot and the slot automatically replaces it with a new notifier. All operations with notifiers are done by specifying an index of a slot it's currently in.When registering a notification the userspace specifies a u64 tag for each slot, which will be copied in notification completion entries as
cqe::user_data.cqe::resis 0 andcqe::flagsis equal to wrap around u32 sequence number counting notifiers of a slot.Changelog:
RFC v2 -> v3:
mem accounting for non-registered buffers allow mixing registered and normal requests per notifier notification flushing viaIORING_OP_RSRC_UPDATETCP support
fix buffer indexing
fix io-wq ->uring_lock locking
fix bugs when mixing withMSG_ZEROCOPY
fix managed refs bugs inskbuff.cRFC -> RFC v2:
remove additional overhead for non-zc fromskb_release_data()avoid msg propagation, hide extra bits of non-zc overheadtask_workbased "buffer free" notifications improve io_uring's notification refcounting
added 5/19, (no pfmemalloc tracking)
added 8/19 and 9/19 preventing small copies with zc misc small changesPavel Begunkov (25):
ipv4: avoid partial copy for zc
ipv6: avoid partial copy for zc
skbuff: addSKBFL_DONT_ORPHANflag
skbuff: carry externalubuf_infoin msghdr
net: bvec specific path inzerocopy_sg_from_iter
net: optimise bvec-based zc page referencing
net: don't track pfmemalloc for managed frags
skbuff: don't mixubuf_infoof different types ipv4/udp: support zc with managed data ipv6/udp: support zc with managed data
tcp: support zc with managed data
io_uring: add zc notification infrastructure
io_uring: export task put
io_uring: cache structio_notif
io_uring: complete notifiers in tw
io_uring: add notification slot registration
io_uring: wire send zc request type
io_uring: account locked pages for non-fixed zc
io_uring: allow to pass addr into sendzc
io_uring: add rsrc referencing for notifiers
io_uring: sendzc with fixed buffers
io_uring: flush notifiers after sendzc
io_uring: renameIORING_OP_FILES_UPDATE
io_uring: add zc notification flush requests selftests/io_uring: test zerocopy sendinclude/linux/io_uring_types.h | 37 ++ include/linux/skbuff.h | 59 +- include/linux/socket.h | 7 + include/uapi/linux/io_uring.h | 43 +- io_uring/Makefile | 2 +- io_uring/io_uring.c | 40 +- io_uring/io_uring.h | 21 + io_uring/net.c | 134 ++++ io_uring/net.h | 4 + io_uring/notif.c | 215 +++++++ io_uring/notif.h | 87 +++ io_uring/opdef.c | 24 +- io_uring/rsrc.c | 55 +- io_uring/rsrc.h | 16 +- io_uring/tctx.h | 26 - net/compat.c | 2 + net/core/datagram.c | 53 +- net/core/skbuff.c | 35 +- net/ipv4/ip_output.c | 63 +- net/ipv4/tcp.c | 52 +- net/ipv6/ip6_output.c | 62 +- net/socket.c | 6 + tools/testing/selftests/net/Makefile | 1 + .../selftests/net/io_uring_zerocopy_tx.c | 605 ++++++++++++++++++ .../selftests/net/io_uring_zerocopy_tx.sh | 131 ++++ 25 files changed, 1652 insertions(+), 128 deletions(-)create mode 100644
io_uring/notif.c
create mode 100644io_uring/notif.h
create mode 100644tools/testing/selftests/net/io_uring_zerocopy_tx.ccreate mode 100755tools/testing/selftests/net/io_uring_zerocopy_tx.sh
Signature · 1 line
Pavel Begunkov