* [PATCH net-next v2 0/5] vsock/virtio: reduce RX per-packet socket overhead
@ 2026-10-10 14:22 Jia Jia
2026-10-10 14:22 ` [PATCH net-next v2 1/5] vsock/virtio: split socket lookup from locked RX processing Jia Jia
` (4 more replies)
0 siblings, 5 replies; 6+ messages in thread
From: Jia Jia @ 2026-10-10 14:22 UTC (permalink / raw)
To: stefanha, sgarzare, netdev, virtualization, kvm
Cc: mst, jasowangio, eperezma, xuanzhuo, davem, edumazet, kuba,
pabeni, horms, linux-kernel, bpf, Jia Jia
This series reduces per-packet socket overhead in the virtio-vsock RX
worker.
Patch 1: separate socket lookup from locked packet processing.
Patch 2: keep one socket lock across a bounded run of established
STREAM/RW packets for the same native socket.
Patch 3: reuse the locked socket for later packets with the same
address tuple.
Patch 4: coalesce default write-space notifications while that
lock is held.
Patch 5: defer the default readable callback until after the batch
unlocks.
Performance:
Tested on a single-stream vsock connection (one Guest receiver thread on
CPU0, virtio-vsock IRQ on Guest CPU1, fresh QEMU Guest per state, 16
balanced AB/BA pairs per payload). The host uses vhost-vsock.
For throughput, vsock_perf runs on the host as the
sender, with a custom single-threaded Guest receiver pinned to Guest CPU0
using blocking recv(). The fixed-64K table uses a 64K buffer; the matched
table uses the sender's request size. The virtio-vsock IRQ/RX worker is
pinned to Guest CPU1. To normalize run times across payloads into a
bounded, deterministic window and avoid long runs being skewed by host
background noise or virtualization scheduling jitter, transfer sizes scale
proportionally with the request size (64B/64M, 256B/256M, etc.), capped at
2G for 4K–64K. The custom receiver keeps the recv() size and count
explicit; vsock_perf's receiver uses poll()+read().
Latency uses a custom user-space vsock ping-pong tool.
The measurements below were collected for the RX batching series.
This benchmark does not run vsock_perf on both ends. Instead, it uses
vsock_perf as the sender on the host (vhost-vsock) side and a custom
blocking recv() program without poll() on the Guest side, with the receiver
thread pinned to Guest CPU0 (and the virtio-vsock RX worker pinned to Guest
CPU1). Plain blocking recv() combined with strict core pinning gives
audit-level determinism for tracing a single-threaded TID.
I think it is necessary to do further testing on the large I/O throughput
peak observed at 4K to find the root cause (and rule out anything
introduced by the measurement method). I used kprobe and temporary
trace_printk in the source for mechanism diagnostics. The diagnostic
data shows that lock wait time and contention at 4K both decrease strictly
monotonically as payload size increases;
wire-level tracing also confirms that 4K is split into two packets, and
that all sizes can reach the 64K batching limit.
The nature of the prominent 4K throughput gain most likely lies in it
sitting exactly at the end-to-end pipeline sweet spot between host and
guest:
Compared with 1K and smaller payloads: 4K halves the packet intensity, so
upstream host-side syscall and interrupt injection overhead is no longer
the bottleneck across the whole path, allowing the host to supply data
adequately.
Compared with larger 8K to 64K payloads: 4K keeps the payload size
moderate, and combined with the observed syscall reduction and cycle
improvement, the batch-draining benefit from notification coalescing is
fully realized.
As payload size increases further from 8K to 64K, the overall gain shrinks
smoothly, consistent with Amdahl's law, because system execution time
shifts toward page copying and VQ processing.
With all patches applied:
End-to-end RX throughput (64K Guest recv() size, SO_RCVLOWAT=1; Gbit/s):
payload baseline RX PATCH RX throughput change (95% CI)
(Gbit/s) (Gbit/s)
64B 0.1065 0.2503 +135.00% [+130.35%, +139.74%]
256B 0.5722 1.0286 +79.76% [+75.40%, +84.23%]
512B 1.1464 1.9779 +72.53% [+69.50%, +75.63%]
1K 2.2714 3.6368 +60.12% [+56.92%, +63.37%]
4K 3.9095 7.7160 +97.37% [+92.09%, +102.79%]
8K 3.3436 5.3111 +58.84% [+53.49%, +64.38%]
16K 3.8876 5.7456 +47.80% [+45.45%, +50.18%]
64K 4.0056 5.3590 +33.79% [+29.37%, +38.36%]
Fixed-syscall stress test (receiver recv() size = sender write size;
Gbit/s):
payload baseline PATCH throughput change (95% CI) recv()
RX RX (base/patch)
(Gbit/s) (Gbit/s)
64B 0.1259 0.2651 +110.52% [+99.08%, +122.62%] 1048577/1048577
256B 0.3790 0.9444 +149.20% [+134.70%, +164.60%] 1048577/1048577
512B 0.7768 1.7009 +118.97% [+103.94%, +135.11%] 1048577/1048577
1K 1.4886 2.9477 +98.02% [+84.82%, +112.17%] 1048577/1048577
4K 2.1129 4.7331 +124.01% [+115.40%, +132.96%] 537436/525524
8K 2.8698 5.3683 +87.06% [+80.96%, +93.37%] 276448/264109
16K 3.5156 5.8463 +66.30% [+63.10%, +69.55%] 147849/132267
64K 4.2459 5.8576 +37.96% [+35.26%, +40.71%] 48447/33144
Request-response latency (Ping-Pong RTT, 10,000 requests):
payload baseline mean RTT (us) PATCH mean RTT (us) change (95% CI)
64B 221.968 218.144 -1.72%
[-4.23%, +0.85%]
256B 126.968 124.541 -1.91%
[-3.35%, -0.45%]
4K 130.362 126.664 -2.84%
[-5.12%, -0.50%]
Diagnostic Guest CPU1 system-wide cycles (fixed-64K receiver, with perf):
Buffer size baseline cycles/B PATCH cycles/B cycles change (95% CI)
64B 62.032 52.370 -15.58% [-17.16%, -13.96%]
256B 15.287 13.630 -10.84% [-12.51%, -9.14%]
512B 7.306 6.611 -9.51% [-10.92%, -8.08%]
1K 3.811 3.458 -9.25% [-10.96%, -7.50%]
4K 1.850 1.669 -9.75% [-11.78%, -7.67%]
8K 1.504 1.363 -9.36% [-12.97%, -5.60%]
16K 1.321 1.210 -8.37% [-10.61%, -6.07%]
64K 1.342 1.227 -8.57% [-10.87%, -6.21%]
Patch-scope measurements and attribution:
The series was tested cumulatively in two ranges, using the same
single-stream setup and paired AB/BA runs. The table below shows results
from patches 1–3 only, before the notification changes in patches 4–5.
payload baseline RX PATCH RX throughput change (95% CI)
(Gbit/s) (Gbit/s)
64B 0.1211 0.2534 +109.26% [+106.34%, +112.23%]
256B 0.5671 0.9989 +76.15% [+70.63%, +81.85%]
512B 1.1334 1.9000 +67.64% [+64.64%, +70.69%]
1K 2.2650 3.5610 +57.22% [+50.91%, +63.79%]
4K 3.8331 7.2888 +90.15% [+83.46%, +97.09%]
8K 4.8244 7.6636 +58.85% [+53.71%, +64.16%]
16K 5.4979 8.2469 +50.00% [+45.57%, +54.57%]
64K 3.8875 4.7588 +22.41% [+17.77%, +27.24%]
Fixed-syscall stress test (receiver recv() size = sender write size;
no perf; Gbit/s):
payload baseline PATCH throughput change (95% CI) recv()
RX RX (base/patch)
(Gbit/s) (Gbit/s)
64B 0.1397 0.2646 +89.40% [+80.92%, +98.27%] 1048577/1048577
256B 0.3960 0.9516 +140.34% [+117.48%, +165.60%] 1048577/1048577
512B 0.7541 1.6496 +118.74% [+101.42%, +137.55%] 1048577/1048577
1K 1.4903 2.8793 +93.21% [+77.06%, +110.82%] 1048577/1048577
4K 2.9466 6.2284 +111.37% [+97.52%, +126.20%] 538192/525220
8K 4.0043 6.9249 +72.94% [+60.18%, +86.71%] 276313/263852
16K 4.8981 7.5599 +54.34% [+49.77%, +59.06%] 147576/131600
64K 5.9294 7.7897 +31.37% [+27.18%, +35.70%] 50207/33014
Ping-Pong RTT (10,000 requests; no perf):
payload baseline mean RTT (us) PATCH mean RTT (us) change (95% CI)
64B 119.688 119.739 +0.04%
[-2.61%, +2.77%]
256B 120.603 124.485 +3.22%
[-0.44%, +7.01%]
4K 121.921 122.491 +0.47%
[-3.51%, +4.61%]
Guest CPU1 system-wide aggregate cycles (fixed-64K receiver, with perf;
cycles/byte):
Buffer size baseline cycles/B PATCH cycles/B cycles change (95% CI)
64B 66.838 60.012 -10.21% [-13.03%, -7.30%]
256B 13.446 12.305 -8.49% [-11.23%, -5.66%]
512B 6.833 6.317 -7.54% [ -9.52%, -5.52%]
1K 3.558 3.434 -3.48% [ -6.02%, -0.87%]
4K 1.806 1.686 -6.64% [ -8.69%, -4.54%]
8K 1.457 1.403 -3.73% [ -5.64%, -1.78%]
16K 1.295 1.279 -1.24% [ -5.21%, +2.89%]
64K 1.214 1.198 -1.30% [ -4.06%, +1.53%]
The split-patch tests show that PATCH 2 and PATCH 3 mainly contribute to
the improvement in I/O Throughput, while PATCH 4 and PATCH 5 mainly
contribute to the improvements in cycles and RTT latency (although PATCH
2-3 show no obvious regression here).
---
v2:
- stop a batch after it consumes the last skb metadata slot, sharing the
admission predicate with virtio_transport_inc_rx_pkt()
- move replacement-callback handoff into vsock, process queued skbs one
at a time, and trigger the handoff from vsock_bpf_update_proto() on
attach and detach
- reduce the packet-context diff in the locked receive helper
- keep RX batch counters in the batch state and reset them in the finish
path
- use the vsock/virtio subject prefix and add the requested measurements
v1: https://lore.kernel.org/virtualization/20261002074551.318789-1-physicalmtea@gmail.com/
Jia Jia (5):
vsock/virtio: split socket lookup from locked RX processing
vsock/virtio: amortize RX socket locking for stream packets
vsock/virtio: reuse same-flow socket lookup in RX batches
vsock/virtio: coalesce RX write-space notifications in lock batches
vsock/virtio: defer RX readable notifications until batch unlock
include/linux/virtio_vsock.h | 16 ++
include/net/af_vsock.h | 8 +
net/vmw_vsock/af_vsock.c | 104 +++++++++
net/vmw_vsock/virtio_transport.c | 29 ++-
net/vmw_vsock/virtio_transport_common.c | 389 +++++++++++++++++++++++++++-----
net/vmw_vsock/vsock_bpf.c | 2 +
6 files changed, 496 insertions(+), 52 deletions(-)
base-commit: 8df0638138d3e0344fd1fb36cf2d1ca1cf5028f0
--
2.53.0
^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH net-next v2 1/5] vsock/virtio: split socket lookup from locked RX processing
2026-10-10 14:22 [PATCH net-next v2 0/5] vsock/virtio: reduce RX per-packet socket overhead Jia Jia
@ 2026-10-10 14:22 ` Jia Jia
2026-10-10 14:22 ` [PATCH net-next v2 2/5] vsock/virtio: amortize RX socket locking for stream packets Jia Jia
` (3 subsequent siblings)
4 siblings, 0 replies; 6+ messages in thread
From: Jia Jia @ 2026-10-10 14:22 UTC (permalink / raw)
To: stefanha, sgarzare, netdev, virtualization, kvm
Cc: mst, jasowangio, eperezma, xuanzhuo, davem, edumazet, kuba,
pabeni, horms, linux-kernel, bpf, Jia Jia
Split virtio_transport_recv_pkt() so socket lookup is separate from the
receive state machine that runs under the socket lock.
Keep virtio_transport_recv_pkt() on its existing per-packet locking path.
Pass the source and destination addresses used for lookup to the locked
receive path so they are decoded only once.
Pass the network namespace and decoded address tuple through a packet
context to the locked receive path.
Preserve source validation and failure handling. Keep the lookup reference
separate from the skb owner reference so the locked receive path can manage
the two lifetimes independently.
Signed-off-by: Jia Jia <physicalmtea@gmail.com>
---
net/vmw_vsock/virtio_transport_common.c | 139 ++++++++++++++++--------
1 file changed, 96 insertions(+), 43 deletions(-)
diff --git a/net/vmw_vsock/virtio_transport_common.c b/net/vmw_vsock/virtio_transport_common.c
index f225f53..e3e75e5 100644
--- a/net/vmw_vsock/virtio_transport_common.c
+++ b/net/vmw_vsock/virtio_transport_common.c
@@ -1774,81 +1774,97 @@ static bool virtio_transport_valid_type(u16 type)
(type == VIRTIO_VSOCK_TYPE_SEQPACKET);
}
-/* We are under the virtio-vsock's vsock->rx_lock or vhost-vsock's vq->mutex
- * lock.
- */
-void virtio_transport_recv_pkt(struct virtio_transport *t,
- struct sk_buff *skb, struct net *net)
+static void
+virtio_transport_recv_pkt_init_addrs(struct sk_buff *skb,
+ struct sockaddr_vm *src,
+ struct sockaddr_vm *dst)
{
struct virtio_vsock_hdr *hdr = virtio_vsock_hdr(skb);
- struct sockaddr_vm src, dst;
- struct vsock_sock *vsk;
- struct sock *sk;
- bool space_available;
- vsock_addr_init(&src, le64_to_cpu(hdr->src_cid),
+ vsock_addr_init(src, le64_to_cpu(hdr->src_cid),
le32_to_cpu(hdr->src_port));
- vsock_addr_init(&dst, le64_to_cpu(hdr->dst_cid),
+ vsock_addr_init(dst, le64_to_cpu(hdr->dst_cid),
le32_to_cpu(hdr->dst_port));
+}
+
+static void
+virtio_transport_trace_recv_pkt(struct sk_buff *skb,
+ const struct sockaddr_vm *src,
+ const struct sockaddr_vm *dst)
+{
+ struct virtio_vsock_hdr *hdr = virtio_vsock_hdr(skb);
- trace_virtio_transport_recv_pkt(src.svm_cid, src.svm_port,
- dst.svm_cid, dst.svm_port,
+ trace_virtio_transport_recv_pkt(src->svm_cid, src->svm_port,
+ dst->svm_cid, dst->svm_port,
le32_to_cpu(hdr->len),
le16_to_cpu(hdr->type),
le16_to_cpu(hdr->op),
le32_to_cpu(hdr->flags),
le32_to_cpu(hdr->buf_alloc),
le32_to_cpu(hdr->fwd_cnt));
+}
- if (!virtio_transport_valid_type(le16_to_cpu(hdr->type))) {
- (void)virtio_transport_reset_no_sock(t, skb, net);
- goto free_pkt;
- }
+static struct sock *
+virtio_transport_recv_pkt_find_socket(struct sk_buff *skb,
+ struct sockaddr_vm *src,
+ struct sockaddr_vm *dst,
+ struct net *net)
+{
+ struct virtio_vsock_hdr *hdr = virtio_vsock_hdr(skb);
+ struct sock *sk;
- /* The socket must be in connected or bound table
- * otherwise send reset back
- */
- sk = vsock_find_connected_socket_net(&src, &dst, net);
- if (!sk) {
- sk = vsock_find_bound_socket_net(&dst, net);
- if (!sk) {
- (void)virtio_transport_reset_no_sock(t, skb, net);
- goto free_pkt;
- }
- }
+ if (!virtio_transport_valid_type(le16_to_cpu(hdr->type)))
+ return NULL;
+
+ sk = vsock_find_connected_socket_net(src, dst, net);
+ if (!sk)
+ sk = vsock_find_bound_socket_net(dst, net);
+ if (!sk)
+ return NULL;
if (virtio_transport_get_type(sk) != le16_to_cpu(hdr->type)) {
- (void)virtio_transport_reset_no_sock(t, skb, net);
sock_put(sk);
- goto free_pkt;
+ return NULL;
}
- if (!skb_set_owner_sk_safe(skb, sk)) {
- WARN_ONCE(1, "receiving vsock socket has sk_refcnt == 0\n");
- goto free_pkt;
- }
+ return sk;
+}
- vsk = vsock_sk(sk);
+struct virtio_transport_rx_pkt_ctx {
+ struct net *net;
+ const struct sockaddr_vm *src;
+ const struct sockaddr_vm *dst;
+};
- lock_sock(sk);
+/*
+ * The caller holds sk's socket lock and must free skb if this returns true.
+ */
+static bool
+virtio_transport_recv_pkt_locked(struct virtio_transport *t,
+ struct sk_buff *skb, struct sock *sk,
+ const struct virtio_transport_rx_pkt_ctx *ctx)
+{
+ const struct sockaddr_vm *src = ctx->src;
+ const struct sockaddr_vm *dst = ctx->dst;
+ struct vsock_sock *vsk = vsock_sk(sk);
+ struct net *net = ctx->net;
+ bool space_available;
- /* Check if sk has been closed or assigned to another transport before
- * lock_sock (note: listener sockets are not assigned to any transport)
+ /* Check after acquiring the socket lock. Listener sockets accept packets
+ * from any source and are not assigned to a transport.
*/
if (sock_flag(sk, SOCK_DONE) ||
(sk->sk_state != TCP_LISTEN &&
- !vsock_check_source(vsk, &t->transport, &src))) {
+ !vsock_check_source(vsk, &t->transport, src))) {
(void)virtio_transport_reset_no_sock(t, skb, net);
- release_sock(sk);
- sock_put(sk);
- goto free_pkt;
+ return true;
}
space_available = virtio_transport_space_update(sk, skb);
/* Update CID in case it has changed after a transport reset event */
if (vsk->local_addr.svm_cid != VMADDR_CID_ANY)
- vsk->local_addr.svm_cid = dst.svm_cid;
+ vsk->local_addr.svm_cid = dst->svm_cid;
if (space_available)
sk->sk_write_space(sk);
@@ -1875,12 +1891,49 @@ void virtio_transport_recv_pkt(struct virtio_transport *t,
break;
}
+ return false;
+}
+
+/* We are under the virtio-vsock's vsock->rx_lock or vhost-vsock's vq->mutex
+ * lock.
+ */
+void virtio_transport_recv_pkt(struct virtio_transport *t,
+ struct sk_buff *skb, struct net *net)
+{
+ struct virtio_transport_rx_pkt_ctx ctx;
+ struct sockaddr_vm src, dst;
+ struct sock *sk;
+ bool free_pkt;
+
+ virtio_transport_recv_pkt_init_addrs(skb, &src, &dst);
+ virtio_transport_trace_recv_pkt(skb, &src, &dst);
+
+ sk = virtio_transport_recv_pkt_find_socket(skb, &src, &dst, net);
+ if (!sk) {
+ (void)virtio_transport_reset_no_sock(t, skb, net);
+ goto free_pkt;
+ }
+
+ if (!skb_set_owner_sk_safe(skb, sk)) {
+ WARN_ONCE(1, "receiving vsock socket has sk_refcnt == 0\n");
+ goto free_pkt;
+ }
+
+ lock_sock(sk);
+ ctx = (struct virtio_transport_rx_pkt_ctx) {
+ .net = net,
+ .src = &src,
+ .dst = &dst,
+ };
+ free_pkt = virtio_transport_recv_pkt_locked(t, skb, sk, &ctx);
release_sock(sk);
/* Release refcnt obtained when we fetched this socket out of the
* bound or connected list.
*/
sock_put(sk);
+ if (free_pkt)
+ kfree_skb(skb);
return;
free_pkt:
--
2.34.1
^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH net-next v2 2/5] vsock/virtio: amortize RX socket locking for stream packets
2026-10-10 14:22 [PATCH net-next v2 0/5] vsock/virtio: reduce RX per-packet socket overhead Jia Jia
2026-10-10 14:22 ` [PATCH net-next v2 1/5] vsock/virtio: split socket lookup from locked RX processing Jia Jia
@ 2026-10-10 14:22 ` Jia Jia
2026-10-10 14:22 ` [PATCH net-next v2 3/5] vsock/virtio: reuse same-flow socket lookup in RX batches Jia Jia
` (2 subsequent siblings)
4 siblings, 0 replies; 6+ messages in thread
From: Jia Jia @ 2026-10-10 14:22 UTC (permalink / raw)
To: stefanha, sgarzare, netdev, virtualization, kvm
Cc: mst, jasowangio, eperezma, xuanzhuo, davem, edumazet, kuba,
pabeni, horms, linux-kernel, bpf, Jia Jia
The virtio-vsock RX worker currently acquires and releases the socket lock
for every received packet. Keep the lock held while processing a bounded
run of packets that belongs to the same socket.
Only ordinary RW packets for an established STREAM socket whose transport
and protocol remain unchanged are eligible. Control packets, SEQPACKET
traffic, state or transport changes, malformed packets, and sockets whose
protocol has been replaced end the active batch and retain their existing
handling.
Serialize the initial eligibility check with sk_callback_lock because
sockmap removal restores the protocol without taking the socket lock.
Limit each batch to 64 packets or 64KB of payload, bounding the work under
one socket lock. Keep the lookup reference until the batch finishes.
Keep a batch only while another skb metadata charge fits in the receive
buffer. Share this predicate with virtio_transport_inc_rx_pkt(). When an
enqueued skb consumes the final metadata slot, finish the batch and release
the socket lock before processing another packet, restoring the reader's
scheduling opportunity from the per-packet path.
The virtqueue callback remains a queue_work() callback. Batching runs only
in the process-context RX worker.
Introduce virtio_transport_rx_batch_finish() to tear down the batch and
release the socket lock and lookup reference.
Signed-off-by: Jia Jia <physicalmtea@gmail.com>
---
include/linux/virtio_vsock.h | 11 ++
net/vmw_vsock/virtio_transport.c | 29 ++++-
net/vmw_vsock/virtio_transport_common.c | 147 +++++++++++++++++++++++-
3 files changed, 183 insertions(+), 4 deletions(-)
diff --git a/include/linux/virtio_vsock.h b/include/linux/virtio_vsock.h
index f91704731057..26dde6909c4e 100644
--- a/include/linux/virtio_vsock.h
+++ b/include/linux/virtio_vsock.h
@@ -282,6 +282,17 @@ void virtio_transport_destruct(struct vsock_sock *vsk);
void virtio_transport_recv_pkt(struct virtio_transport *t,
struct sk_buff *skb, struct net *net);
+
+struct virtio_transport_rx_batch {
+ struct sock *sk;
+ unsigned int pkts;
+ size_t bytes;
+};
+
+void virtio_transport_recv_pkt_batch(struct virtio_transport *t,
+ struct sk_buff *skb, struct net *net,
+ struct virtio_transport_rx_batch *batch);
+void virtio_transport_rx_batch_finish(struct virtio_transport_rx_batch *batch);
void virtio_transport_inc_tx_pkt(struct virtio_vsock_sock *vvs, struct sk_buff *skb);
u32 virtio_transport_get_credit(struct virtio_vsock_sock *vvs, u32 wanted);
void virtio_transport_put_credit(struct virtio_vsock_sock *vvs, u32 credit);
diff --git a/net/vmw_vsock/virtio_transport.c b/net/vmw_vsock/virtio_transport.c
index 4f9aa9c4c3aa..ef076732158d 100644
--- a/net/vmw_vsock/virtio_transport.c
+++ b/net/vmw_vsock/virtio_transport.c
@@ -629,8 +629,16 @@ virtio_transport_seqpacket_allow(struct vsock_sock *vsk, u32 remote_cid)
return seqpacket_allow;
}
+/*
+ * Keep a bounded run of packets for one socket under a single socket lock.
+ * Limit packet count and payload size to bound the work done while locked.
+ */
+#define VIRTIO_TRANSPORT_RX_BATCH_MAX_PKTS 64
+#define VIRTIO_TRANSPORT_RX_BATCH_MAX_BYTES (64 * 1024)
+
static void virtio_transport_rx_work(struct work_struct *work)
{
+ struct virtio_transport_rx_batch batch = {};
struct virtio_vsock *vsock =
container_of(work, struct virtio_vsock, rx_work);
struct virtqueue *vq;
@@ -666,6 +674,7 @@ static void virtio_transport_rx_work(struct work_struct *work)
/* Drop short/long packets */
if (unlikely(len < sizeof(*hdr) ||
len > virtio_vsock_skb_len(skb))) {
+ virtio_transport_rx_batch_finish(&batch);
kfree_skb(skb);
continue;
}
@@ -673,6 +682,7 @@ static void virtio_transport_rx_work(struct work_struct *work)
hdr = virtio_vsock_hdr(skb);
payload_len = le32_to_cpu(hdr->len);
if (unlikely(payload_len > len - sizeof(*hdr))) {
+ virtio_transport_rx_batch_finish(&batch);
kfree_skb(skb);
continue;
}
@@ -680,16 +690,33 @@ static void virtio_transport_rx_work(struct work_struct *work)
if (payload_len)
virtio_vsock_skb_put(skb, payload_len);
+ if (batch.sk &&
+ payload_len > VIRTIO_TRANSPORT_RX_BATCH_MAX_BYTES -
+ batch.bytes) {
+ virtio_transport_rx_batch_finish(&batch);
+ }
+
virtio_transport_deliver_tap_pkt(skb);
/* Force virtio-transport into global mode since it
* does not yet support local-mode namespacing.
*/
- virtio_transport_recv_pkt(&virtio_transport, skb, NULL);
+ virtio_transport_recv_pkt_batch(&virtio_transport, skb, NULL,
+ &batch);
+
+ if (batch.sk) {
+ batch.pkts++;
+ batch.bytes += payload_len;
+ if (batch.pkts >= VIRTIO_TRANSPORT_RX_BATCH_MAX_PKTS ||
+ batch.bytes >= VIRTIO_TRANSPORT_RX_BATCH_MAX_BYTES) {
+ virtio_transport_rx_batch_finish(&batch);
+ }
+ }
}
} while (!virtqueue_enable_cb(vq));
out:
+ virtio_transport_rx_batch_finish(&batch);
if (vsock->rx_buf_nr < vsock->rx_buf_max_nr / 2)
virtio_vsock_rx_fill(vsock);
out_nofill:
diff --git a/net/vmw_vsock/virtio_transport_common.c b/net/vmw_vsock/virtio_transport_common.c
index e3e75e59896f..a73e37b3fa62 100644
--- a/net/vmw_vsock/virtio_transport_common.c
+++ b/net/vmw_vsock/virtio_transport_common.c
@@ -576,10 +576,16 @@ virtio_transport_collapse_rx_queue(struct virtio_vsock_sock *vvs,
skb_queue_splice(&new_queue, &vvs->rx_queue);
}
+static bool
+virtio_transport_rx_skb_has_headroom(struct virtio_vsock_sock *vvs)
+{
+ return (u64)(skb_queue_len(&vvs->rx_queue) + 1) * SKB_TRUESIZE(0) <=
+ vvs->buf_alloc;
+}
+
static bool virtio_transport_inc_rx_pkt(struct virtio_vsock_sock *vvs,
u32 len)
{
- u64 skb_overhead = (skb_queue_len(&vvs->rx_queue) + 1) * SKB_TRUESIZE(0);
/* Allow at most buf_alloc * 2 total budget (payload + overhead),
* similar to how SO_RCVBUF is doubled to reserve space for sk_buff
@@ -588,7 +594,7 @@ static bool virtio_transport_inc_rx_pkt(struct virtio_vsock_sock *vvs,
* queue growth.
*/
if ((u64)vvs->buf_used + len > vvs->buf_alloc ||
- skb_overhead > vvs->buf_alloc)
+ !virtio_transport_rx_skb_has_headroom(vvs))
return false;
vvs->rx_bytes += len;
@@ -1834,10 +1840,28 @@ struct virtio_transport_rx_pkt_ctx {
struct net *net;
const struct sockaddr_vm *src;
const struct sockaddr_vm *dst;
+ bool *batchable;
};
+static bool
+virtio_transport_recv_pkt_batchable(struct virtio_transport *t,
+ struct sock *sk)
+{
+ struct vsock_sock *vsk = vsock_sk(sk);
+ struct virtio_vsock_sock *vvs = vsk->trans;
+
+ return sk->sk_state == TCP_ESTABLISHED &&
+ sk->sk_type == SOCK_STREAM &&
+ READ_ONCE(sk->sk_prot) == sk->sk_prot_creator &&
+ !sock_flag(sk, SOCK_DONE) &&
+ vsk->transport == &t->transport &&
+ vvs && virtio_transport_rx_skb_has_headroom(vvs);
+}
+
/*
- * The caller holds sk's socket lock and must free skb if this returns true.
+ * The caller holds sk's socket lock. Set @batchable if the socket can remain
+ * locked for another ordinary STREAM/RW packet. Return true if the caller
+ * must free @skb.
*/
static bool
virtio_transport_recv_pkt_locked(struct virtio_transport *t,
@@ -1850,6 +1874,9 @@ virtio_transport_recv_pkt_locked(struct virtio_transport *t,
struct net *net = ctx->net;
bool space_available;
+ if (ctx->batchable)
+ *ctx->batchable = false;
+
/* Check after acquiring the socket lock. Listener sockets accept packets
* from any source and are not assigned to a transport.
*/
@@ -1891,6 +1918,9 @@ virtio_transport_recv_pkt_locked(struct virtio_transport *t,
break;
}
+ if (ctx->batchable)
+ *ctx->batchable = virtio_transport_recv_pkt_batchable(t, sk);
+
return false;
}
@@ -1941,6 +1971,117 @@ void virtio_transport_recv_pkt(struct virtio_transport *t,
}
EXPORT_SYMBOL_GPL(virtio_transport_recv_pkt);
+/*
+ * Finish the RX batch.
+ * For a non-empty batch, the caller must hold the socket lock acquired with
+ * lock_sock(). This function releases the lock and the batch's lookup
+ * reference.
+ * An empty batch is a no-op.
+ */
+void virtio_transport_rx_batch_finish(struct virtio_transport_rx_batch *batch)
+{
+ struct sock *sk = batch->sk;
+
+ batch->sk = NULL;
+ batch->pkts = 0;
+ batch->bytes = 0;
+
+ if (!sk)
+ return;
+
+ release_sock(sk);
+ sock_put(sk);
+}
+EXPORT_SYMBOL_GPL(virtio_transport_rx_batch_finish);
+
+void virtio_transport_recv_pkt_batch(struct virtio_transport *t,
+ struct sk_buff *skb, struct net *net,
+ struct virtio_transport_rx_batch *batch)
+{
+ struct virtio_vsock_hdr *hdr = virtio_vsock_hdr(skb);
+ struct virtio_transport_rx_pkt_ctx ctx;
+ struct sockaddr_vm src, dst;
+ bool batchable, start_batch;
+ struct sock *sk;
+ bool free_pkt;
+
+ /* Only STREAM/RW packets can share a socket lock. */
+ if (le16_to_cpu(hdr->type) != VIRTIO_VSOCK_TYPE_STREAM ||
+ le16_to_cpu(hdr->op) != VIRTIO_VSOCK_OP_RW) {
+ virtio_transport_rx_batch_finish(batch);
+ virtio_transport_recv_pkt(t, skb, net);
+ return;
+ }
+
+ virtio_transport_recv_pkt_init_addrs(skb, &src, &dst);
+ virtio_transport_trace_recv_pkt(skb, &src, &dst);
+
+ sk = virtio_transport_recv_pkt_find_socket(skb, &src, &dst, net);
+ if (!sk) {
+ virtio_transport_rx_batch_finish(batch);
+ (void)virtio_transport_reset_no_sock(t, skb, net);
+ kfree_skb(skb);
+ return;
+ }
+
+ if (!skb_set_owner_sk_safe(skb, sk)) {
+ WARN_ONCE(1, "receiving vsock socket has sk_refcnt == 0\n");
+ virtio_transport_rx_batch_finish(batch);
+ kfree_skb(skb);
+ return;
+ }
+
+ if (batch->sk && batch->sk != sk) {
+ /* Never acquire a second socket lock. */
+ virtio_transport_rx_batch_finish(batch);
+ }
+
+ if (batch->sk == sk) {
+ /* Keep the batch reference; drop this packet's lookup reference. */
+ sock_put(sk);
+ ctx = (struct virtio_transport_rx_pkt_ctx) {
+ .net = net,
+ .src = &src,
+ .dst = &dst,
+ .batchable = &batchable,
+ };
+ free_pkt = virtio_transport_recv_pkt_locked(t, skb, sk, &ctx);
+ if (!batchable)
+ virtio_transport_rx_batch_finish(batch);
+ if (free_pkt)
+ kfree_skb(skb);
+ return;
+ }
+
+ lock_sock(sk);
+ /*
+ * Sockmap removal restores the native protocol under sk_callback_lock.
+ * Serialize this initial eligibility check with that update.
+ */
+ read_lock_bh(&sk->sk_callback_lock);
+ start_batch = virtio_transport_recv_pkt_batchable(t, sk);
+ read_unlock_bh(&sk->sk_callback_lock);
+
+ ctx = (struct virtio_transport_rx_pkt_ctx) {
+ .net = net,
+ .src = &src,
+ .dst = &dst,
+ .batchable = start_batch ? &batchable : NULL,
+ };
+ free_pkt = virtio_transport_recv_pkt_locked(t, skb, sk, &ctx);
+ if (start_batch && batchable) {
+ /* Keep the lookup reference until the batch is released. */
+ batch->sk = sk;
+ return;
+ }
+
+ release_sock(sk);
+ sock_put(sk);
+ if (free_pkt)
+ kfree_skb(skb);
+}
+EXPORT_SYMBOL_GPL(virtio_transport_recv_pkt_batch);
+
/* Remove skbs found in a queue that have a vsk that matches.
*
* Each skb is freed.
--
2.34.1
^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH net-next v2 3/5] vsock/virtio: reuse same-flow socket lookup in RX batches
2026-10-10 14:22 [PATCH net-next v2 0/5] vsock/virtio: reduce RX per-packet socket overhead Jia Jia
2026-10-10 14:22 ` [PATCH net-next v2 1/5] vsock/virtio: split socket lookup from locked RX processing Jia Jia
2026-10-10 14:22 ` [PATCH net-next v2 2/5] vsock/virtio: amortize RX socket locking for stream packets Jia Jia
@ 2026-10-10 14:22 ` Jia Jia
2026-10-10 14:22 ` [PATCH net-next v2 4/5] vsock/virtio: coalesce RX write-space notifications in lock batches Jia Jia
2026-10-10 14:22 ` [PATCH net-next v2 5/5] vsock/virtio: defer RX readable notifications until batch unlock Jia Jia
4 siblings, 0 replies; 6+ messages in thread
From: Jia Jia @ 2026-10-10 14:22 UTC (permalink / raw)
To: stefanha, sgarzare, netdev, virtualization, kvm
Cc: mst, jasowangio, eperezma, xuanzhuo, davem, edumazet, kuba,
pabeni, horms, linux-kernel, bpf, Jia Jia
An RX lock batch still looks up the socket for every packet and takes a
temporary lookup reference, even though the batch already holds a reference
to the locked socket.
Record the network namespace and packet address tuple when a batch starts.
Reuse the batch socket for later STREAM/RW packets with the same tuple, and
release the batch before looking up a different flow.
The cached path still traces every packet, takes an skb owner reference,
validates socket state, source and transport, updates credit, and runs the
receive state machine. Only the socket table lookup and its temporary
reference are skipped.
Signed-off-by: Jia Jia <physicalmtea@gmail.com>
---
include/linux/virtio_vsock.h | 3 ++
net/vmw_vsock/virtio_transport_common.c | 58 +++++++++++++++----------
2 files changed, 38 insertions(+), 23 deletions(-)
diff --git a/include/linux/virtio_vsock.h b/include/linux/virtio_vsock.h
index 26dde6909c4e..d6528681e052 100644
--- a/include/linux/virtio_vsock.h
+++ b/include/linux/virtio_vsock.h
@@ -287,6 +287,9 @@ struct virtio_transport_rx_batch {
struct sock *sk;
unsigned int pkts;
size_t bytes;
+ struct net *net;
+ struct sockaddr_vm src;
+ struct sockaddr_vm dst;
};
void virtio_transport_recv_pkt_batch(struct virtio_transport *t,
diff --git a/net/vmw_vsock/virtio_transport_common.c b/net/vmw_vsock/virtio_transport_common.c
index e67dfcaa279c..73e5c7dfe8b4 100644
--- a/net/vmw_vsock/virtio_transport_common.c
+++ b/net/vmw_vsock/virtio_transport_common.c
@@ -1977,6 +1977,7 @@ void virtio_transport_rx_batch_finish(struct virtio_transport_rx_batch *batch)
batch->sk = NULL;
batch->pkts = 0;
batch->bytes = 0;
+ batch->net = NULL;
if (!sk)
return;
@@ -2008,6 +2009,37 @@ void virtio_transport_recv_pkt_batch(struct virtio_transport *t,
virtio_transport_recv_pkt_init_addrs(skb, &src, &dst);
virtio_transport_trace_recv_pkt(skb, &src, &dst);
+ if (batch->sk) {
+ if (batch->net == net &&
+ vsock_addr_equals_addr(&batch->src, &src) &&
+ vsock_addr_equals_addr(&batch->dst, &dst) &&
+ virtio_transport_recv_pkt_batchable(t, batch->sk)) {
+ sk = batch->sk;
+ if (!skb_set_owner_sk_safe(skb, sk)) {
+ WARN_ONCE(1, "receiving vsock socket has sk_refcnt == 0\n");
+ virtio_transport_rx_batch_finish(batch);
+ kfree_skb(skb);
+ return;
+ }
+
+ ctx = (struct virtio_transport_rx_pkt_ctx) {
+ .net = net,
+ .src = &src,
+ .dst = &dst,
+ .batchable = &batchable,
+ };
+ free_pkt = virtio_transport_recv_pkt_locked(t, skb, sk, &ctx);
+
+ if (!batchable)
+ virtio_transport_rx_batch_finish(batch);
+ if (free_pkt)
+ kfree_skb(skb);
+ return;
+ }
+
+ virtio_transport_rx_batch_finish(batch);
+ }
+
sk = virtio_transport_recv_pkt_find_socket(skb, &src, &dst, net);
if (!sk) {
virtio_transport_rx_batch_finish(batch);
@@ -2018,33 +2050,10 @@ void virtio_transport_recv_pkt_batch(struct virtio_transport *t,
if (!skb_set_owner_sk_safe(skb, sk)) {
WARN_ONCE(1, "receiving vsock socket has sk_refcnt == 0\n");
- virtio_transport_rx_batch_finish(batch);
kfree_skb(skb);
return;
}
- if (batch->sk && batch->sk != sk) {
- /* Never acquire a second socket lock. */
- virtio_transport_rx_batch_finish(batch);
- }
-
- if (batch->sk == sk) {
- /* Keep the batch reference; drop this packet's lookup reference. */
- sock_put(sk);
- ctx = (struct virtio_transport_rx_pkt_ctx) {
- .net = net,
- .src = &src,
- .dst = &dst,
- .batchable = &batchable,
- };
- free_pkt = virtio_transport_recv_pkt_locked(t, skb, sk, &ctx);
- if (!batchable)
- virtio_transport_rx_batch_finish(batch);
- if (free_pkt)
- kfree_skb(skb);
- return;
- }
-
lock_sock(sk);
/*
* Sockmap removal restores the native protocol under sk_callback_lock.
@@ -2063,6 +2072,9 @@ void virtio_transport_recv_pkt_batch(struct virtio_transport *t,
free_pkt = virtio_transport_recv_pkt_locked(t, skb, sk, &ctx);
if (start_batch && batchable) {
/* Keep the lookup reference until the batch is released. */
+ batch->net = net;
+ batch->src = src;
+ batch->dst = dst;
batch->sk = sk;
return;
}
--
2.34.1
^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH net-next v2 4/5] vsock/virtio: coalesce RX write-space notifications in lock batches
2026-10-10 14:22 [PATCH net-next v2 0/5] vsock/virtio: reduce RX per-packet socket overhead Jia Jia
` (2 preceding siblings ...)
2026-10-10 14:22 ` [PATCH net-next v2 3/5] vsock/virtio: reuse same-flow socket lookup in RX batches Jia Jia
@ 2026-10-10 14:22 ` Jia Jia
2026-10-10 14:22 ` [PATCH net-next v2 5/5] vsock/virtio: defer RX readable notifications until batch unlock Jia Jia
4 siblings, 0 replies; 6+ messages in thread
From: Jia Jia @ 2026-10-10 14:22 UTC (permalink / raw)
To: stefanha, sgarzare, netdev, virtualization, kvm
Cc: mst, jasowangio, eperezma, xuanzhuo, davem, edumazet, kuba,
pabeni, horms, linux-kernel, bpf, Jia Jia
Each received packet updates peer credit and calls sk_write_space() when
send space is available. A writer cannot use the newly advertised credit
until the socket lock is released, so repeated callbacks within one lock
batch cannot let it make progress sooner.
For the callback installed by sock_init_data(), record one pending
write-space notification and deliver it through the saved default callback
before release_sock(). If the callback has been replaced by batch finish,
invoke the replacement as well. This keeps the native writer wakeup from
being lost across a sockmap callback change while still notifying sockmap.
Save the initial sock_def_write_space() callback when the AF_VSOCK socket
is created because it is not visible to virtio_transport_common when built
as a module. The fast path uses READ_ONCE() and adds no callback lock.
Set the batch socket before processing its first packet so a batch ending
on that packet cannot lose the notification.
Packets outside the eligible STREAM/RW batch path retain per-packet
notification behavior. The 64-packet and 64K limits cap the packets whose
notifications can be coalesced.
Signed-off-by: Jia Jia <physicalmtea@gmail.com>
---
include/linux/virtio_vsock.h | 1 +
include/net/af_vsock.h | 2 ++
net/vmw_vsock/af_vsock.c | 1 +
net/vmw_vsock/virtio_transport_common.c | 42 ++++++++++++++++++++++---
4 files changed, 41 insertions(+), 5 deletions(-)
diff --git a/include/linux/virtio_vsock.h b/include/linux/virtio_vsock.h
index d6528681e052..3a58120d9078 100644
--- a/include/linux/virtio_vsock.h
+++ b/include/linux/virtio_vsock.h
@@ -290,6 +290,7 @@ struct virtio_transport_rx_batch {
struct net *net;
struct sockaddr_vm src;
struct sockaddr_vm dst;
+ bool write_space_pending;
};
void virtio_transport_recv_pkt_batch(struct virtio_transport *t,
diff --git a/include/net/af_vsock.h b/include/net/af_vsock.h
index 5549298c1ec6..9d8ae62209ed 100644
--- a/include/net/af_vsock.h
+++ b/include/net/af_vsock.h
@@ -63,6 +63,8 @@ struct vsock_sock {
u32 peer_shutdown;
bool sent_request;
bool ignore_connecting_rst;
+ /* Initial callback, used to identify replacements. */
+ void (*default_write_space)(struct sock *sk);
/* Protected by lock_sock(sk) */
u64 buffer_size;
diff --git a/net/vmw_vsock/af_vsock.c b/net/vmw_vsock/af_vsock.c
index 44cee7451955..fe7f49da6c4b 100644
--- a/net/vmw_vsock/af_vsock.c
+++ b/net/vmw_vsock/af_vsock.c
@@ -958,6 +958,7 @@ static struct sock *__vsock_create(struct net *net,
sk->sk_type = type;
vsk = vsock_sk(sk);
+ vsk->default_write_space = sk->sk_write_space;
vsock_addr_init(&vsk->local_addr, VMADDR_CID_ANY, VMADDR_PORT_ANY);
vsock_addr_init(&vsk->remote_addr, VMADDR_CID_ANY, VMADDR_PORT_ANY);
diff --git a/net/vmw_vsock/virtio_transport_common.c b/net/vmw_vsock/virtio_transport_common.c
index 73e5c7dfe8b4..f0398ae2200d 100644
--- a/net/vmw_vsock/virtio_transport_common.c
+++ b/net/vmw_vsock/virtio_transport_common.c
@@ -1835,6 +1835,7 @@ struct virtio_transport_rx_pkt_ctx {
const struct sockaddr_vm *src;
const struct sockaddr_vm *dst;
bool *batchable;
+ struct virtio_transport_rx_batch *batch;
};
static bool
@@ -1863,6 +1864,7 @@ virtio_transport_recv_pkt_locked(struct virtio_transport *t,
const struct sockaddr_vm *src = ctx->src;
const struct sockaddr_vm *dst = ctx->dst;
struct vsock_sock *vsk = vsock_sk(sk);
+ void (*write_space)(struct sock *sk);
struct net *net = ctx->net;
bool space_available;
@@ -1885,8 +1887,17 @@ virtio_transport_recv_pkt_locked(struct virtio_transport *t,
if (vsk->local_addr.svm_cid != VMADDR_CID_ANY)
vsk->local_addr.svm_cid = dst->svm_cid;
- if (space_available)
- sk->sk_write_space(sk);
+ if (space_available) {
+ write_space = READ_ONCE(sk->sk_write_space);
+ if (ctx->batch &&
+ write_space == vsk->default_write_space &&
+ virtio_transport_recv_pkt_batchable(t, sk)) {
+ ctx->batch->write_space_pending = true;
+ } else {
+ /* Use the callback seen for this packet. */
+ write_space(sk);
+ }
+ }
switch (sk->sk_state) {
case TCP_LISTEN:
@@ -1972,16 +1983,28 @@ EXPORT_SYMBOL_GPL(virtio_transport_recv_pkt);
*/
void virtio_transport_rx_batch_finish(struct virtio_transport_rx_batch *batch)
{
+ bool write_space_pending = batch->write_space_pending;
+ void (*write_space)(struct sock *sk);
struct sock *sk = batch->sk;
+ struct vsock_sock *vsk;
batch->sk = NULL;
batch->pkts = 0;
batch->bytes = 0;
batch->net = NULL;
+ batch->write_space_pending = false;
if (!sk)
return;
+ if (write_space_pending) {
+ vsk = vsock_sk(sk);
+ vsk->default_write_space(sk);
+ write_space = READ_ONCE(sk->sk_write_space);
+ if (write_space != vsk->default_write_space)
+ write_space(sk);
+ }
+
release_sock(sk);
sock_put(sk);
}
@@ -2027,6 +2050,7 @@ void virtio_transport_recv_pkt_batch(struct virtio_transport *t,
.src = &src,
.dst = &dst,
.batchable = &batchable,
+ .batch = batch,
};
free_pkt = virtio_transport_recv_pkt_locked(t, skb, sk, &ctx);
@@ -2063,11 +2087,15 @@ void virtio_transport_recv_pkt_batch(struct virtio_transport *t,
start_batch = virtio_transport_recv_pkt_batchable(t, sk);
read_unlock_bh(&sk->sk_callback_lock);
+ if (start_batch)
+ batch->sk = sk;
+
ctx = (struct virtio_transport_rx_pkt_ctx) {
.net = net,
.src = &src,
.dst = &dst,
.batchable = start_batch ? &batchable : NULL,
+ .batch = start_batch ? batch : NULL,
};
free_pkt = virtio_transport_recv_pkt_locked(t, skb, sk, &ctx);
if (start_batch && batchable) {
@@ -2075,12 +2103,16 @@ void virtio_transport_recv_pkt_batch(struct virtio_transport *t,
batch->net = net;
batch->src = src;
batch->dst = dst;
- batch->sk = sk;
return;
}
- release_sock(sk);
- sock_put(sk);
+ if (start_batch) {
+ virtio_transport_rx_batch_finish(batch);
+ } else {
+ release_sock(sk);
+ sock_put(sk);
+ }
+
if (free_pkt)
kfree_skb(skb);
}
--
2.34.1
^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH net-next v2 5/5] vsock/virtio: defer RX readable notifications until batch unlock
2026-10-10 14:22 [PATCH net-next v2 0/5] vsock/virtio: reduce RX per-packet socket overhead Jia Jia
` (3 preceding siblings ...)
2026-10-10 14:22 ` [PATCH net-next v2 4/5] vsock/virtio: coalesce RX write-space notifications in lock batches Jia Jia
@ 2026-10-10 14:22 ` Jia Jia
4 siblings, 0 replies; 6+ messages in thread
From: Jia Jia @ 2026-10-10 14:22 UTC (permalink / raw)
To: stefanha, sgarzare, netdev, virtualization, kvm
Cc: mst, jasowangio, eperezma, xuanzhuo, davem, edumazet, kuba,
pabeni, horms, linux-kernel, bpf, Jia Jia
An RX batch can make a socket readable while the worker still owns its
socket lock. Waking a reader at that point can make it immediately wait
for the same lock.
For the callback installed by sock_init_data(), record when the existing
low-watermark condition is met and issue one notification after the batch
releases the lock. Save the initial sock_def_readable() callback when the
AF_VSOCK socket is created because virtio_transport_common can be built as
a module and cannot refer to that unexported symbol directly.
When sk_data_ready or sk_write_space no longer matches that initial
callback, pass queued skbs to the current callback one skb at a time. The
handoff stops if the callback is the default again or the queue does not
shrink. After 64 successful callbacks, queue another bounded handoff if
data remains. vsock_bpf_update_proto() schedules the same handoff so an
attach or detach outside the worker still drains skbs already queued.
Always deliver a coalesced write-space event to the saved default callback
before sampling the callback state. A current replacement is invoked by
the handoff as well, so a callback change cannot hide the native writer
wakeup or pending sockmap work.
Sampling at batch finish takes sk_callback_lock once. The per-packet fast
path still uses READ_ONCE() and does not take that lock. Control packets,
state transitions, transport changes and non-batched sockets keep their
existing behavior.
Signed-off-by: Jia Jia <physicalmtea@gmail.com>
---
include/linux/virtio_vsock.h | 1 +
include/net/af_vsock.h | 8 +-
net/vmw_vsock/af_vsock.c | 103 ++++++++++++++++++++++++
net/vmw_vsock/virtio_transport_common.c | 73 ++++++++++++++---
net/vmw_vsock/vsock_bpf.c | 2 +
5 files changed, 174 insertions(+), 13 deletions(-)
diff --git a/include/linux/virtio_vsock.h b/include/linux/virtio_vsock.h
index 3a58120d9078..21a3460b940f 100644
--- a/include/linux/virtio_vsock.h
+++ b/include/linux/virtio_vsock.h
@@ -291,6 +291,7 @@ struct virtio_transport_rx_batch {
struct sockaddr_vm src;
struct sockaddr_vm dst;
bool write_space_pending;
+ bool data_ready_pending;
};
void virtio_transport_recv_pkt_batch(struct virtio_transport *t,
diff --git a/include/net/af_vsock.h b/include/net/af_vsock.h
index 9d8ae62209ed..86d6e6ba3d9d 100644
--- a/include/net/af_vsock.h
+++ b/include/net/af_vsock.h
@@ -63,8 +63,11 @@ struct vsock_sock {
u32 peer_shutdown;
bool sent_request;
bool ignore_connecting_rst;
- /* Initial callback, used to identify replacements. */
+ /* Initial callbacks, used to identify replacements. */
+ void (*default_data_ready)(struct sock *sk);
void (*default_write_space)(struct sock *sk);
+ struct delayed_work rx_cb_work;
+ bool rx_cb_retried;
/* Protected by lock_sock(sk) */
u64 buffer_size;
@@ -80,6 +83,9 @@ s64 vsock_stream_has_data(struct vsock_sock *vsk);
s64 vsock_stream_has_space(struct vsock_sock *vsk);
struct sock *vsock_create_connected(struct sock *parent);
void vsock_data_ready(struct sock *sk);
+bool vsock_rx_cb_is_native(struct sock *sk);
+void vsock_rx_callback_kick(struct sock *sk);
+void vsock_rx_callback_handoff(struct sock *sk);
/**** TRANSPORT ****/
diff --git a/net/vmw_vsock/af_vsock.c b/net/vmw_vsock/af_vsock.c
index fe7f49da6c4b..df6faee0bd37 100644
--- a/net/vmw_vsock/af_vsock.c
+++ b/net/vmw_vsock/af_vsock.c
@@ -931,6 +931,107 @@ static int __vsock_bind(struct sock *sk, struct sockaddr_vm *addr)
return retval;
}
+#define VSOCK_RX_CB_HANDOFF_MAX 64
+
+static void vsock_rx_callback_queue(struct sock *sk, unsigned long delay)
+{
+ struct vsock_sock *vsk = vsock_sk(sk);
+
+ sock_hold(sk);
+ if (!schedule_delayed_work(&vsk->rx_cb_work, delay))
+ sock_put(sk);
+}
+
+void vsock_rx_callback_kick(struct sock *sk)
+{
+ vsock_rx_callback_queue(sk, 0);
+}
+
+/* Caller holds sk_callback_lock. */
+bool vsock_rx_cb_is_native(struct sock *sk)
+{
+ struct vsock_sock *vsk = vsock_sk(sk);
+
+ return READ_ONCE(sk->sk_prot) == sk->sk_prot_creator &&
+ READ_ONCE(sk->sk_data_ready) == vsk->default_data_ready &&
+ READ_ONCE(sk->sk_write_space) == vsk->default_write_space;
+}
+EXPORT_SYMBOL_GPL(vsock_rx_cb_is_native);
+
+/* Caller holds lock_sock(). */
+void vsock_rx_callback_handoff(struct sock *sk)
+{
+ struct vsock_sock *vsk = vsock_sk(sk);
+ void (*write_space)(struct sock *sk);
+ void (*data_ready)(struct sock *sk);
+ bool wrote = false;
+ s64 before, after;
+ int i;
+
+ if (sock_flag(sk, SOCK_DEAD) || !vsk->transport)
+ return;
+
+ read_lock_bh(&sk->sk_callback_lock);
+ data_ready = READ_ONCE(sk->sk_data_ready);
+ if (READ_ONCE(sk->sk_prot) != sk->sk_prot_creator &&
+ data_ready == vsk->default_data_ready) {
+ read_unlock_bh(&sk->sk_callback_lock);
+ /*
+ * start_verdict publishes sk_data_ready after the proto swap.
+ * Retry once before treating this as a stable non-RX setup.
+ */
+ if (!vsk->rx_cb_retried) {
+ vsk->rx_cb_retried = true;
+ vsock_rx_callback_queue(sk, 1);
+ return;
+ }
+ vsk->rx_cb_retried = false;
+ data_ready(sk);
+ return;
+ }
+ read_unlock_bh(&sk->sk_callback_lock);
+ vsk->rx_cb_retried = false;
+
+ for (i = 0; i < VSOCK_RX_CB_HANDOFF_MAX; i++) {
+ read_lock_bh(&sk->sk_callback_lock);
+ data_ready = READ_ONCE(sk->sk_data_ready);
+ write_space = READ_ONCE(sk->sk_write_space);
+ read_unlock_bh(&sk->sk_callback_lock);
+
+ if (!wrote && write_space != vsk->default_write_space) {
+ write_space(sk);
+ wrote = true;
+ }
+ if (data_ready == vsk->default_data_ready) {
+ data_ready(sk);
+ break;
+ }
+ before = vsock_stream_has_data(vsk);
+ if (before <= 0)
+ break;
+ data_ready(sk);
+ after = vsock_stream_has_data(vsk);
+ if (after >= before)
+ break;
+ if (i == VSOCK_RX_CB_HANDOFF_MAX - 1 && after > 0)
+ vsock_rx_callback_queue(sk, 0);
+ }
+}
+EXPORT_SYMBOL_GPL(vsock_rx_callback_handoff);
+
+static void vsock_rx_callback_work(struct work_struct *work)
+{
+ struct vsock_sock *vsk = container_of(work, struct vsock_sock,
+ rx_cb_work.work);
+ struct sock *sk = sk_vsock(vsk);
+
+ lock_sock(sk);
+ if (!sock_flag(sk, SOCK_DEAD))
+ vsock_rx_callback_handoff(sk);
+ release_sock(sk);
+ sock_put(sk);
+}
+
static void vsock_connect_timeout(struct work_struct *work);
static struct sock *__vsock_create(struct net *net,
@@ -958,6 +1059,7 @@ static struct sock *__vsock_create(struct net *net,
sk->sk_type = type;
vsk = vsock_sk(sk);
+ vsk->default_data_ready = sk->sk_data_ready;
vsk->default_write_space = sk->sk_write_space;
vsock_addr_init(&vsk->local_addr, VMADDR_CID_ANY, VMADDR_PORT_ANY);
vsock_addr_init(&vsk->remote_addr, VMADDR_CID_ANY, VMADDR_PORT_ANY);
@@ -976,6 +1078,7 @@ static struct sock *__vsock_create(struct net *net,
WRITE_ONCE(vsk->peer_shutdown, 0);
INIT_DELAYED_WORK(&vsk->connect_work, vsock_connect_timeout);
INIT_DELAYED_WORK(&vsk->pending_work, vsock_pending_work);
+ INIT_DELAYED_WORK(&vsk->rx_cb_work, vsock_rx_callback_work);
psk = parent ? vsock_sk(parent) : NULL;
if (parent) {
diff --git a/net/vmw_vsock/virtio_transport_common.c b/net/vmw_vsock/virtio_transport_common.c
index f0398ae2200d..5499cd1d8a25 100644
--- a/net/vmw_vsock/virtio_transport_common.c
+++ b/net/vmw_vsock/virtio_transport_common.c
@@ -1583,10 +1583,12 @@ out:
static int
virtio_transport_recv_connected(struct sock *sk,
- struct sk_buff *skb)
+ struct sk_buff *skb,
+ bool *data_ready_pending)
{
struct virtio_vsock_hdr *hdr = virtio_vsock_hdr(skb);
struct vsock_sock *vsk = vsock_sk(sk);
+ void (*data_ready)(struct sock *sk);
int err = 0;
switch (le16_to_cpu(hdr->op)) {
@@ -1602,7 +1604,23 @@ virtio_transport_recv_connected(struct sock *sk,
vsock_remove_sock(vsk);
break;
}
- vsock_data_ready(sk);
+ if (!data_ready_pending) {
+ vsock_data_ready(sk);
+ } else {
+ data_ready = READ_ONCE(sk->sk_data_ready);
+ if (data_ready == vsk->default_data_ready) {
+ if (!*data_ready_pending &&
+ (vsock_stream_has_data(vsk) >= sk->sk_rcvlowat ||
+ sock_flag(sk, SOCK_DONE)))
+ *data_ready_pending = true;
+ } else {
+ /* Use the callback seen for this packet. */
+ *data_ready_pending = false;
+ if (vsock_stream_has_data(vsk) >= sk->sk_rcvlowat ||
+ sock_flag(sk, SOCK_DONE))
+ data_ready(sk);
+ }
+ }
return err;
case VIRTIO_VSOCK_OP_CREDIT_REQUEST:
virtio_transport_send_credit_update(vsk);
@@ -1836,6 +1854,7 @@ struct virtio_transport_rx_pkt_ctx {
const struct sockaddr_vm *dst;
bool *batchable;
struct virtio_transport_rx_batch *batch;
+ bool defer_data_ready;
};
static bool
@@ -1909,7 +1928,9 @@ virtio_transport_recv_pkt_locked(struct virtio_transport *t,
kfree_skb(skb);
break;
case TCP_ESTABLISHED:
- virtio_transport_recv_connected(sk, skb);
+ virtio_transport_recv_connected(sk, skb,
+ ctx->defer_data_ready && ctx->batch ?
+ &ctx->batch->data_ready_pending : NULL);
break;
case TCP_CLOSING:
virtio_transport_recv_disconnecting(sk, skb);
@@ -1978,33 +1999,45 @@ EXPORT_SYMBOL_GPL(virtio_transport_recv_pkt);
* Finish the RX batch.
* For a non-empty batch, the caller must hold the socket lock acquired with
* lock_sock(). This function releases the lock and the batch's lookup
- * reference.
- * An empty batch is a no-op.
+ * reference. An empty batch is a no-op.
*/
void virtio_transport_rx_batch_finish(struct virtio_transport_rx_batch *batch)
{
bool write_space_pending = batch->write_space_pending;
- void (*write_space)(struct sock *sk);
+ bool data_ready_pending = batch->data_ready_pending;
struct sock *sk = batch->sk;
struct vsock_sock *vsk;
+ bool native;
batch->sk = NULL;
batch->pkts = 0;
batch->bytes = 0;
batch->net = NULL;
batch->write_space_pending = false;
+ batch->data_ready_pending = false;
if (!sk)
return;
- if (write_space_pending) {
- vsk = vsock_sk(sk);
+ vsk = vsock_sk(sk);
+ if (write_space_pending)
vsk->default_write_space(sk);
- write_space = READ_ONCE(sk->sk_write_space);
- if (write_space != vsk->default_write_space)
- write_space(sk);
+
+ read_lock_bh(&sk->sk_callback_lock);
+ native = vsock_rx_cb_is_native(sk);
+ read_unlock_bh(&sk->sk_callback_lock);
+
+ if (native) {
+ release_sock(sk);
+ if (data_ready_pending)
+ vsk->default_data_ready(sk);
+ sock_put(sk);
+ return;
}
+ if (write_space_pending || data_ready_pending)
+ vsock_rx_callback_handoff(sk);
+
release_sock(sk);
sock_put(sk);
}
@@ -2015,9 +2048,9 @@ void virtio_transport_recv_pkt_batch(struct virtio_transport *t,
struct virtio_transport_rx_batch *batch)
{
struct virtio_vsock_hdr *hdr = virtio_vsock_hdr(skb);
+ bool batchable, defer_data_ready, start_batch;
struct virtio_transport_rx_pkt_ctx ctx;
struct sockaddr_vm src, dst;
- bool batchable, start_batch;
struct sock *sk;
bool free_pkt;
@@ -2038,6 +2071,17 @@ void virtio_transport_recv_pkt_batch(struct virtio_transport *t,
vsock_addr_equals_addr(&batch->dst, &dst) &&
virtio_transport_recv_pkt_batchable(t, batch->sk)) {
sk = batch->sk;
+ defer_data_ready = READ_ONCE(sk->sk_data_ready) ==
+ vsock_sk(sk)->default_data_ready;
+ if (unlikely(batch->data_ready_pending && !defer_data_ready)) {
+ /*
+ * BPF map updates can replace callbacks while lock_sock()
+ * remains held. Finish the old notification first.
+ */
+ virtio_transport_rx_batch_finish(batch);
+ goto lookup;
+ }
+
if (!skb_set_owner_sk_safe(skb, sk)) {
WARN_ONCE(1, "receiving vsock socket has sk_refcnt == 0\n");
virtio_transport_rx_batch_finish(batch);
@@ -2051,6 +2095,7 @@ void virtio_transport_recv_pkt_batch(struct virtio_transport *t,
.dst = &dst,
.batchable = &batchable,
.batch = batch,
+ .defer_data_ready = defer_data_ready,
};
free_pkt = virtio_transport_recv_pkt_locked(t, skb, sk, &ctx);
@@ -2064,6 +2109,7 @@ void virtio_transport_recv_pkt_batch(struct virtio_transport *t,
virtio_transport_rx_batch_finish(batch);
}
+lookup:
sk = virtio_transport_recv_pkt_find_socket(skb, &src, &dst, net);
if (!sk) {
virtio_transport_rx_batch_finish(batch);
@@ -2089,6 +2135,8 @@ void virtio_transport_recv_pkt_batch(struct virtio_transport *t,
if (start_batch)
batch->sk = sk;
+ defer_data_ready = start_batch &&
+ READ_ONCE(sk->sk_data_ready) == vsock_sk(sk)->default_data_ready;
ctx = (struct virtio_transport_rx_pkt_ctx) {
.net = net,
@@ -2096,6 +2144,7 @@ void virtio_transport_recv_pkt_batch(struct virtio_transport *t,
.dst = &dst,
.batchable = start_batch ? &batchable : NULL,
.batch = start_batch ? batch : NULL,
+ .defer_data_ready = defer_data_ready,
};
free_pkt = virtio_transport_recv_pkt_locked(t, skb, sk, &ctx);
if (start_batch && batchable) {
diff --git a/net/vmw_vsock/vsock_bpf.c b/net/vmw_vsock/vsock_bpf.c
index 9049d2648646..7770a6c51dbe 100644
--- a/net/vmw_vsock/vsock_bpf.c
+++ b/net/vmw_vsock/vsock_bpf.c
@@ -154,6 +154,7 @@ int vsock_bpf_update_proto(struct sock *sk, struct sk_psock *psock, bool restore
if (restore) {
sk->sk_write_space = psock->saved_write_space;
sock_replace_proto(sk, psock->sk_proto);
+ vsock_rx_callback_kick(sk);
return 0;
}
@@ -166,6 +167,7 @@ int vsock_bpf_update_proto(struct sock *sk, struct sk_psock *psock, bool restore
vsock_bpf_check_needs_rebuild(psock->sk_proto);
sock_replace_proto(sk, &vsock_bpf_prot);
+ vsock_rx_callback_kick(sk);
return 0;
}
--
2.34.1
^ permalink raw reply [flat|nested] 6+ messages in thread
end of thread, other threads:[~2026-10-10 14:23 UTC | newest]
Thread overview: 6+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-10 14:22 [PATCH net-next v2 0/5] vsock/virtio: reduce RX per-packet socket overhead Jia Jia
2026-10-10 14:22 ` [PATCH net-next v2 1/5] vsock/virtio: split socket lookup from locked RX processing Jia Jia
2026-10-10 14:22 ` [PATCH net-next v2 2/5] vsock/virtio: amortize RX socket locking for stream packets Jia Jia
2026-10-10 14:22 ` [PATCH net-next v2 3/5] vsock/virtio: reuse same-flow socket lookup in RX batches Jia Jia
2026-10-10 14:22 ` [PATCH net-next v2 4/5] vsock/virtio: coalesce RX write-space notifications in lock batches Jia Jia
2026-10-10 14:22 ` [PATCH net-next v2 5/5] vsock/virtio: defer RX readable notifications until batch unlock Jia Jia
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®