mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Huiwen He <huiwen.he@linux.dev>
To: linkinjeon@kernel.org
Cc: linux-cifs@vger.kernel.org, senozhatsky@chromium.org,
	tom@talpey.com, chenxiaosong@chenxiaosong.com, tj@kernel.org,
	linux-kernel@vger.kernel.org
Subject: [RFC PATCH 2/4] ksmbd: route WRITE requests to an unbound workqueue
Date: Thu, 22 Oct 2026 17:25:12 +0800	[thread overview]
Message-ID: <20261022092514.2590010-3-huiwen.he@linux.dev> (raw)
In-Reply-To: <20261022092514.2590010-1-huiwen.he@linux.dev>

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


  parent reply	other threads:[~2026-10-22  9:26 UTC|newest]

Thread overview: 5+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
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 [this message]
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

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20261022092514.2590010-3-huiwen.he@linux.dev \
    --to=huiwen.he@linux.dev \
    --cc=chenxiaosong@chenxiaosong.com \
    --cc=linkinjeon@kernel.org \
    --cc=linux-cifs@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=senozhatsky@chromium.org \
    --cc=tj@kernel.org \
    --cc=tom@talpey.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
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®