* [RFC PATCH 0/4] ksmbd: improve WRITE scheduling and TCP READ sends
@ 2026-10-22 9:25 Huiwen He
2026-10-22 9:25 ` [RFC PATCH 1/4] workqueue: export attribute and sysfs setup helpers Huiwen He
` (3 more replies)
0 siblings, 4 replies; 5+ messages in thread
From: Huiwen He @ 2026-10-22 9:25 UTC (permalink / raw)
To: linkinjeon; +Cc: linux-cifs, senozhatsky, tom, chenxiaosong, tj, linux-kernel
From: Huiwen He <hehuiwen@kylinos.cn>
Hi Namjae,
This series addresses two sources of overhead in ksmbd:
- WRITE scheduling: requests from one connection can accumulate on a
busy per-CPU worker pool even when other CPUs are available. Route
eligible single-channel WRITEs to a CPU-scoped, non-strict unbound
workqueue, while keeping other requests on the per-CPU queue.
- READ sending: TCP copies READ data after the filesystem has filled
the response buffer. Use page-backed buffers and MSG_SPLICE_PAGES
to avoid this copy, with the existing copy path as a fallback.
Test setup:
Server: Linux 7.3.0-rc3+, dual socket, 128 CPUs, tmpfs.
Client: Linux 7.0.0-38-generic, local 4-vCPU VM; file-data caching
bypassed for both protocols.
NFS: Linux nfsd, NFSv4.2/TCP, sec=sys, 32 threads.
SMB: ksmbd and Samba 4.23.6, SMB3.1.1.
fio: 16 jobs, QD32/job, 1 GiB total, libaio; four 30-second runs
after warmup. Mixed: --rw=rw --rwmixread=50, READ + WRITE.
1. Baseline throughput (median MiB/s)
Block Conn Workload NFS Samba ksmbd
----- ---- -------- --------- --------- ---------
4KiB 1 READ 195.59 241.71 292.64
4KiB 1 WRITE 154.68 228.92 219.55
4KiB 1 mixed 172.49 232.42 236.85
4KiB 2 READ 346.23 274.58 401.12
4KiB 2 WRITE 312.62 292.39 325.75
4KiB 2 mixed 329.28 286.86 345.04
4KiB 4 READ 428.34 277.92 463.80
4KiB 4 WRITE 387.19 285.59 415.65
4KiB 4 mixed 402.74 284.52 397.76
64KiB 1 READ 2156.89 1647.55 2152.98
64KiB 1 WRITE 1664.58 1956.34 1808.18
64KiB 1 mixed 1790.19 1854.00 1961.47
64KiB 2 READ 3607.94 1526.36 3345.74
64KiB 2 WRITE 2969.75 2706.88 3223.25
64KiB 2 mixed 3469.13 1990.95 3285.02
64KiB 4 READ 4375.82 1850.92 4213.09
64KiB 4 WRITE 2622.47 2908.68 3016.62
64KiB 4 mixed 4293.94 1857.41 3950.03
1MiB 1 READ 3688.67 2266.23 2142.63
1MiB 1 WRITE 3750.42 3782.38 2184.30
1MiB 1 mixed 3986.36 3108.39 2827.65
1MiB 2 READ 5756.65 2245.04 3960.96
1MiB 2 WRITE 3858.07 3418.41 4063.92
1MiB 2 mixed 5347.24 3151.78 4614.56
1MiB 4 READ 7908.09 2378.32 6750.79
1MiB 4 WRITE 3422.60 4138.59 3566.57
1MiB 4 mixed 6161.38 2676.61 5471.26
2. Combined throughput (median MiB/s)
Before/After: both optimizations off/on in the same kernel.
Block Ch Workload Before After Paired %
----- -- -------- --------- --------- ----------
4KiB 1 READ 292.64 282.21 -3.61%
4KiB 1 WRITE 219.55 192.84 -11.01%
4KiB 1 mixed 236.85 240.15 +0.36%
4KiB 2 READ 401.12 404.66 +1.17%
4KiB 2 WRITE 325.75 342.19 +5.37%
4KiB 2 mixed 345.04 336.34 -2.12%
4KiB 4 READ 463.80 456.10 -1.95%
4KiB 4 WRITE 415.65 417.24 -1.06%
4KiB 4 mixed 397.76 386.93 -2.74%
64KiB 1 READ 2152.98 2558.10 +18.34%
64KiB 1 WRITE 1808.18 2031.92 +11.69%
64KiB 1 mixed 1961.47 2176.16 +9.53%
64KiB 2 READ 3345.74 3591.95 +7.32%
64KiB 2 WRITE 3223.25 3205.70 -0.54%
64KiB 2 mixed 3285.02 3317.22 +2.39%
64KiB 4 READ 4213.09 4427.29 +4.65%
64KiB 4 WRITE 3016.62 3047.30 -0.59%
64KiB 4 mixed 3950.03 3962.34 +0.44%
1MiB 1 READ 2142.63 3353.03 +56.51%
1MiB 1 WRITE 2184.30 3460.33 +58.75%
1MiB 1 mixed 2827.65 3096.88 +9.71%
1MiB 2 READ 3960.96 5546.88 +39.95%
1MiB 2 WRITE 4063.92 4174.43 +2.75%
1MiB 2 mixed 4614.56 4954.98 +7.37%
1MiB 4 READ 6750.79 8311.51 +21.79%
1MiB 4 WRITE 3566.57 3612.88 +0.74%
1MiB 4 mixed 5471.26 5604.89 +3.85%
4 KiB single-channel WRITE regresses by 11.01%. If no better solution
is found, a request-size check could fall back to the per-CPU queue
for small WRITEs.
Thanks,
Huiwen
Huiwen He (4):
workqueue: export attribute and sysfs setup helpers
ksmbd: route WRITE requests to an unbound workqueue
ksmbd: complete TCP frames before reporting send success
ksmbd: send READ payloads with MSG_SPLICE_PAGES
fs/smb/server/connection.c | 1 +
fs/smb/server/connection.h | 6 ++
fs/smb/server/ksmbd_work.c | 141 ++++++++++++++++++++++++++++------
fs/smb/server/ksmbd_work.h | 11 +++
fs/smb/server/server.c | 78 ++++++++++++++++++-
fs/smb/server/smb2pdu.c | 126 +++++++++++++++++++++++++-----
fs/smb/server/smb2pdu.h | 1 +
fs/smb/server/transport_tcp.c | 71 ++++++++++++++++-
fs/smb/server/transport_tcp.h | 1 +
kernel/workqueue.c | 4 +
10 files changed, 395 insertions(+), 45 deletions(-)
--
2.43.0
^ permalink raw reply [flat|nested] 5+ messages in thread
* [RFC PATCH 1/4] workqueue: export attribute and sysfs setup helpers
2026-10-22 9:25 [RFC PATCH 0/4] ksmbd: improve WRITE scheduling and TCP READ sends Huiwen He
@ 2026-10-22 9:25 ` Huiwen He
2026-10-22 9:25 ` [RFC PATCH 2/4] ksmbd: route WRITE requests to an unbound workqueue Huiwen He
` (2 subsequent siblings)
3 siblings, 0 replies; 5+ messages in thread
From: Huiwen He @ 2026-10-22 9:25 UTC (permalink / raw)
To: linkinjeon; +Cc: linux-cifs, senozhatsky, tom, chenxiaosong, tj, linux-kernel
From: Huiwen He <hehuiwen@kylinos.cn>
The workqueue attribute helpers are not available to modules.
Export the allocation, application and release helpers for GPL modules.
Also export workqueue_sysfs_register() so a module can apply attributes
before exposing its queue in sysfs.
Signed-off-by: Huiwen He <hehuiwen@kylinos.cn>
---
kernel/workqueue.c | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/kernel/workqueue.c b/kernel/workqueue.c
index 1ae3732a2c51..ec4709395594 100644
--- a/kernel/workqueue.c
+++ b/kernel/workqueue.c
@@ -4822,6 +4822,7 @@ void free_workqueue_attrs(struct workqueue_attrs *attrs)
kfree(attrs);
}
}
+EXPORT_SYMBOL_GPL(free_workqueue_attrs);
/**
* alloc_workqueue_attrs - allocate a workqueue_attrs
@@ -4850,6 +4851,7 @@ struct workqueue_attrs *alloc_workqueue_attrs_noprof(void)
free_workqueue_attrs(attrs);
return NULL;
}
+EXPORT_SYMBOL_GPL(alloc_workqueue_attrs_noprof);
static void copy_workqueue_attrs(struct workqueue_attrs *to,
const struct workqueue_attrs *from)
@@ -5618,6 +5620,7 @@ int apply_workqueue_attrs(struct workqueue_struct *wq,
return ret;
}
+EXPORT_SYMBOL_GPL(apply_workqueue_attrs);
/**
* unbound_wq_update_pwq - update a pwq slot for CPU hot[un]plug
@@ -7746,6 +7749,7 @@ int workqueue_sysfs_register(struct workqueue_struct *wq)
kobject_uevent(&wq_dev->dev.kobj, KOBJ_ADD);
return 0;
}
+EXPORT_SYMBOL_GPL(workqueue_sysfs_register);
/**
* workqueue_sysfs_unregister - undo workqueue_sysfs_register()
--
2.43.0
^ permalink raw reply [flat|nested] 5+ messages in thread
* [RFC PATCH 2/4] ksmbd: route WRITE requests to an unbound workqueue
2026-10-22 9:25 [RFC PATCH 0/4] ksmbd: improve WRITE scheduling and TCP READ sends Huiwen He
2026-10-22 9:25 ` [RFC PATCH 1/4] workqueue: export attribute and sysfs setup helpers Huiwen He
@ 2026-10-22 9:25 ` Huiwen He
2026-10-22 9:25 ` [RFC PATCH 3/4] ksmbd: complete TCP frames before reporting send success Huiwen He
2026-10-22 9:25 ` [RFC PATCH 4/4] ksmbd: send READ payloads with MSG_SPLICE_PAGES Huiwen He
3 siblings, 0 replies; 5+ messages in thread
From: Huiwen He @ 2026-10-22 9:25 UTC (permalink / raw)
To: linkinjeon; +Cc: linux-cifs, senozhatsky, tom, chenxiaosong, tj, linux-kernel
From: Huiwen He <hehuiwen@kylinos.cn>
WRITE requests from one connection can accumulate on a busy per-CPU
pool, limiting throughput even when other CPUs are available.
Route eligible single-channel WRITEs to a CPU-scoped, non-strict
unbound workqueue, while retaining per-CPU execution for other requests.
Tested with fio on a 128-CPU server with a local 4-vCPU client VM
(tmpfs, 16 jobs, QD32 per job, libaio, direct=0, cache=none;
mixed=50/50 R/W). Throughput (MiB/s) and paired changes are medians
of four runs.
A: write_wq=0; B: write_wq=1.
Block Ch Workload A MiB/s B MiB/s B/A change
----- -- --------- --------- --------- ----------
4KiB 1 READ 292.64 290.73 -0.71%
4KiB 1 WRITE 219.55 193.01 -12.03%
4KiB 1 mixed 236.85 238.67 +0.46%
4KiB 2 READ 401.12 404.46 +2.12%
4KiB 2 WRITE 325.75 334.67 +4.22%
4KiB 2 mixed 345.04 338.24 -2.46%
4KiB 4 READ 463.80 453.28 -1.41%
4KiB 4 WRITE 415.65 415.69 -0.14%
4KiB 4 mixed 397.76 396.75 -1.35%
64KiB 1 READ 2152.98 2162.74 +4.12%
64KiB 1 WRITE 1808.18 1976.37 +9.34%
64KiB 1 mixed 1961.47 1974.08 +0.53%
64KiB 2 READ 3345.74 3246.35 -2.93%
64KiB 2 WRITE 3223.25 3222.85 -0.14%
64KiB 2 mixed 3285.02 3283.75 +0.36%
64KiB 4 READ 4213.09 4227.13 +0.34%
64KiB 4 WRITE 3016.62 3089.17 +1.82%
64KiB 4 mixed 3950.03 3877.03 -2.00%
1MiB 1 READ 2142.63 2126.23 -0.99%
1MiB 1 WRITE 2184.30 3452.84 +58.30%
1MiB 1 mixed 2827.65 2961.50 +5.88%
1MiB 2 READ 3960.96 3887.33 -1.99%
1MiB 2 WRITE 4063.92 4158.22 +2.37%
1MiB 2 mixed 4613.54 4631.33 +0.39%
1MiB 4 READ 6750.79 6707.91 -1.49%
1MiB 4 WRITE 3566.57 3557.26 +0.64%
1MiB 4 mixed 5471.26 5444.00 +0.42%
Mixed workloads show no consistent gain: interleaved READs keep later
WRITEs per-CPU until the connection becomes idle.
Signed-off-by: Huiwen He <hehuiwen@kylinos.cn>
---
fs/smb/server/connection.h | 4 ++
fs/smb/server/ksmbd_work.c | 53 ++++++++++++++++++++++++
fs/smb/server/ksmbd_work.h | 2 +
fs/smb/server/server.c | 77 ++++++++++++++++++++++++++++++++++-
fs/smb/server/smb2pdu.c | 6 +++
fs/smb/server/transport_tcp.c | 5 +++
fs/smb/server/transport_tcp.h | 1 +
7 files changed, 147 insertions(+), 1 deletion(-)
diff --git a/fs/smb/server/connection.h b/fs/smb/server/connection.h
index 63484c8efbbd..a92e0fea0f06 100644
--- a/fs/smb/server/connection.h
+++ b/fs/smb/server/connection.h
@@ -59,6 +59,10 @@ struct ksmbd_conn {
struct smb_version_cmds *cmds;
unsigned int max_cmds;
struct mutex srv_mutex;
+ /* RX-only: keep WRITEs per-CPU until idle is observed. */
+ bool write_wq_blocked;
+ /* Sticky across channel removal; accessed with READ/WRITE_ONCE. */
+ bool multichannel_seen;
int status;
unsigned int cli_cap;
bool stop_called;
diff --git a/fs/smb/server/ksmbd_work.c b/fs/smb/server/ksmbd_work.c
index d307aefe0aec..e1b87bc0c949 100644
--- a/fs/smb/server/ksmbd_work.c
+++ b/fs/smb/server/ksmbd_work.c
@@ -7,6 +7,7 @@
#include <linux/mm.h>
#include <linux/slab.h>
#include <linux/workqueue.h>
+#include <linux/module.h>
#include "server.h"
#include "connection.h"
@@ -17,6 +18,23 @@
static struct kmem_cache *work_cache;
static struct workqueue_struct *ksmbd_wq;
+/* Load-time switch for the WRITE request routing policy. */
+bool ksmbd_write_wq_enabled;
+module_param_named(write_wq, ksmbd_write_wq_enabled, bool, 0444);
+MODULE_PARM_DESC(write_wq,
+ "Route eligible WRITEs to an unbound workqueue; load-time only");
+
+static struct workqueue_struct *ksmbd_write_wq;
+
+/* Only the sole receiver calls this for newly admitted requests. */
+bool ksmbd_queue_request(struct ksmbd_work *work, bool use_write_wq)
+{
+ struct workqueue_struct *wq = use_write_wq ? ksmbd_write_wq : ksmbd_wq;
+
+ /* No work/conn access after publication: the consumer may free both. */
+ return queue_work(wq, &work->work);
+}
+
static int ksmbd_reserve_iov(struct ksmbd_work *work, int need_iov_cnt)
{
struct kvec *new;
@@ -110,14 +128,49 @@ int ksmbd_work_pool_init(void)
int ksmbd_workqueue_init(void)
{
+ struct workqueue_attrs *attrs;
+ int ret = -ENOMEM;
+
ksmbd_wq = alloc_workqueue("ksmbd-io", WQ_PERCPU, 0);
if (!ksmbd_wq)
return -ENOMEM;
+ if (!ksmbd_write_wq_enabled)
+ return 0;
+
+ ksmbd_write_wq = alloc_workqueue("ksmbd-write", WQ_UNBOUND, 0);
+ if (!ksmbd_write_wq)
+ goto destroy_io_wq;
+ attrs = alloc_workqueue_attrs();
+ if (!attrs)
+ goto destroy_write_wq;
+ attrs->affn_scope = WQ_AFFN_CPU;
+ attrs->affn_strict = false;
+ ret = apply_workqueue_attrs(ksmbd_write_wq, attrs);
+ free_workqueue_attrs(attrs);
+ if (ret)
+ goto destroy_write_wq;
+
+ /* Set the initial policy before exposing writable sysfs attributes. */
+ ret = workqueue_sysfs_register(ksmbd_write_wq);
+ if (ret)
+ goto destroy_write_wq;
return 0;
+
+destroy_write_wq:
+ destroy_workqueue(ksmbd_write_wq);
+ ksmbd_write_wq = NULL;
+destroy_io_wq:
+ destroy_workqueue(ksmbd_wq);
+ ksmbd_wq = NULL;
+ return ret;
}
void ksmbd_workqueue_destroy(void)
{
+ if (ksmbd_write_wq) {
+ destroy_workqueue(ksmbd_write_wq);
+ ksmbd_write_wq = NULL;
+ }
destroy_workqueue(ksmbd_wq);
ksmbd_wq = NULL;
}
diff --git a/fs/smb/server/ksmbd_work.h b/fs/smb/server/ksmbd_work.h
index 0844aa929f55..9f87b6c4393a 100644
--- a/fs/smb/server/ksmbd_work.h
+++ b/fs/smb/server/ksmbd_work.h
@@ -156,6 +156,8 @@ int ksmbd_work_pool_init(void);
int ksmbd_workqueue_init(void);
void ksmbd_workqueue_destroy(void);
bool ksmbd_queue_work(struct ksmbd_work *work);
+extern bool ksmbd_write_wq_enabled;
+bool ksmbd_queue_request(struct ksmbd_work *work, bool use_write_wq);
int ksmbd_iov_pin_rsp_read(struct ksmbd_work *work, void *ib, int len,
void *aux_buf, unsigned int aux_size);
int ksmbd_iov_pin_rsp(struct ksmbd_work *work, void *ib, int len);
diff --git a/fs/smb/server/server.c b/fs/smb/server/server.c
index 0069d4e6a60a..5347eba916ed 100644
--- a/fs/smb/server/server.c
+++ b/fs/smb/server/server.c
@@ -12,6 +12,7 @@
#include <linux/sysfs.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
+#include <linux/unaligned.h>
#include "server.h"
#include "smb_common.h"
@@ -19,6 +20,7 @@
#include "../common/smb2status.h"
#include "connection.h"
#include "transport_ipc.h"
+#include "transport_tcp.h"
#include "mgmt/user_session.h"
#include "crypto_ctx.h"
#include "auth.h"
@@ -372,6 +374,79 @@ static void handle_ksmbd_work(struct work_struct *wk)
ksmbd_conn_r_count_dec(conn);
}
+enum ksmbd_wq_class {
+ KSMBD_WQ_LOCAL,
+ KSMBD_WQ_WRITE,
+ KSMBD_WQ_BLOCK_WRITE,
+};
+
+/*
+ * Classify a complete RFC1002-framed request for workqueue placement only.
+ * Check bounds before inspecting fields; leave protocol validation to the worker.
+ */
+static enum ksmbd_wq_class ksmbd_smb2_wq_classify(void *buf)
+{
+ const struct smb2_hdr *hdr = smb_get_msg(buf);
+ const struct smb2_write_req *req = (const void *)hdr;
+ unsigned int len = get_rfc1002_len(buf);
+
+ if (len < sizeof(*hdr) || hdr->ProtocolId != SMB2_PROTO_NUMBER ||
+ hdr->NextCommand ||
+ (hdr->Flags & (SMB2_FLAGS_SERVER_TO_REDIR | SMB2_FLAGS_ASYNC_COMMAND |
+ SMB2_FLAGS_RELATED_OPERATIONS | SMB2_FLAGS_SIGNED)))
+ return KSMBD_WQ_BLOCK_WRITE;
+
+ switch (get_unaligned_le16(&hdr->Command)) {
+ case SMB2_WRITE_HE:
+ if (len < sizeof(*req) || !req->Length ||
+ req->Channel != SMB2_CHANNEL_NONE || req->Flags)
+ return KSMBD_WQ_BLOCK_WRITE;
+ return KSMBD_WQ_WRITE;
+ case SMB2_NEGOTIATE_HE:
+ case SMB2_SESSION_SETUP_HE:
+ case SMB2_LOGOFF_HE:
+ case SMB2_TREE_CONNECT_HE:
+ case SMB2_TREE_DISCONNECT_HE:
+ case SMB2_CREATE_HE:
+ case SMB2_CLOSE_HE:
+ case SMB2_FLUSH_HE:
+ case SMB2_CANCEL_HE:
+ case SMB2_ECHO_HE:
+ case SMB2_QUERY_INFO_HE:
+ case SMB2_SET_INFO_HE:
+ /* Keep these commands per-CPU without blocking the WRITE queue. */
+ return KSMBD_WQ_LOCAL;
+ default:
+ /* READ and unclassified traffic also keep later WRITEs per-CPU. */
+ return KSMBD_WQ_BLOCK_WRITE;
+ }
+}
+
+/*
+ * RX-only placement policy. The current request already holds r_count.
+ * READ or unclassified traffic keeps later WRITEs per-CPU until RX
+ * observes r_count == 1. This does not drain or move work already queued.
+ * Extra oplock/notify references can delay rearming, never hasten it.
+ */
+static bool ksmbd_use_write_wq(struct ksmbd_work *work)
+{
+ struct ksmbd_conn *conn = work->conn;
+ enum ksmbd_wq_class wq_class = KSMBD_WQ_BLOCK_WRITE;
+
+ if (!ksmbd_write_wq_enabled)
+ return false;
+
+ if (ksmbd_transport_is_tcp(conn->transport))
+ wq_class = ksmbd_smb2_wq_classify(work->request_buf);
+ if (wq_class == KSMBD_WQ_BLOCK_WRITE)
+ conn->write_wq_blocked = true;
+ else if (conn->write_wq_blocked && atomic_read(&conn->r_count) == 1)
+ conn->write_wq_blocked = false;
+
+ return wq_class == KSMBD_WQ_WRITE && !conn->write_wq_blocked &&
+ !READ_ONCE(conn->multichannel_seen);
+}
+
/**
* queue_ksmbd_work() - queue a smb request to worker thread queue
* for processing smb command and sending response
@@ -403,7 +478,7 @@ static int queue_ksmbd_work(struct ksmbd_conn *conn)
/* update activity on connection */
conn->last_active = jiffies;
INIT_WORK(&work->work, handle_ksmbd_work);
- ksmbd_queue_work(work);
+ ksmbd_queue_request(work, ksmbd_use_write_wq(work));
return 0;
}
diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c
index b7ce67094626..54ecdd096bd2 100644
--- a/fs/smb/server/smb2pdu.c
+++ b/fs/smb/server/smb2pdu.c
@@ -125,6 +125,12 @@ static int register_session_channel(struct ksmbd_session *sess,
if (xa_is_err(old)) {
kfree_sensitive(chann);
rc = xa_err(old);
+ goto out;
+ }
+ if (ksmbd_write_wq_enabled && count) {
+ /* Remember the extra channel even if binding later fails. */
+ xa_for_each(&sess->ksmbd_chann_list, index, chann)
+ WRITE_ONCE(chann->conn->multichannel_seen, true);
}
out:
up_write(&sess->chann_lock);
diff --git a/fs/smb/server/transport_tcp.c b/fs/smb/server/transport_tcp.c
index 4968cfc1a572..5a2aad35739d 100644
--- a/fs/smb/server/transport_tcp.c
+++ b/fs/smb/server/transport_tcp.c
@@ -719,3 +719,8 @@ static const struct ksmbd_transport_ops ksmbd_tcp_transport_ops = {
.shutdown = ksmbd_tcp_shutdown,
.free_transport = ksmbd_tcp_free_transport,
};
+
+bool ksmbd_transport_is_tcp(struct ksmbd_transport *t)
+{
+ return t->ops == &ksmbd_tcp_transport_ops;
+}
diff --git a/fs/smb/server/transport_tcp.h b/fs/smb/server/transport_tcp.h
index 1e51675ee1b2..4095854ee91d 100644
--- a/fs/smb/server/transport_tcp.h
+++ b/fs/smb/server/transport_tcp.h
@@ -9,6 +9,7 @@
int ksmbd_tcp_set_interfaces(char *ifc_list, int ifc_list_sz);
struct interface *ksmbd_find_netdev_name_iface_list(char *netdev_name);
void ksmbd_free_transport(struct ksmbd_transport *kt);
+bool ksmbd_transport_is_tcp(struct ksmbd_transport *t);
int ksmbd_tcp_init(void);
void ksmbd_tcp_destroy(void);
--
2.43.0
^ permalink raw reply [flat|nested] 5+ messages in thread
* [RFC PATCH 3/4] ksmbd: complete TCP frames before reporting send success
2026-10-22 9:25 [RFC PATCH 0/4] ksmbd: improve WRITE scheduling and TCP READ sends Huiwen He
2026-10-22 9:25 ` [RFC PATCH 1/4] workqueue: export attribute and sysfs setup helpers Huiwen He
2026-10-22 9:25 ` [RFC PATCH 2/4] ksmbd: route WRITE requests to an unbound workqueue Huiwen He
@ 2026-10-22 9:25 ` Huiwen He
2026-10-22 9:25 ` [RFC PATCH 4/4] ksmbd: send READ payloads with MSG_SPLICE_PAGES Huiwen He
3 siblings, 0 replies; 5+ messages in thread
From: Huiwen He @ 2026-10-22 9:25 UTC (permalink / raw)
To: linkinjeon; +Cc: linux-cifs, senozhatsky, tom, chenxiaosong, tj, linux-kernel
From: Huiwen He <hehuiwen@kylinos.cn>
A positive kernel_sendmsg() return may cover only part of an SMB frame.
Reporting success lets the next response follow an incomplete frame.
Retry short sends with the same iterator while holding srv_mutex.
On zero progress or error, shut down the socket before releasing the
lock so no later response can follow an incomplete frame.
Signed-off-by: Huiwen He <hehuiwen@kylinos.cn>
---
fs/smb/server/transport_tcp.c | 17 +++++++++++++++--
1 file changed, 15 insertions(+), 2 deletions(-)
diff --git a/fs/smb/server/transport_tcp.c b/fs/smb/server/transport_tcp.c
index 5a2aad35739d..1183bb75aa24 100644
--- a/fs/smb/server/transport_tcp.c
+++ b/fs/smb/server/transport_tcp.c
@@ -437,9 +437,22 @@ static int ksmbd_tcp_writev(struct ksmbd_transport *t,
struct msghdr smb_msg = {
.msg_flags = MSG_NOSIGNAL | tx->msg_flags,
};
+ struct socket *sock = TCP_TRANS(t)->sock;
+ int ret;
+
+ iov_iter_kvec(&smb_msg.msg_iter, ITER_SOURCE, tx->iov,
+ tx->iov_cnt, tx->size);
+ /* The caller holds srv_mutex across the entire frame. */
+ while (iov_iter_count(&smb_msg.msg_iter)) {
+ ret = sock_sendmsg(sock, &smb_msg);
+ if (ret <= 0) {
+ /* Never append another SMB frame to an incomplete one. */
+ kernel_sock_shutdown(sock, SHUT_RDWR);
+ return ret ?: -EIO;
+ }
+ }
- return kernel_sendmsg(TCP_TRANS(t)->sock, &smb_msg, tx->iov,
- tx->iov_cnt, tx->size);
+ return tx->size;
}
static void ksmbd_tcp_disconnect(struct ksmbd_transport *t)
--
2.43.0
^ permalink raw reply [flat|nested] 5+ messages in thread
* [RFC PATCH 4/4] ksmbd: send READ payloads with MSG_SPLICE_PAGES
2026-10-22 9:25 [RFC PATCH 0/4] ksmbd: improve WRITE scheduling and TCP READ sends Huiwen He
` (2 preceding siblings ...)
2026-10-22 9:25 ` [RFC PATCH 3/4] ksmbd: complete TCP frames before reporting send success Huiwen He
@ 2026-10-22 9:25 ` Huiwen He
3 siblings, 0 replies; 5+ messages in thread
From: Huiwen He @ 2026-10-22 9:25 UTC (permalink / raw)
To: linkinjeon; +Cc: linux-cifs, senozhatsky, tom, chenxiaosong, tj, linux-kernel
From: Huiwen He <hehuiwen@kylinos.cn>
The TCP send path copies READ payloads that ksmbd has already read into
its response buffer, adding CPU and memory bandwidth overhead.
Use page-backed READ buffers and MSG_SPLICE_PAGES to avoid this copy.
Keep the existing copy path as a fallback when page-backed sending is
not applicable.
Tested with fio on a 128-CPU server with a local 4-vCPU client VM
(tmpfs, 16 jobs, QD32 per job, libaio, direct=0, cache=none;
mixed=50/50 R/W). Throughput (MiB/s) and paired changes are medians
of four runs.
A: read_splice=0; B: read_splice=1.
Block Ch Workload A MiB/s B MiB/s B/A change
----- -- --------- --------- --------- ----------
4KiB 1 READ 290.73 282.21 -2.94%
4KiB 1 WRITE 193.01 192.84 +1.17%
4KiB 1 mixed 238.67 240.15 +0.55%
4KiB 2 READ 404.46 404.66 +1.17%
4KiB 2 WRITE 334.67 342.19 +0.34%
4KiB 2 mixed 338.24 336.34 -0.38%
4KiB 4 READ 453.28 456.10 -0.87%
4KiB 4 WRITE 415.69 417.24 +0.37%
4KiB 4 mixed 396.75 386.93 -1.41%
64KiB 1 READ 2162.74 2558.10 +19.56%
64KiB 1 WRITE 1976.37 2031.92 +1.26%
64KiB 1 mixed 1974.08 2176.16 +10.13%
64KiB 2 READ 3246.35 3591.95 +10.58%
64KiB 2 WRITE 3222.85 3205.70 -0.66%
64KiB 2 mixed 3283.75 3317.22 +0.86%
64KiB 4 READ 4227.13 4427.29 +5.01%
64KiB 4 WRITE 3089.17 3047.30 -1.36%
64KiB 4 mixed 3877.03 3962.34 +2.09%
1MiB 1 READ 2126.23 3353.03 +59.40%
1MiB 1 WRITE 3452.84 3460.33 +0.25%
1MiB 1 mixed 2961.50 3096.88 +4.64%
1MiB 2 READ 3887.33 5546.88 +42.10%
1MiB 2 WRITE 4158.22 4174.43 +0.18%
1MiB 2 mixed 4335.01 4954.98 +18.71%
1MiB 4 READ 6707.91 8311.51 +23.28%
1MiB 4 WRITE 3557.26 3612.88 +0.38%
1MiB 4 mixed 5444.00 5604.89 +3.12%
Signed-off-by: Huiwen He <hehuiwen@kylinos.cn>
---
fs/smb/server/connection.c | 1 +
fs/smb/server/connection.h | 2 +
fs/smb/server/ksmbd_work.c | 88 ++++++++++++++++++-------
fs/smb/server/ksmbd_work.h | 9 +++
fs/smb/server/server.c | 1 +
fs/smb/server/smb2pdu.c | 120 ++++++++++++++++++++++++++++------
fs/smb/server/smb2pdu.h | 1 +
fs/smb/server/transport_tcp.c | 61 +++++++++++++++--
8 files changed, 235 insertions(+), 48 deletions(-)
diff --git a/fs/smb/server/connection.c b/fs/smb/server/connection.c
index 4cb92d6599ee..4eb59d81f966 100644
--- a/fs/smb/server/connection.c
+++ b/fs/smb/server/connection.c
@@ -518,6 +518,7 @@ static int __ksmbd_conn_write(struct ksmbd_work *work,
tx->iov = work->iov;
tx->iov_cnt = work->iov_cnt;
tx->size = get_rfc1002_len(work->iov[0].iov_base) + 4;
+ tx->splice_header_len = work->splice_header_len;
tx->need_invalidate_rkey = work->need_invalidate_rkey;
tx->remote_key = work->remote_key;
diff --git a/fs/smb/server/connection.h b/fs/smb/server/connection.h
index a92e0fea0f06..27a8b17bcbb7 100644
--- a/fs/smb/server/connection.h
+++ b/fs/smb/server/connection.h
@@ -166,6 +166,8 @@ struct ksmbd_transport_write {
struct kvec *iov;
int iov_cnt;
int size;
+ /* Prefix to copy into a page fragment before the page-backed last iov. */
+ unsigned int splice_header_len;
bool need_invalidate_rkey;
unsigned int remote_key;
int msg_flags;
diff --git a/fs/smb/server/ksmbd_work.c b/fs/smb/server/ksmbd_work.c
index e1b87bc0c949..9bdd9af1b955 100644
--- a/fs/smb/server/ksmbd_work.c
+++ b/fs/smb/server/ksmbd_work.c
@@ -35,6 +35,47 @@ bool ksmbd_queue_request(struct ksmbd_work *work, bool use_write_wq)
return queue_work(wq, &work->work);
}
+struct aux_read *ksmbd_alloc_read_buf(size_t length, bool allow_splice)
+{
+ struct aux_read *ar;
+ size_t alloc_size = ALIGN(length, 8);
+
+ ar = kzalloc_obj(*ar, KSMBD_DEFAULT_GFP);
+ if (!ar)
+ return NULL;
+
+ if (allow_splice && length) {
+ unsigned int order = get_order(alloc_size);
+
+ /* TCP references to tails must keep the whole allocation alive. */
+ if (order <= MAX_PAGE_ORDER)
+ ar->page = alloc_pages(GFP_KERNEL | __GFP_COMP |
+ __GFP_NORETRY | __GFP_NOWARN, order);
+ }
+ if (ar->page)
+ ar->buf = page_address(ar->page);
+ else
+ ar->buf = kvmalloc(alloc_size, KSMBD_DEFAULT_GFP);
+ if (!ar->buf) {
+ kfree(ar);
+ return NULL;
+ }
+ return ar;
+}
+
+void ksmbd_free_read_buf(struct aux_read *ar)
+{
+ if (!ar)
+ return;
+ if (ar->page) {
+ /* TCP may still own references; never reuse or modify these pages. */
+ put_page(ar->page);
+ } else {
+ kvfree(ar->buf);
+ }
+ kfree(ar);
+}
+
static int ksmbd_reserve_iov(struct ksmbd_work *work, int need_iov_cnt)
{
struct kvec *new;
@@ -92,9 +133,8 @@ void ksmbd_free_work_struct(struct ksmbd_work *work)
kvfree(work->response_buf);
list_for_each_entry_safe(ar, tmp, &work->aux_read_list, entry) {
- kvfree(ar->buf);
list_del(&ar->entry);
- kfree(ar);
+ ksmbd_free_read_buf(ar);
}
kfree(work->tr_buf);
@@ -188,23 +228,14 @@ static inline void __ksmbd_iov_pin(struct ksmbd_work *work, void *ib,
work->iov_cnt++;
}
-static int __ksmbd_iov_pin_rsp(struct ksmbd_work *work, void *ib, int len,
- void *aux_buf, unsigned int aux_size)
+/* On success, work owns ar, including a buffer with no response payload. */
+int ksmbd_iov_pin_rsp_read(struct ksmbd_work *work, void *ib, int len,
+ struct aux_read *ar, unsigned int aux_size)
{
- struct aux_read *ar = NULL;
- int need_iov_cnt = 1;
-
- if (aux_size) {
- need_iov_cnt++;
- ar = kmalloc_obj(struct aux_read, KSMBD_DEFAULT_GFP);
- if (!ar)
- return -ENOMEM;
- }
+ int need_iov_cnt = aux_size ? 2 : 1;
- if (ksmbd_reserve_iov(work, need_iov_cnt)) {
- kfree(ar);
+ if (ksmbd_reserve_iov(work, need_iov_cnt))
return -ENOMEM;
- }
/* Plus rfc_length size on first iov */
if (!work->iov_idx) {
@@ -218,25 +249,36 @@ static int __ksmbd_iov_pin_rsp(struct ksmbd_work *work, void *ib, int len,
inc_rfc1001_len(work->iov[0].iov_base, len);
if (aux_size) {
- __ksmbd_iov_pin(work, aux_buf, aux_size);
+ __ksmbd_iov_pin(work, ar->buf, aux_size);
inc_rfc1001_len(work->iov[0].iov_base, aux_size);
-
- ar->buf = aux_buf;
- list_add(&ar->entry, &work->aux_read_list);
}
+ if (ar)
+ list_add(&ar->entry, &work->aux_read_list);
return 0;
}
int ksmbd_iov_pin_rsp(struct ksmbd_work *work, void *ib, int len)
{
- return __ksmbd_iov_pin_rsp(work, ib, len, NULL, 0);
+ return ksmbd_iov_pin_rsp_read(work, ib, len, NULL, 0);
}
-int ksmbd_iov_pin_rsp_read(struct ksmbd_work *work, void *ib, int len,
+int ksmbd_iov_pin_rsp_data(struct ksmbd_work *work, void *ib, int len,
void *aux_buf, unsigned int aux_size)
{
- return __ksmbd_iov_pin_rsp(work, ib, len, aux_buf, aux_size);
+ struct aux_read *ar;
+ int ret;
+
+ if (!aux_size)
+ return ksmbd_iov_pin_rsp(work, ib, len);
+ ar = kzalloc_obj(*ar, KSMBD_DEFAULT_GFP);
+ if (!ar)
+ return -ENOMEM;
+ ar->buf = aux_buf;
+ ret = ksmbd_iov_pin_rsp_read(work, ib, len, ar, aux_size);
+ if (ret)
+ kfree(ar); /* The caller still owns aux_buf. */
+ return ret;
}
int allocate_interim_rsp_buf(struct ksmbd_work *work)
diff --git a/fs/smb/server/ksmbd_work.h b/fs/smb/server/ksmbd_work.h
index 9f87b6c4393a..26b3d57a8429 100644
--- a/fs/smb/server/ksmbd_work.h
+++ b/fs/smb/server/ksmbd_work.h
@@ -13,6 +13,7 @@ struct ksmbd_conn;
struct ksmbd_session;
struct ksmbd_tree_connect;
struct ksmbd_file;
+struct page;
#define KSMBD_WORK_INLINE_IOVS 4
@@ -24,6 +25,8 @@ enum {
struct aux_read {
void *buf;
+ /* Non-NULL for a buffer owned through a compound-page reference. */
+ struct page *page;
struct list_head entry;
};
@@ -45,6 +48,8 @@ struct ksmbd_work {
int iov_alloc_cnt;
int iov_cnt;
int iov_idx;
+ /* Final response prefix before a page-backed last iov; zero disables splice. */
+ unsigned int splice_header_len;
struct kvec iov_inline[KSMBD_WORK_INLINE_IOVS];
/* Next cmd hdr in compound req buf*/
@@ -149,6 +154,8 @@ static inline void *ksmbd_req_buf_next(struct ksmbd_work *work)
struct ksmbd_work *ksmbd_alloc_work_struct(void);
void ksmbd_free_work_struct(struct ksmbd_work *work);
+struct aux_read *ksmbd_alloc_read_buf(size_t length, bool allow_splice);
+void ksmbd_free_read_buf(struct aux_read *ar);
void ksmbd_work_pool_destroy(void);
int ksmbd_work_pool_init(void);
@@ -159,6 +166,8 @@ bool ksmbd_queue_work(struct ksmbd_work *work);
extern bool ksmbd_write_wq_enabled;
bool ksmbd_queue_request(struct ksmbd_work *work, bool use_write_wq);
int ksmbd_iov_pin_rsp_read(struct ksmbd_work *work, void *ib, int len,
+ struct aux_read *ar, unsigned int aux_size);
+int ksmbd_iov_pin_rsp_data(struct ksmbd_work *work, void *ib, int len,
void *aux_buf, unsigned int aux_size);
int ksmbd_iov_pin_rsp(struct ksmbd_work *work, void *ib, int len);
int allocate_interim_rsp_buf(struct ksmbd_work *work);
diff --git a/fs/smb/server/server.c b/fs/smb/server/server.c
index 5347eba916ed..70cad4e0e8e2 100644
--- a/fs/smb/server/server.c
+++ b/fs/smb/server/server.c
@@ -351,6 +351,7 @@ static void __handle_ksmbd_work(struct ksmbd_work *work,
if (work->sess)
ksmbd_user_session_put(work->sess);
+ smb2_set_read_splice(work);
ksmbd_conn_write(work);
}
diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c
index 54ecdd096bd2..3d139c686393 100644
--- a/fs/smb/server/smb2pdu.c
+++ b/fs/smb/server/smb2pdu.c
@@ -14,6 +14,8 @@
#include <linux/ethtool.h>
#include <linux/falloc.h>
#include <linux/mount.h>
+#include <linux/mm.h>
+#include <linux/module.h>
#include <linux/filelock.h>
#include <linux/fileattr.h>
#include <linux/timekeeping.h>
@@ -47,6 +49,29 @@
#include "transport_tcp.h"
#include "compress.h"
+/* Switching affects allocation, not ownership of existing READ buffers. */
+static bool read_splice;
+
+static int read_splice_set(const char *val, const struct kernel_param *kp)
+{
+ bool enabled;
+ int ret;
+
+ ret = kstrtobool(val ?: "1", &enabled);
+ if (!ret)
+ WRITE_ONCE(*(bool *)kp->arg, enabled);
+ return ret;
+}
+
+static const struct kernel_param_ops read_splice_ops = {
+ .flags = KERNEL_PARAM_OPS_FL_NOARG,
+ .set = read_splice_set,
+ .get = param_get_bool,
+};
+module_param_cb(read_splice, &read_splice_ops, &read_splice, 0644);
+__MODULE_PARM_TYPE(read_splice, "bool");
+MODULE_PARM_DESC(read_splice, "Page-backed ordinary TCP READ sends");
+
static void __wbuf(struct ksmbd_work *work, void **req, void **rsp)
{
if (work->next_smb2_rcv_hdr_off) {
@@ -7700,9 +7725,9 @@ static int smb2_get_info_sec(struct ksmbd_work *work,
if (rc)
goto err_out;
- rc = ksmbd_iov_pin_rsp_read(work, (void *)rsp,
- offsetof(struct smb2_query_info_rsp, Buffer),
- pntsd, secdesclen);
+ rc = ksmbd_iov_pin_rsp_data(work, (void *)rsp,
+ offsetof(struct smb2_query_info_rsp, Buffer),
+ pntsd, secdesclen);
err_out:
if (rc) {
rsp->OutputBufferLength = 0;
@@ -8684,7 +8709,7 @@ static noinline int smb2_read_pipe(struct ksmbd_work *work)
rpc_resp->payload_sz);
nbytes = rpc_resp->payload_sz;
- err = ksmbd_iov_pin_rsp_read(work, (void *)rsp,
+ err = ksmbd_iov_pin_rsp_data(work, (void *)rsp,
offsetof(struct smb2_read_rsp, Buffer),
aux_payload_buf, nbytes);
if (err) {
@@ -9004,6 +9029,63 @@ static ssize_t smb2_read_rdma(struct ksmbd_work *work,
return length;
}
+void smb2_set_read_splice(struct ksmbd_work *work)
+{
+ const unsigned int prefix_len = sizeof(__be32);
+ const unsigned int rsp_len = offsetof(struct smb2_read_rsp, Buffer);
+ const unsigned int header_len = prefix_len + rsp_len;
+ const struct smb2_read_rsp *rsp;
+ const struct kvec *payload;
+ const struct aux_read *ar;
+
+ work->splice_header_len = 0;
+ if (!list_is_singular(&work->aux_read_list))
+ return;
+ ar = list_first_entry(&work->aux_read_list, struct aux_read, entry);
+ /* Only eligible TCP READs allocate a page-backed auxiliary buffer. */
+ if (!ar->page)
+ return;
+
+ /* The final response must still be RFC1002 length + READ header + data. */
+ if (work->iov_cnt != 3 || work->iov[0].iov_len != prefix_len ||
+ work->iov[1].iov_len != rsp_len)
+ return;
+ rsp = work->iov[1].iov_base;
+ payload = &work->iov[2];
+
+ /* Signing follows the READ handler; session/tcon refs may be gone. */
+ if (rsp->hdr.Flags & SMB2_FLAGS_SIGNED)
+ return;
+
+ /* Retain the original payload and send only the initialized bytes. */
+ if (payload->iov_base != ar->buf ||
+ !payload->iov_len ||
+ payload->iov_len > page_size(ar->page) ||
+ get_rfc1002_len(work->iov[0].iov_base) + prefix_len !=
+ payload->iov_len + header_len)
+ return;
+
+ /* Do not pin an oversized compound allocation for a short READ. */
+ if (get_order(payload->iov_len) < compound_order(ar->page))
+ return;
+
+ work->splice_header_len = header_len;
+}
+
+static bool smb2_read_splice_eligible(struct ksmbd_work *work,
+ const struct smb2_read_req *req)
+{
+ /* Negotiated signing/encryption capabilities alone do not exclude READs. */
+ return READ_ONCE(read_splice) &&
+ !work->next_smb2_rcv_hdr_off && !req->hdr.NextCommand &&
+ !work->encrypted &&
+ !work->sess->sign && !work->conn->sign &&
+ !(req->hdr.Flags & SMB2_FLAGS_SIGNED) &&
+ !(req->Flags & SMB2_READFLAG_REQUEST_COMPRESSED) &&
+ req->Channel == SMB2_CHANNEL_NONE &&
+ ksmbd_transport_is_tcp(work->conn->transport);
+}
+
/**
* smb2_read() - handler for smb2 read from file
* @work: smb work containing read command buffer
@@ -9013,6 +9095,7 @@ static ssize_t smb2_read_rdma(struct ksmbd_work *work,
int smb2_read(struct ksmbd_work *work)
{
struct ksmbd_conn *conn = work->conn;
+ struct aux_read *ar = NULL;
struct smb2_read_req *req;
struct smb2_read_rsp *rsp;
struct ksmbd_file *fp = NULL;
@@ -9024,7 +9107,6 @@ int smb2_read(struct ksmbd_work *work)
bool is_rdma_channel = false, async_interim = false;
unsigned int max_read_size = conn->vals->max_read_size;
unsigned int id = KSMBD_NO_FID, pid = KSMBD_NO_FID;
- void *aux_payload_buf;
ksmbd_debug(SMB, "Received smb2 read request\n");
@@ -9137,15 +9219,14 @@ int smb2_read(struct ksmbd_work *work)
ksmbd_debug(SMB, "filename %pD, offset %lld, len %zu\n",
fp->filp, offset, length);
- aux_payload_buf = kvmalloc(ALIGN(length, 8), KSMBD_DEFAULT_GFP);
- if (!aux_payload_buf) {
+ ar = ksmbd_alloc_read_buf(length, smb2_read_splice_eligible(work, req));
+ if (!ar) {
err = -ENOMEM;
goto out;
}
- nbytes = ksmbd_vfs_read(work, fp, length, &offset, aux_payload_buf);
+ nbytes = ksmbd_vfs_read(work, fp, length, &offset, ar->buf);
if (nbytes < 0) {
- kvfree(aux_payload_buf);
err = nbytes;
goto out;
}
@@ -9157,10 +9238,10 @@ int smb2_read(struct ksmbd_work *work)
* client. Zero just that tail.
*/
if (nbytes & 7)
- memset(aux_payload_buf + nbytes, 0, ALIGN(nbytes, 8) - nbytes);
+ memset(ar->buf + nbytes, 0, ALIGN(nbytes, 8) - nbytes);
if ((nbytes == 0 && length != 0) || nbytes < mincount) {
- kvfree(aux_payload_buf);
+ ksmbd_free_read_buf(ar);
rsp->hdr.Status = STATUS_END_OF_FILE;
smb2_set_err_rsp(work);
if (async_interim)
@@ -9175,16 +9256,15 @@ int smb2_read(struct ksmbd_work *work)
if (is_rdma_channel == true) {
rdma_transform_len = smb2_prep_rdma_read(work, req,
rsp,
- aux_payload_buf,
+ ar->buf,
nbytes);
if (rdma_transform_len < 0) {
- kvfree(aux_payload_buf);
err = rdma_transform_len;
goto out;
}
/* write data to the client using rdma channel */
remain_bytes = smb2_read_rdma(work, req,
- aux_payload_buf,
+ ar->buf,
nbytes);
if (remain_bytes < 0)
pr_err("RDMA READ transfer failed: session=%llu payload=%zu transform=%d rc=%zd\n",
@@ -9195,8 +9275,8 @@ int smb2_read(struct ksmbd_work *work)
"RDMA READ transfer completed: session=%llu payload=%zu transform=%d\n",
work->sess ? work->sess->id : 0, nbytes,
rdma_transform_len);
- kvfree(aux_payload_buf);
- aux_payload_buf = NULL;
+ ksmbd_free_read_buf(ar);
+ ar = NULL;
nbytes = 0;
if (remain_bytes < 0) {
err = (int)remain_bytes;
@@ -9214,11 +9294,10 @@ int smb2_read(struct ksmbd_work *work)
err = ksmbd_iov_pin_rsp_read(work, (void *)rsp,
offsetof(struct smb2_read_rsp, Buffer) +
rdma_transform_len,
- aux_payload_buf, nbytes);
- if (err) {
- kvfree(aux_payload_buf);
+ ar, nbytes);
+ if (err)
goto out;
- }
+ ar = NULL; /* The response now owns the buffer. */
if (async_interim)
release_async_work(work);
/*
@@ -9234,6 +9313,7 @@ int smb2_read(struct ksmbd_work *work)
return 0;
out:
+ ksmbd_free_read_buf(ar);
if (async_interim)
release_async_work(work);
if (err) {
diff --git a/fs/smb/server/smb2pdu.h b/fs/smb/server/smb2pdu.h
index 3f08d1ca5a38..27e60f9ff4db 100644
--- a/fs/smb/server/smb2pdu.h
+++ b/fs/smb/server/smb2pdu.h
@@ -429,6 +429,7 @@ bool smb3_encryption_negotiated(struct ksmbd_conn *conn);
/* smb2 misc functions */
int ksmbd_smb2_check_message(struct ksmbd_work *work);
void smb2_complete_request_open(struct ksmbd_work *work);
+void smb2_set_read_splice(struct ksmbd_work *work);
/* smb2 command handlers */
int smb2_handle_negotiate(struct ksmbd_work *work);
diff --git a/fs/smb/server/transport_tcp.c b/fs/smb/server/transport_tcp.c
index 1183bb75aa24..5acf183dab64 100644
--- a/fs/smb/server/transport_tcp.c
+++ b/fs/smb/server/transport_tcp.c
@@ -5,6 +5,7 @@
*/
#include <linux/freezer.h>
+#include <linux/page_frag_cache.h>
#include "smb_common.h"
#include "server.h"
@@ -32,6 +33,8 @@ static int bind_additional_ifaces;
struct tcp_transport {
struct ksmbd_transport transport;
struct socket *sock;
+ /* Serialized by conn->srv_mutex. */
+ struct page_frag_cache tx_frag_cache;
struct kvec *iov;
unsigned int nr_iov;
};
@@ -66,6 +69,7 @@ static struct tcp_transport *alloc_transport(struct socket *client_sk)
if (!t)
return NULL;
t->sock = client_sk;
+ page_frag_cache_init(&t->tx_frag_cache);
conn = ksmbd_conn_alloc();
if (!conn) {
@@ -100,6 +104,7 @@ static void ksmbd_tcp_free_transport(struct ksmbd_transport *kt)
struct tcp_transport *t = TCP_TRANS(kt);
sock_release(t->sock);
+ page_frag_cache_drain(&t->tx_frag_cache);
kfree(t->iov);
kfree(t);
}
@@ -431,6 +436,38 @@ static int ksmbd_tcp_read(struct ksmbd_transport *t, char *buf,
return ksmbd_tcp_readv(TCP_TRANS(t), &iov, 1, to_read, max_retries);
}
+/* The last iov is page-backed; consolidate the prefix into a page fragment. */
+static void *ksmbd_tcp_build_splice_iov(struct tcp_transport *t,
+ const struct ksmbd_transport_write *tx,
+ struct kvec *splice_iov)
+{
+ unsigned int header_len = tx->splice_header_len;
+ const struct kvec *payload;
+ struct iov_iter iter;
+ void *header_frag;
+
+ if (!header_len || header_len > PAGE_SIZE || header_len >= tx->size ||
+ tx->iov_cnt < 2)
+ return NULL;
+ payload = &tx->iov[tx->iov_cnt - 1];
+ if (payload->iov_len != tx->size - header_len)
+ return NULL;
+
+ header_frag = page_frag_alloc(&t->tx_frag_cache, header_len,
+ GFP_KERNEL | __GFP_NORETRY | __GFP_NOWARN);
+ if (!header_frag)
+ return NULL;
+ iov_iter_kvec(&iter, ITER_SOURCE, tx->iov, tx->iov_cnt, header_len);
+ if (!copy_from_iter_full(header_frag, header_len, &iter)) {
+ page_frag_free(header_frag);
+ return NULL;
+ }
+ splice_iov[0].iov_base = header_frag;
+ splice_iov[0].iov_len = header_len;
+ splice_iov[1] = *payload;
+ return header_frag;
+}
+
static int ksmbd_tcp_writev(struct ksmbd_transport *t,
const struct ksmbd_transport_write *tx)
{
@@ -438,21 +475,35 @@ static int ksmbd_tcp_writev(struct ksmbd_transport *t,
.msg_flags = MSG_NOSIGNAL | tx->msg_flags,
};
struct socket *sock = TCP_TRANS(t)->sock;
+ struct kvec splice_iov[2];
+ void *header_frag;
int ret;
- iov_iter_kvec(&smb_msg.msg_iter, ITER_SOURCE, tx->iov,
- tx->iov_cnt, tx->size);
- /* The caller holds srv_mutex across the entire frame. */
+ /* The caller holds srv_mutex across preparation and the entire frame. */
+ header_frag = ksmbd_tcp_build_splice_iov(TCP_TRANS(t), tx, splice_iov);
+ if (header_frag) {
+ iov_iter_kvec(&smb_msg.msg_iter, ITER_SOURCE, splice_iov,
+ ARRAY_SIZE(splice_iov), tx->size);
+ smb_msg.msg_flags |= MSG_SPLICE_PAGES;
+ } else {
+ iov_iter_kvec(&smb_msg.msg_iter, ITER_SOURCE, tx->iov,
+ tx->iov_cnt, tx->size);
+ }
while (iov_iter_count(&smb_msg.msg_iter)) {
ret = sock_sendmsg(sock, &smb_msg);
if (ret <= 0) {
/* Never append another SMB frame to an incomplete one. */
kernel_sock_shutdown(sock, SHUT_RDWR);
- return ret ?: -EIO;
+ ret = ret ?: -EIO;
+ goto out;
}
}
- return tx->size;
+ ret = tx->size;
+out:
+ if (header_frag)
+ page_frag_free(header_frag);
+ return ret;
}
static void ksmbd_tcp_disconnect(struct ksmbd_transport *t)
--
2.43.0
^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2026-10-22 9:26 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-22 9:25 [RFC PATCH 0/4] ksmbd: improve WRITE scheduling and TCP READ sends Huiwen He
2026-10-22 9:25 ` [RFC PATCH 1/4] workqueue: export attribute and sysfs setup helpers Huiwen He
2026-10-22 9:25 ` [RFC PATCH 2/4] ksmbd: route WRITE requests to an unbound workqueue Huiwen He
2026-10-22 9:25 ` [RFC PATCH 3/4] ksmbd: complete TCP frames before reporting send success Huiwen He
2026-10-22 9:25 ` [RFC PATCH 4/4] ksmbd: send READ payloads with MSG_SPLICE_PAGES Huiwen He
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®