All Virtuozzo development lists (kernel + QEMU)
 help / color / mirror / Atom feed
From: Liu Kui <kui.liu@virtuozzo.com>
To: Konstantin Khorenko <khorenko@virtuozzo.com>
Cc: azaitsev@virtuozzo.com, devel@openvz.org,
	Liu Kui <kui.liu@virtuozzo.com>
Subject: [Devel] [PATCH hci-8.1 1/3] fs/fuse kio: teach pcs_umem about hugepage-backed mappings
Date: Wed, 30 Sep 2026 15:34:31 +0800	[thread overview]
Message-ID: <20260930-sockio-refactor-8-1-v1-1-806348b9eb26@virtuozzo.com> (raw)
In-Reply-To: <20260930-sockio-refactor-8-1-v1-0-806348b9eb26@virtuozzo.com>

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

  reply	other threads:[~2026-09-30  7:36 UTC|newest]

Thread overview: 9+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
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 [this message]
2026-09-30  7:41   ` [Devel] [PATCH hci-8.1 1/3] fs/fuse kio: teach pcs_umem about hugepage-backed mappings 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
2026-09-30  7:42   ` no-reply
2026-09-30 16:29     ` Konstantin Khorenko
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  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

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=20260930-sockio-refactor-8-1-v1-1-806348b9eb26@virtuozzo.com \
    --to=kui.liu@virtuozzo.com \
    --cc=azaitsev@virtuozzo.com \
    --cc=devel@openvz.org \
    --cc=khorenko@virtuozzo.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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.