* [Devel] [PATCH hci-8.1 1/3] fs/fuse kio: teach pcs_umem about hugepage-backed mappings
2026-09-30 7:34 [Devel] [PATCH hci-8.1 0/3] fs/fuse kio: cut per-4k-page sendmsg/recvmsg calls in the sockio path Liu Kui
@ 2026-09-30 7:34 ` Liu Kui
2026-09-30 7:41 ` no-reply
2026-09-30 7:34 ` [Devel] [PATCH hci-8.1 2/3] fs/fuse kio: refactor transmit path in pcs_sockio_send() Liu Kui
` (2 subsequent siblings)
3 siblings, 1 reply; 9+ messages in thread
From: Liu Kui @ 2026-09-30 7:34 UTC (permalink / raw)
To: Konstantin Khorenko; +Cc: azaitsev, devel, Liu Kui
pcs_umem pins every mapping as a flat array of 4k pages and the krpc
bvec builder emits one bio_vec per page, so a 1M chunk costs 256 page
pointers and 256 bio_vecs even when it sits in a single hugepage.
Detect hugepage backing after pinning: if the range is 2M-aligned and
each 2M chunk is backed by one folio (hugetlbfs or fully-mapped THP),
keep only the folio head page per chunk and raise umem->page_shift to
PMD_SHIFT. The bvec builder then caps bv_len at the backing unit
instead of PAGE_SIZE, so a 1M chunk in a hugepage umem becomes a
single bio_vec. Mixed or misaligned mappings stay at 4k.
Also pin with pin_user_pages_fast(), which needs no mmap_lock, and
fix the pcs_umem_get() error path, which leaked chunk page arrays and
could deref a NULL page_chunk.
https://virtuozzo.atlassian.net/browse/VSTOR-142166
Signed-off-by: Liu Kui <kui.liu@virtuozzo.com>
---
fs/fuse/kio/pcs/pcs_krpc.c | 9 ++++-
fs/fuse/kio/pcs/pcs_mr.c | 84 +++++++++++++++++++++++++++++++++++++---------
fs/fuse/kio/pcs/pcs_mr.h | 45 +++++++++++++++++++++----
3 files changed, 115 insertions(+), 23 deletions(-)
diff --git a/fs/fuse/kio/pcs/pcs_krpc.c b/fs/fuse/kio/pcs/pcs_krpc.c
index 7d40e17ce8ae..d4baf28cef71 100644
--- a/fs/fuse/kio/pcs/pcs_krpc.c
+++ b/fs/fuse/kio/pcs/pcs_krpc.c
@@ -410,6 +410,7 @@ static void kreq_prepare_data_buff(struct krpc_req *kreq, struct krpc_chunk *chu
}
} else {
struct pcs_umem *umem;
+ unsigned int contig;
umem = (chunk->type == KRPC_CHUNK_TYPE_MR) ? chunk->mr->umem : chunk->umem;
@@ -426,7 +427,13 @@ static void kreq_prepare_data_buff(struct krpc_req *kreq, struct krpc_chunk *chu
bvec->bv_page = page;
bvec->bv_offset = offset;
- bvec->bv_len = len < (PAGE_SIZE - offset) ? len : (PAGE_SIZE - offset);
+ /*
+ * One bio_vec per physically contiguous extent: a
+ * whole 2M block when the umem is hugepage-backed,
+ * a single page otherwise.
+ */
+ contig = pcs_umem_contig(umem, pos);
+ bvec->bv_len = len < contig ? len : contig;
pos += bvec->bv_len;
len -= bvec->bv_len;
diff --git a/fs/fuse/kio/pcs/pcs_mr.c b/fs/fuse/kio/pcs/pcs_mr.c
index 1e3a74033510..58f1ff41c750 100644
--- a/fs/fuse/kio/pcs/pcs_mr.c
+++ b/fs/fuse/kio/pcs/pcs_mr.c
@@ -7,20 +7,30 @@
#include <linux/slab.h>
#include <linux/sched/mm.h>
#include <linux/resource.h>
+#include <linux/mm.h>
+#include <linux/pgtable.h>
#include "pcs_mr.h"
void pcs_umem_release(struct pcs_umem *umem)
{
struct mm_struct *mm_s = umem->mm;
- int i, npages = umem->npages;
-
- for (i = 0; npages; i++) {
- int to_free = min_t(int, PCS_PAGES_PER_CHUNK, npages);
-
- unpin_user_pages(umem->page_chunk[i].pages, to_free);
- kfree(umem->page_chunk[i].pages);
- npages -= to_free;
+ int npages = umem->npages;
+ int i;
+
+ for (i = 0; umem->page_chunk && i < umem->nchunks; i++) {
+ struct pcs_page_chunk *chunk = &umem->page_chunk[i];
+ int n = min_t(int, PCS_PAGES_PER_CHUNK, npages);
+
+ if (umem->page_shift == PMD_SHIFT) {
+ /* one full hugepage folio per chunk */
+ unpin_user_page_range_dirty_lock(chunk->head,
+ PCS_PAGES_PER_CHUNK, false);
+ } else if (chunk->pages) {
+ unpin_user_pages(chunk->pages, n);
+ kfree(chunk->pages);
+ }
+ npages -= n;
}
atomic64_sub(umem->npages, &mm_s->pinned_vm);
mmdrop(mm_s);
@@ -28,6 +38,43 @@ void pcs_umem_release(struct pcs_umem *umem)
kfree(umem);
}
+/*
+ * A page chunk covers exactly one hugepage. If the whole pinned range
+ * is 2M-aligned and every chunk is backed by one aligned hugepage folio
+ * (hugetlbfs or fully-mapped THP), drop the 4k page arrays and keep just
+ * the folio head page in each chunk, raising page_shift accordingly.
+ * Mixed or misaligned mappings simply stay at 4k granularity.
+ */
+static void pcs_umem_try_compact(struct pcs_umem *umem)
+{
+ int i, j;
+
+ if (umem->fp_addr & (PMD_SIZE - 1))
+ return;
+ if (!umem->npages || umem->npages % PCS_PAGES_PER_CHUNK)
+ return;
+
+ for (i = 0; i < umem->nchunks; i++) {
+ struct page *head = umem->page_chunk[i].pages[0];
+ struct folio *folio = page_folio(head);
+
+ if (folio_nr_pages(folio) - folio_page_idx(folio, head) <
+ PCS_PAGES_PER_CHUNK)
+ return;
+ for (j = 1; j < PCS_PAGES_PER_CHUNK; j++)
+ if (umem->page_chunk[i].pages[j] != nth_page(head, j))
+ return;
+ }
+
+ for (i = 0; i < umem->nchunks; i++) {
+ struct page *head = umem->page_chunk[i].pages[0];
+
+ kfree(umem->page_chunk[i].pages);
+ umem->page_chunk[i].head = head;
+ }
+ umem->page_shift = PMD_SHIFT;
+}
+
/*
* Pin pages from userspace memory
*/
@@ -36,11 +83,10 @@ struct pcs_umem *pcs_umem_get(u64 start, u64 len)
struct pcs_umem *umem = NULL;
struct mm_struct *mm_s;
u64 fp_va;
- int npages, nchunks, i, ret;
+ int npages, i, ret;
fp_va = start & PAGE_MASK;
npages = PAGE_ALIGN(start + len - fp_va) >> PAGE_SHIFT;
- nchunks = DIV_ROUND_UP(npages, PCS_PAGES_PER_CHUNK);
umem = kzalloc(sizeof(*umem), GFP_KERNEL);
if (!umem)
@@ -49,12 +95,14 @@ struct pcs_umem *pcs_umem_get(u64 start, u64 len)
/* pin user pages */
mm_s = current->mm;
umem->mm = mm_s;
+ umem->page_shift = PAGE_SHIFT;
+ umem->nchunks = DIV_ROUND_UP(npages, PCS_PAGES_PER_CHUNK);
mmgrab(mm_s);
- mmap_read_lock(mm_s);
umem->fp_addr = fp_va;
- umem->page_chunk = kcalloc(nchunks, sizeof(struct pcs_page_chunk), GFP_KERNEL);
+ umem->page_chunk = kcalloc(umem->nchunks, sizeof(struct pcs_page_chunk),
+ GFP_KERNEL);
if (!umem->page_chunk) {
ret = -ENOMEM;
goto out_err;
@@ -71,9 +119,13 @@ struct pcs_umem *pcs_umem_get(u64 start, u64 len)
umem->page_chunk[i].pages = pages;
while (n) {
- ret = pin_user_pages(fp_va, n, FOLL_WRITE | FOLL_LONGTERM, pages);
- if (ret < 0)
+ ret = pin_user_pages_fast(fp_va, n,
+ FOLL_WRITE | FOLL_LONGTERM, pages);
+ if (ret <= 0) {
+ if (!ret)
+ ret = -EFAULT;
goto out_err;
+ }
atomic64_add(ret, &mm_s->pinned_vm);
umem->npages += ret;
@@ -83,12 +135,12 @@ struct pcs_umem *pcs_umem_get(u64 start, u64 len)
npages -= ret;
}
}
- mmap_read_unlock(mm_s);
+
+ pcs_umem_try_compact(umem);
return umem;
out_err:
- mmap_read_unlock(mm_s);
pcs_umem_release(umem);
return ERR_PTR(ret);
diff --git a/fs/fuse/kio/pcs/pcs_mr.h b/fs/fuse/kio/pcs/pcs_mr.h
index 64f237f57dec..ea02af1dd622 100644
--- a/fs/fuse/kio/pcs/pcs_mr.h
+++ b/fs/fuse/kio/pcs/pcs_mr.h
@@ -6,12 +6,14 @@
#define _PCS_MR_H_ 1
#include <linux/types.h>
+#include <linux/mm.h>
#include <linux/highmem-internal.h>
struct pcs_umem;
#define PCS_MAX_MR 0x10000
-#define PCS_PAGE_CHUNK_SHIFT 9
+/* A page chunk covers exactly one hugepage: 2M, PMD_SIZE */
+#define PCS_PAGE_CHUNK_SHIFT (PMD_SHIFT - PAGE_SHIFT)
#define PCS_PAGES_PER_CHUNK (1 << PCS_PAGE_CHUNK_SHIFT)
struct pcs_mr_set {
@@ -20,14 +22,30 @@ struct pcs_mr_set {
atomic_t mr_num; /* number of registered MRs*/
};
+/*
+ * A chunk covers exactly one 2M hugepage of VA: PCS_PAGES_PER_CHUNK 4k
+ * pages. Depending on umem->page_shift it stores either an array of
+ * the pinned 4k pages, or — when the chunk is backed by one aligned
+ * hugepage folio — just the folio head page, with no array at all.
+ */
struct pcs_page_chunk
{
- struct page **pages; /* array of pinned pages */
+ union {
+ struct page **pages; /* page_shift == PAGE_SHIFT */
+ struct page *head; /* page_shift == PMD_SHIFT */
+ };
};
struct pcs_umem {
struct pcs_page_chunk *page_chunk;
- int npages; /* number of pinned pages */
+ int nchunks; /* number of allocated page chunks */
+ int npages; /* number of pinned PAGE_SIZE pages */
+ /*
+ * Granularity of the stored page pointers: PAGE_SHIFT (chunks
+ * hold 4k page arrays) or PMD_SHIFT (chunks hold hugepage folio
+ * heads).
+ */
+ unsigned int page_shift;
u64 fp_addr; /* First page base address */
struct mm_struct *mm; /* mm the memory belongs to */
};
@@ -47,13 +65,28 @@ struct pcs_mr {
*/
static inline struct page *pcs_umem_page(struct pcs_umem *umem, u64 addr)
{
- unsigned int page_idx = (addr - umem->fp_addr) >> PAGE_SHIFT;
+ u64 off = addr - umem->fp_addr;
+ unsigned int page_idx = off >> PAGE_SHIFT;
unsigned int chunk_idx = page_idx >> PCS_PAGE_CHUNK_SHIFT;
unsigned int page_in_chunk = page_idx & (PCS_PAGES_PER_CHUNK - 1);
- if (likely(page_idx < umem->npages))
+ if (unlikely(page_idx >= umem->npages))
+ return NULL;
+
+ if (umem->page_shift == PAGE_SHIFT)
return umem->page_chunk[chunk_idx].pages[page_in_chunk];
- return NULL;
+ return nth_page(umem->page_chunk[chunk_idx].head, page_in_chunk);
+}
+
+/*
+ * Number of physically contiguous bytes mapped at the address: from addr
+ * to the end of its backing unit (PAGE_SIZE or a 2M hugepage folio).
+ * Lets callers build bio_vecs spanning a whole hugepage instead of
+ * one bio_vec per 4k page.
+ */
+static inline unsigned int pcs_umem_contig(struct pcs_umem *umem, u64 addr)
+{
+ return (1U << umem->page_shift) - (addr & ((1U << umem->page_shift) - 1));
}
struct pcs_umem *pcs_umem_get(u64 start, u64 len);
--
Git-155)
_______________________________________________
Devel mailing list
Devel@openvz.org
https://lists.openvz.org/mailman/listinfo/devel
^ permalink raw reply [flat|nested] 9+ messages in thread* [Devel] [PATCH hci-8.1 2/3] fs/fuse kio: refactor transmit path in pcs_sockio_send()
2026-09-30 7:34 [Devel] [PATCH hci-8.1 0/3] fs/fuse kio: cut per-4k-page sendmsg/recvmsg calls in the sockio path Liu Kui
2026-09-30 7:34 ` [Devel] [PATCH hci-8.1 1/3] fs/fuse kio: teach pcs_umem about hugepage-backed mappings Liu Kui
@ 2026-09-30 7:34 ` Liu Kui
2026-09-30 7:42 ` no-reply
2026-09-30 7:34 ` [Devel] [PATCH hci-8.1 3/3] fs/fuse kio: refactor receive path in pcs_sockio_recv() Liu Kui
2026-09-30 16:09 ` [Devel] [PATCH hci-8.1 0/3] fs/fuse kio: cut per-4k-page sendmsg/recvmsg calls in the sockio path Konstantin Khorenko
3 siblings, 1 reply; 9+ messages in thread
From: Liu Kui @ 2026-09-30 7:34 UTC (permalink / raw)
To: Konstantin Khorenko; +Cc: azaitsev, devel, Liu Kui
The transmit path sends one iov_iter segment per sock_sendmsg() call,
and the bvec builder produces one bio_vec per 4k page, so a 1M data
chunk costs up to 256 sendmsg calls where the socket could take the
whole iov_iter at once.
Replace do_send_one_seg() with do_sockio_sendmsg(), which submits the
entire iov_iter in a single sock_sendmsg() call (with MSG_SPLICE_PAGES
when all pages qualify), reducing pcs_sockio_send() to pure queue
management.
Also clear PCS_MSG_BUSY in the transport whenever IO on the msg ends,
like the rdma transport does, instead of relying on krpc to mop it up.
https://virtuozzo.atlassian.net/browse/VSTOR-142167
Signed-off-by: Liu Kui <kui.liu@virtuozzo.com>
---
fs/fuse/kio/pcs/pcs_sock_io.c | 207 ++++++++++++++++++++++--------------------
1 file changed, 109 insertions(+), 98 deletions(-)
diff --git a/fs/fuse/kio/pcs/pcs_sock_io.c b/fs/fuse/kio/pcs/pcs_sock_io.c
index fa26105b2185..828c8a98cabf 100644
--- a/fs/fuse/kio/pcs/pcs_sock_io.c
+++ b/fs/fuse/kio/pcs/pcs_sock_io.c
@@ -139,54 +139,6 @@ static bool pcs_should_fail_sock_io(void)
}
#endif
-static int do_send_one_seg(struct socket *sock, struct iov_iter *it, size_t left, int has_more)
-{
- int ret = -EIO;
- size_t size = iov_iter_single_seg_count(it);
- bool more = (size < left);
- int flags = (MSG_DONTWAIT | MSG_NOSIGNAL) | ((more || has_more) ? MSG_MORE : 0);
- struct msghdr msg = { .msg_flags = flags };
-
- if (unlikely(pcs_use_eor) && !more)
- flags |= MSG_EOR;
-
- DTRACE("sock(%px) len:%ld, more:%d\n", sock, iov_iter_count(it), more);
-
- if (unlikely(!size))
- return 0;
-
- if (pcs_should_fail_sock_io())
- goto out;
-
- if (iov_iter_is_kvec(it)) {
- struct kvec kv;
-
- iov_iter_get_kvec(it, &kv);
- BUG_ON(kv.iov_len != size);
-
- iov_iter_kvec(&msg.msg_iter, ITER_SOURCE, &kv, 1, size);
- } else if (iov_iter_is_bvec(it)) {
- struct bio_vec bv;
-
- /* iov_iter_iovec() for ITER_BVEC */
- bvec_set_page(&bv, it->bvec->bv_page,
- min(size, it->bvec->bv_len - it->iov_offset),
- it->bvec->bv_offset + it->iov_offset);
-
- if (sendpage_ok(bv.bv_page))
- msg.msg_flags |= MSG_SPLICE_PAGES;
- else
- msg.msg_flags &= ~MSG_SPLICE_PAGES;
-
- iov_iter_bvec(&msg.msg_iter, ITER_SOURCE, &bv, 1, bv.bv_len);
- }
-
- ret = sock_sendmsg(sock, &msg);
-out:
- DTRACE("sock(%px) len:%ld, more:%d ret:%d\n", sock, iov_iter_count(it), more, ret);
- return ret;
-}
-
static int do_sock_recv(struct socket *sock, void *buf, size_t len)
{
@@ -352,14 +304,110 @@ static void pcs_sockio_recv(struct pcs_sockio *sio)
}
+static bool iov_iter_sendpages_ok(struct iov_iter *it)
+{
+ const struct bio_vec *bvec = it->bvec;
+
+ if (!iov_iter_is_bvec(it))
+ return false;
+
+ while (bvec < (it->bvec + it->nr_segs)) {
+ if (!sendpages_ok(bvec->bv_page, bvec->bv_len, bvec->bv_offset))
+ return false;
+ bvec++;
+ }
+
+ return true;
+}
+
+static int do_sockio_sendmsg(struct pcs_sockio *sio, struct pcs_msg *msg)
+{
+ struct iov_iter *it = &sio->write_iter;
+ unsigned int flags = MSG_DONTWAIT | MSG_NOSIGNAL;
+
+ if (unlikely(pcs_should_fail_sock_io()))
+ return -PCS_ERR_NET_ABORT;
+
+ set_bit(PCS_MSG_BUSY, &msg->flags);
+
+ /* pair with the smp_mb() between set PCS_MSG_ABORTED and test PCS_MSG_BUSY */
+ smp_mb();
+ /* Shouldn't abort a half sent message */
+ if (unlikely(test_bit(PCS_MSG_ABORTED, &msg->flags)) && !sio->write_offset) {
+ pcs_set_local_error(&msg->error, PCS_ERR_CANCEL_REQUEST);
+ goto out;
+ }
+
+ while (sio->write_offset < msg->size) {
+ size_t left = msg->size - sio->write_offset;
+ struct msghdr sockmsg = {.msg_flags = flags};
+
+ TRACE("sio(%px) offset:%d msg:%px left:%zu, it->len:%zu\n", sio, sio->write_offset, msg, left, iov_iter_count(it));
+
+ if (!iov_iter_count(it)) {
+ /* Current iter is exhausted, init new one */
+ msg->get_iter(msg, sio->write_offset, it, WRITE);
+ }
+
+ BUG_ON(iov_iter_count(it) > left);
+
+ if (iov_iter_count(it) < left ||
+ msg->list.next != &sio->write_queue)
+ sockmsg.msg_flags |= MSG_MORE;
+ if (iov_iter_count(it) == left && unlikely(pcs_use_eor))
+ sockmsg.msg_flags |= MSG_EOR;
+
+ if (iov_iter_sendpages_ok(it))
+ sockmsg.msg_flags |= MSG_SPLICE_PAGES;
+
+ sockmsg.msg_iter = *it;
+
+ while (iov_iter_count(it)) {
+ int ret;
+
+ DTRACE("sock(%px) len:%zu, flags:%08x\n", sio->socket, iov_iter_count(it), sockmsg.msg_flags);
+ ret = sock_sendmsg(sio->socket, &sockmsg);
+ DTRACE("sock(%px) len:%zu, flags:%08x, ret=%d\n", sio->socket, iov_iter_count(it), sockmsg.msg_flags, ret);
+ if (ret <= 0) {
+ if (ret == -EAGAIN) {
+ if (!time_is_before_jiffies((unsigned long)(msg->start_time + sio->send_timeout)))
+ /* Keep PCS_MSG_BUSY: the msg is half
+ * sent and the transfer will resume,
+ * it must not be aborted meanwhile. */
+ return 0;
+
+ ret = -PCS_ERR_WRITE_TIMEOUT;
+ } else {
+ WARN_ON(ret == 0);
+ ret = -PCS_ERR_NET_ABORT;
+ }
+
+ /* The caller aborts the connection: no more IO on
+ * this msg, release it for pcs_msg_abort() before
+ * sio_abort() completes it. */
+ clear_and_wake_up_bit(PCS_MSG_BUSY, &msg->flags);
+ return ret;
+ }
+
+ iov_iter_advance(it, ret);
+ sio->write_offset += ret;
+ }
+ WARN_ON(msg_data_left(&sockmsg));
+ }
+
+out:
+ clear_and_wake_up_bit(PCS_MSG_BUSY, &msg->flags);
+ return 1;
+}
+
static void pcs_sockio_send(struct pcs_sockio *sio)
{
struct pcs_rpc *ep __maybe_unused = sio->netio.parent;
- struct iov_iter *it = &sio->write_iter;
unsigned long loop_timeout = jiffies + PCS_SIO_SLICE;
struct pcs_msg * msg;
- int done = 0;
int count = 0;
+ int ret = 0;
+ bool to_push = false;
while (!list_empty(&sio->write_queue)) {
msg = list_first_entry(&sio->write_queue, struct pcs_msg, list);
@@ -380,54 +428,15 @@ static void pcs_sockio_send(struct pcs_sockio *sio)
return;
}
- set_bit(PCS_MSG_BUSY, &msg->flags);
-
- /* pair with the smp_mb() between set PCS_MSG_ABORTED and test PCS_MSG_BUSY */
- smp_mb();
-
- /* Shouldn't abort a half sent message */
- if (unlikely(test_bit(PCS_MSG_ABORTED, &msg->flags)) && !sio->write_offset) {
- pcs_set_local_error(&msg->error, PCS_ERR_CANCEL_REQUEST);
- goto skip_send;
- }
-
- /* TODO: cond resched here? */
- while (sio->write_offset < msg->size) {
- size_t left = msg->size - sio->write_offset;
- int has_more = (msg->list.next != &sio->write_queue);
- int n;
-
- TRACE(PEER_FMT "offset:%d msg:%px left:%ld, it->len:%ld\n", PEER_ARGS(ep), sio->write_offset, msg,
- left, iov_iter_count(it));
-
- if (!iov_iter_count(it)) {
- /* Current iter is exhausted, init new one */
- msg->get_iter(msg, sio->write_offset, it, WRITE);
- }
- BUG_ON(iov_iter_count(it) > left);
- n = do_send_one_seg(sio->socket, it, left, has_more);
- if (n > 0) {
- sio->write_offset += n;
- iov_iter_advance(it, n);
- done = 1;
- } else {
- if (n == 0)
- WARN_ON(1);
-
- if (n == -EAGAIN) {
- unsigned long timeout = msg->start_time + sio->send_timeout;
- if (time_is_before_jiffies(timeout))
- sio_abort(sio, PCS_ERR_WRITE_TIMEOUT);
- return;
- }
- sio_abort(sio, PCS_ERR_NET_ABORT);
- return;
- }
+ /* Send out this msg */
+ ret = do_sockio_sendmsg(sio, msg);
+ if (ret <= 0) {
+ if (ret < 0)
+ sio_abort(sio, -ret);
+ return;
}
-skip_send:
- clear_and_wake_up_bit(PCS_MSG_BUSY, &msg->flags);
-
+ to_push = true;
list_del_init(&msg->list);
sio->write_queue_len -= msg->size;
@@ -435,8 +444,9 @@ static void pcs_sockio_send(struct pcs_sockio *sio)
if (sio->write_wakeup)
sio->write_wakeup(sio);
}
+
sio->write_offset = 0;
- iov_iter_kvec(it, WRITE, NULL, 0, 0);
+ iov_iter_kvec(&sio->write_iter, WRITE, NULL, 0, 0);
pcs_msg_sent(msg);
msg->done(msg);
if (++count >= PCS_SIO_PREEMPT_LIMIT ||
@@ -445,7 +455,8 @@ static void pcs_sockio_send(struct pcs_sockio *sio)
break;
}
}
- if (done)
+
+ if (to_push)
sio_push(sio);
}
--
Git-155)
_______________________________________________
Devel mailing list
Devel@openvz.org
https://lists.openvz.org/mailman/listinfo/devel
^ permalink raw reply [flat|nested] 9+ messages in thread* Re: [Devel] [PATCH hci-8.1 2/3] fs/fuse kio: refactor transmit path in pcs_sockio_send()
2026-09-30 7:34 ` [Devel] [PATCH hci-8.1 2/3] fs/fuse kio: refactor transmit path in pcs_sockio_send() Liu Kui
@ 2026-09-30 7:42 ` no-reply
2026-09-30 16:29 ` Konstantin Khorenko
0 siblings, 1 reply; 9+ messages in thread
From: no-reply @ 2026-09-30 7:42 UTC (permalink / raw)
To: Liu Kui, Konstantin Khorenko; +Cc: devel, azaitsev
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Medium] fs/fuse kio: do_sockio_sendmsg() can spin forever on an empty get_iter() refill
- [Low] fs/fuse kio: iov_iter_sendpages_ok() under-checks pages of unaligned multi-page bvecs
--
commit 2b6fc40ff00b50732f7cf6408214e55943ba4135
Author: Liu Kui <kui.liu@virtuozzo.com>
fs/fuse kio: refactor transmit path in pcs_sockio_send()
This patch replaces do_send_one_seg() with do_sockio_sendmsg(). The new
function submits the whole iov_iter in one sock_sendmsg() call, and uses
MSG_SPLICE_PAGES when all pages qualify. It also clears PCS_MSG_BUSY in
the transport whenever IO on the msg ends.
Link: https://virtuozzo.atlassian.net/browse/VSTOR-142167
> diff --git a/fs/fuse/kio/pcs/pcs_sock_io.c b/fs/fuse/kio/pcs/pcs_sock_io.c
> --- a/fs/fuse/kio/pcs/pcs_sock_io.c
> +++ b/fs/fuse/kio/pcs/pcs_sock_io.c
[ ... ]
> +static bool iov_iter_sendpages_ok(struct iov_iter *it)
> +{
> + const struct bio_vec *bvec = it->bvec;
> +
> + if (!iov_iter_is_bvec(it))
> + return false;
> +
> + while (bvec < (it->bvec + it->nr_segs)) {
> + if (!sendpages_ok(bvec->bv_page, bvec->bv_len, bvec->bv_offset))
> + return false;
> + bvec++;
> + }
> +
> + return true;
> +}
[Severity: Low]
Can iov_iter_sendpages_ok() skip the last page of a multi-page bvec that
has an unaligned bv_offset?
sendpages_ok() starts at page + (offset >> PAGE_SHIFT) and steps
PAGE_SIZE for len bytes. For example, with bv_offset=2048 and
bv_len=4096 the range covers two pages, but only one is checked with
sendpage_ok().
The bvec builder in pcs_krpc.c (umem branch) sets bv_offset to a
non-page-aligned value and bv_len up to the contiguous length, so such
bvecs look possible:
bvec->bv_offset = offset;
...
bvec->bv_len = len < contig ? len : contig;
If that happens, MSG_SPLICE_PAGES could be set in do_sockio_sendmsg()
while the unchecked page is a slab page or has a zero refcount. That
page would then be spliced into the socket by reference instead of being
copied. Is the page check meant to cover the pages that the byte range
actually touches?
> +static int do_sockio_sendmsg(struct pcs_sockio *sio, struct pcs_msg *msg)
> +{
[ ... ]
> + while (sio->write_offset < msg->size) {
> + size_t left = msg->size - sio->write_offset;
> + struct msghdr sockmsg = {.msg_flags = flags};
> +
> + TRACE("sio(%px) offset:%d msg:%px left:%zu, it->len:%zu\n", sio, sio->write_offset, msg, left, iov_iter_count(it));
> +
> + if (!iov_iter_count(it)) {
> + /* Current iter is exhausted, init new one */
> + msg->get_iter(msg, sio->write_offset, it, WRITE);
> + }
> +
> + BUG_ON(iov_iter_count(it) > left);
[ ... ]
> + while (iov_iter_count(it)) {
[Severity: Medium]
Can do_sockio_sendmsg() spin forever here if msg->get_iter() returns an
iterator with a zero count while sio->write_offset < msg->size?
In that case the BUG_ON(iov_iter_count(it) > left) check passes, the
inner while (iov_iter_count(it)) loop is skipped, and sock_sendmsg() is
never called. WARN_ON(msg_data_left(&sockmsg)) sees zero and does not
fire. write_offset does not change, so the outer loop repeats with the
same state and no exit, which would be a soft lockup.
The old do_send_one_seg() returned 0 for an empty segment, and
pcs_sockio_send() turned that into a WARN_ON and sio_abort(). I did not
find a get_iter() callback that returns an empty iterator for an
in-range offset (krpc_msg_get_data() and pcs_get_iter_inline() look
fine), so this looks latent. Would it make sense to add a check that
fails the message when get_iter() returns an empty iterator?
> + int ret;
[ ... ]
--
Sashiko AI review · https://sashiko.vzint.dev/#/patchset/20260930-sockio-refactor-8-1-v1-0-806348b9eb26@virtuozzo.com?part=2
_______________________________________________
Devel mailing list
Devel@openvz.org
https://lists.openvz.org/mailman/listinfo/devel
^ permalink raw reply [flat|nested] 9+ messages in thread* Re: [Devel] [PATCH hci-8.1 2/3] fs/fuse kio: refactor transmit path in pcs_sockio_send()
2026-09-30 7:42 ` no-reply
@ 2026-09-30 16:29 ` Konstantin Khorenko
0 siblings, 0 replies; 9+ messages in thread
From: Konstantin Khorenko @ 2026-09-30 16:29 UTC (permalink / raw)
To: Liu Kui, Alexey Kuznetsov; +Cc: devel, azaitsev
i have commited this patch, but this review looks valid, is you agree,
please consider sending an incremental patch like
diff --git a/fs/fuse/kio/pcs/pcs_sock_io.c b/fs/fuse/kio/pcs/pcs_sock_io.c
index 2da6d51c898b0..3a2228088540a 100644
--- a/fs/fuse/kio/pcs/pcs_sock_io.c
+++ b/fs/fuse/kio/pcs/pcs_sock_io.c
@@ -307,6 +307,12 @@ static int do_sockio_sendmsg(struct pcs_sockio *sio, struct pcs_msg *msg)
if (!iov_iter_count(it)) {
/* Current iter is exhausted, init new one */
msg->get_iter(msg, sio->write_offset, it, WRITE);
+
+ /* An empty refill would spin this loop forever */
+ if (WARN_ON(!iov_iter_count(it))) {
+ clear_and_wake_up_bit(PCS_MSG_BUSY, &msg->flags);
+ return -PCS_ERR_NET_ABORT;
+ }
}
BUG_ON(iov_iter_count(it) > left);
--
Best regards,
Konstantin Khorenko,
Virtuozzo Linux Kernel Team
On 9/30/26 09:42, no-reply@sashiko.virtuozzo.com wrote:
> Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
> - [Medium] fs/fuse kio: do_sockio_sendmsg() can spin forever on an empty get_iter() refill
> - [Low] fs/fuse kio: iov_iter_sendpages_ok() under-checks pages of unaligned multi-page bvecs
> --
>
> commit 2b6fc40ff00b50732f7cf6408214e55943ba4135
> Author: Liu Kui <kui.liu@virtuozzo.com>
>
> fs/fuse kio: refactor transmit path in pcs_sockio_send()
>
> This patch replaces do_send_one_seg() with do_sockio_sendmsg(). The new
> function submits the whole iov_iter in one sock_sendmsg() call, and uses
> MSG_SPLICE_PAGES when all pages qualify. It also clears PCS_MSG_BUSY in
> the transport whenever IO on the msg ends.
>
> Link: https://virtuozzo.atlassian.net/browse/VSTOR-142167
>
>> diff --git a/fs/fuse/kio/pcs/pcs_sock_io.c b/fs/fuse/kio/pcs/pcs_sock_io.c
>> --- a/fs/fuse/kio/pcs/pcs_sock_io.c
>> +++ b/fs/fuse/kio/pcs/pcs_sock_io.c
> [ ... ]
>
>> +static bool iov_iter_sendpages_ok(struct iov_iter *it)
>> +{
>> + const struct bio_vec *bvec = it->bvec;
>> +
>> + if (!iov_iter_is_bvec(it))
>> + return false;
>> +
>> + while (bvec < (it->bvec + it->nr_segs)) {
>> + if (!sendpages_ok(bvec->bv_page, bvec->bv_len, bvec->bv_offset))
>> + return false;
>> + bvec++;
>> + }
>> +
>> + return true;
>> +}
> [Severity: Low]
> Can iov_iter_sendpages_ok() skip the last page of a multi-page bvec that
> has an unaligned bv_offset?
>
> sendpages_ok() starts at page + (offset >> PAGE_SHIFT) and steps
> PAGE_SIZE for len bytes. For example, with bv_offset=2048 and
> bv_len=4096 the range covers two pages, but only one is checked with
> sendpage_ok().
>
> The bvec builder in pcs_krpc.c (umem branch) sets bv_offset to a
> non-page-aligned value and bv_len up to the contiguous length, so such
> bvecs look possible:
>
> bvec->bv_offset = offset;
> ...
> bvec->bv_len = len < contig ? len : contig;
>
> If that happens, MSG_SPLICE_PAGES could be set in do_sockio_sendmsg()
> while the unchecked page is a slab page or has a zero refcount. That
> page would then be spliced into the socket by reference instead of being
> copied. Is the page check meant to cover the pages that the byte range
> actually touches?
>
>> +static int do_sockio_sendmsg(struct pcs_sockio *sio, struct pcs_msg *msg)
>> +{
> [ ... ]
>
>> + while (sio->write_offset < msg->size) {
>> + size_t left = msg->size - sio->write_offset;
>> + struct msghdr sockmsg = {.msg_flags = flags};
>> +
>> + TRACE("sio(%px) offset:%d msg:%px left:%zu, it->len:%zu\n", sio, sio->write_offset, msg, left, iov_iter_count(it));
>> +
>> + if (!iov_iter_count(it)) {
>> + /* Current iter is exhausted, init new one */
>> + msg->get_iter(msg, sio->write_offset, it, WRITE);
>> + }
>> +
>> + BUG_ON(iov_iter_count(it) > left);
> [ ... ]
>
>> + while (iov_iter_count(it)) {
> [Severity: Medium]
> Can do_sockio_sendmsg() spin forever here if msg->get_iter() returns an
> iterator with a zero count while sio->write_offset < msg->size?
>
> In that case the BUG_ON(iov_iter_count(it) > left) check passes, the
> inner while (iov_iter_count(it)) loop is skipped, and sock_sendmsg() is
> never called. WARN_ON(msg_data_left(&sockmsg)) sees zero and does not
> fire. write_offset does not change, so the outer loop repeats with the
> same state and no exit, which would be a soft lockup.
>
> The old do_send_one_seg() returned 0 for an empty segment, and
> pcs_sockio_send() turned that into a WARN_ON and sio_abort(). I did not
> find a get_iter() callback that returns an empty iterator for an
> in-range offset (krpc_msg_get_data() and pcs_get_iter_inline() look
> fine), so this looks latent. Would it make sense to add a check that
> fails the message when get_iter() returns an empty iterator?
>
>> + int ret;
> [ ... ]
>
> -- Sashiko AI review · https://sashiko.vzint.dev/#/patchset/20260930-sockio-refactor-8-1-v1-0-806348b9eb26@virtuozzo.com?part=2
>
_______________________________________________
Devel mailing list
Devel@openvz.org
https://lists.openvz.org/mailman/listinfo/devel
^ permalink raw reply [flat|nested] 9+ messages in thread
* [Devel] [PATCH hci-8.1 3/3] fs/fuse kio: refactor receive path in pcs_sockio_recv()
2026-09-30 7:34 [Devel] [PATCH hci-8.1 0/3] fs/fuse kio: cut per-4k-page sendmsg/recvmsg calls in the sockio path Liu Kui
2026-09-30 7:34 ` [Devel] [PATCH hci-8.1 1/3] fs/fuse kio: teach pcs_umem about hugepage-backed mappings Liu Kui
2026-09-30 7:34 ` [Devel] [PATCH hci-8.1 2/3] fs/fuse kio: refactor transmit path in pcs_sockio_send() Liu Kui
@ 2026-09-30 7:34 ` Liu Kui
2026-09-30 7:42 ` no-reply
2026-09-30 16:09 ` [Devel] [PATCH hci-8.1 0/3] fs/fuse kio: cut per-4k-page sendmsg/recvmsg calls in the sockio path Konstantin Khorenko
3 siblings, 1 reply; 9+ messages in thread
From: Liu Kui @ 2026-09-30 7:34 UTC (permalink / raw)
To: Konstantin Khorenko; +Cc: azaitsev, devel, Liu Kui
The receive path consumes data one 4k page per kernel_recvmsg() call:
do_recv_one_seg() extracts a single page from the iov_iter, kmaps it
and receives into it, so a 1M message body costs up to 256 recvmsg
calls plus a page reference and kmap/kunmap per page.
Replace do_recv_one_seg() with do_sockio_recvmsg(), the receive twin
of do_sockio_sendmsg(): hand the whole remaining iov_iter to
sock_recvmsg() and let it drain everything available in one call.
The header read goes through the same helper too, replacing
do_sock_recv(), so the whole receive path now uses one recv primitive.
https://virtuozzo.atlassian.net/browse/VSTOR-142167
Signed-off-by: Liu Kui <kui.liu@virtuozzo.com>
---
fs/fuse/kio/pcs/pcs_sock_io.c | 80 +++++++++++--------------------------------
fs/fuse/kio/pcs/pcs_sock_io.h | 1 +
2 files changed, 21 insertions(+), 60 deletions(-)
diff --git a/fs/fuse/kio/pcs/pcs_sock_io.c b/fs/fuse/kio/pcs/pcs_sock_io.c
index 828c8a98cabf..2da6d51c898b 100644
--- a/fs/fuse/kio/pcs/pcs_sock_io.c
+++ b/fs/fuse/kio/pcs/pcs_sock_io.c
@@ -139,69 +139,24 @@ static bool pcs_should_fail_sock_io(void)
}
#endif
-static int do_sock_recv(struct socket *sock, void *buf, size_t len)
+static int do_sockio_recvmsg(struct pcs_sockio *sio, struct iov_iter *it, size_t left)
{
+ struct msghdr sockmsg = { .msg_flags = MSG_DONTWAIT | MSG_NOSIGNAL };
+ int ret;
- struct kvec iov = {buf, len};
- struct msghdr msg = { .msg_flags = MSG_DONTWAIT | MSG_NOSIGNAL };
- struct pcs_sockio __maybe_unused *sio;
- int ret = -EIO;
-
- if (pcs_should_fail_sock_io())
- goto out;
-
- ret = kernel_recvmsg(sock, &msg, &iov, 1, len, msg.msg_flags);
-out:
-
-#ifdef CONFIG_FUSE_KIO_DEBUG
- rcu_read_lock();
- sio = rcu_dereference_sk_user_data(sock->sk);
- if (sio) {
- TRACE("RET: "PEER_FMT" len:%ld ret:%d\n", PEER_ARGS(sio->netio.parent),
- len, ret);
- }
- rcu_read_unlock();
-#endif /* CONFIG_FUSE_KIO_DEBUG */
-
- return ret;
-}
-
-static int do_recv_one_seg(struct socket *sock, struct iov_iter *it, size_t left)
-{
- int ret = -EINVAL;
- size_t size = min(iov_iter_single_seg_count(it), left);
-
- if (unlikely(!size))
- return 0;
-
- if (iov_iter_is_kvec(it)) {
- struct kvec kv;
-
- iov_iter_get_kvec(it, &kv);
- if (kv.iov_len > size)
- kv.iov_len = size;
-
- ret = do_sock_recv(sock, kv.iov_base, kv.iov_len);
- if (ret > 0)
- iov_iter_advance(it, ret);
+ if (unlikely(pcs_should_fail_sock_io()))
+ return -EIO;
- } else if (iov_iter_is_bvec(it)) {
- size_t offset;
- ssize_t len;
- struct page* page;
+ sockmsg.msg_iter = *it;
+ /* The iter may cover more than what is left of the message (trash msg) */
+ iov_iter_truncate(&sockmsg.msg_iter, left);
- len = iov_iter_get_pages2(it, &page, size, 1, &offset);
- BUG_ON(len <=0);
+ ret = sock_recvmsg(sio->socket, &sockmsg, sockmsg.msg_flags);
- ret = do_sock_recv(sock, kmap(page) + offset, len);
- if (ret <= 0)
- iov_iter_revert(it, len);
- else if (ret < len)
- iov_iter_revert(it, len - ret);
+ DTRACE("sock(%px) len:%zu ret:%d\n", sio->socket, left, ret);
- kunmap(page);
- put_page(page);
- }
+ if (ret > 0)
+ iov_iter_advance(it, ret);
return ret;
}
@@ -232,8 +187,13 @@ static void pcs_sockio_recv(struct pcs_sockio *sio)
sio->read_offset = 0;
n = 0;
- if (copy)
- n = do_sock_recv(sio->socket, (char *)sio_inline_buffer(sio) + sio->hdr_ptr, copy);
+ if (copy) {
+ sio->hdr_kv.iov_base = (char *)sio_inline_buffer(sio) + sio->hdr_ptr;
+ sio->hdr_kv.iov_len = copy;
+ iov_iter_kvec(it, READ, &sio->hdr_kv, 1, copy);
+
+ n = do_sockio_recvmsg(sio, it, copy);
+ }
if (n > 0 || n == copy /* recv return 0 when copy is 0 */) {
sio->hdr_ptr += n;
@@ -279,7 +239,7 @@ static void pcs_sockio_recv(struct pcs_sockio *sio)
if (msg != PCS_TRASH_MSG)
BUG_ON(iov_iter_count(it) > msg_size - sio->read_offset);
- n = do_recv_one_seg(sio->socket, it, (size_t)(msg_size - sio->read_offset));
+ n = do_sockio_recvmsg(sio, it, (size_t)(msg_size - sio->read_offset));
if (n > 0) {
sio->read_offset += n;
} else {
diff --git a/fs/fuse/kio/pcs/pcs_sock_io.h b/fs/fuse/kio/pcs/pcs_sock_io.h
index d894aebdca7f..01c99f73dc9d 100644
--- a/fs/fuse/kio/pcs/pcs_sock_io.h
+++ b/fs/fuse/kio/pcs/pcs_sock_io.h
@@ -151,6 +151,7 @@ struct pcs_sockio
u32 current_msg_size;
int read_offset;
int write_offset;
+ struct kvec hdr_kv; /* backs read_iter for header reads */
struct iov_iter read_iter;
struct iov_iter write_iter;
struct mutex mutex;
--
Git-155)
_______________________________________________
Devel mailing list
Devel@openvz.org
https://lists.openvz.org/mailman/listinfo/devel
^ permalink raw reply [flat|nested] 9+ messages in thread* Re: [Devel] [PATCH hci-8.1 0/3] fs/fuse kio: cut per-4k-page sendmsg/recvmsg calls in the sockio path
2026-09-30 7:34 [Devel] [PATCH hci-8.1 0/3] fs/fuse kio: cut per-4k-page sendmsg/recvmsg calls in the sockio path Liu Kui
` (2 preceding siblings ...)
2026-09-30 7:34 ` [Devel] [PATCH hci-8.1 3/3] fs/fuse kio: refactor receive path in pcs_sockio_recv() Liu Kui
@ 2026-09-30 16:09 ` Konstantin Khorenko
3 siblings, 0 replies; 9+ messages in thread
From: Konstantin Khorenko @ 2026-09-30 16:09 UTC (permalink / raw)
To: Liu Kui; +Cc: devel, azaitsev
On Wed, 30 Sep 2026 15:34:30 +0800, Liu Kui wrote:
> fs/fuse kio: cut per-4k-page sendmsg/recvmsg calls in the sockio path
>
> The sockio transport submits one 4k segment per sock_sendmsg() /
> kernel_recvmsg() call, so a 1M chunk costs up to 256 socket calls
> each way. Rework both paths to hand the whole iov_iter to the socket
> in one call, and teach pcs_umem to keep hugepage-backed mappings as
> single 2M chunks so the bvec builder stops splitting them into 4k
> bio_vecs.
>
> [...]
Applied to branch-rh10-6.12.0-211.52.1.22.x.vz10-ovz, will be released in rh10-6.12.0-211.52.1.22.5.vz10, thanks!
[1/3] fs/fuse kio: teach pcs_umem about hugepage-backed mappings
https://bitbucket.org/virtuozzocore/vzkernel.vzs/commits/83f385d3a683
[2/3] fs/fuse kio: refactor transmit path in pcs_sockio_send()
https://bitbucket.org/virtuozzocore/vzkernel.vzs/commits/32b44dc7ba67
[3/3] fs/fuse kio: refactor receive path in pcs_sockio_recv()
https://bitbucket.org/virtuozzocore/vzkernel.vzs/commits/d1de79fe3866
--
Konstantin Khorenko <khorenko@virtuozzo.com>
_______________________________________________
Devel mailing list
Devel@openvz.org
https://lists.openvz.org/mailman/listinfo/devel
^ permalink raw reply [flat|nested] 9+ messages in thread