From: Zi Yan <ziy@nvidia.com>
To: Fang Xieyan <fangxy@xiaopeng.com>,
Andrew Morton <akpm@linux-foundation.org>,
David Hildenbrand <david@kernel.org>,
Matthew Wilcox <willy@infradead.org>, Jan Kara <jack@suse.cz>,
Hugh Dickins <hughd@google.com>, Carlos Maiolino <cem@kernel.org>,
Pankaj Raghav <p.raghav@samsung.com>,
Luiz Capitulino <luizcap@redhat.com>
Cc: Baolin Wang <baolin.wang@linux.alibaba.com>,
"Liam R . Howlett" <liam@infradead.org>,
Nico Pache <nico.pache@linux.dev>,
Ryan Roberts <ryan.roberts@arm.com>, Dev Jain <dev.jain@arm.com>,
Barry Song <baohua@kernel.org>, Lance Yang <lance.yang@linux.dev>,
Usama Arif <usama.arif@linux.dev>,
Kiryl Shutsemau <kas@kernel.org>,
Lorenzo Stoakes <ljs@kernel.org>,
Vlastimil Babka <vbabka@kernel.org>,
Mike Rapoport <rppt@kernel.org>,
Suren Baghdasaryan <surenb@google.com>,
Michal Hocko <mhocko@suse.com>,
Muchun Song <muchun.song@linux.dev>,
Kairui Song <kasong@tencent.com>, <linux-mm@kvack.org>,
<linux-kernel@vger.kernel.org>, <linux-fsdevel@vger.kernel.org>,
<linux-xfs@vger.kernel.org>
Subject: Re: [RFC PATCH 0/3] mm: enable filesystem page-cache large folios without THP
Date: Mon, 09 Nov 2026 23:11:16 -0500 [thread overview]
Message-ID: <FB183619-34C4-4B9E-9BFD-D5F9233E9C13@nvidia.com> (raw)
In-Reply-To: <DMR8IXC61XST.3GFDCM6FZ5OCM@nvidia.com>
+Luiz for real, sorry for the fat fingers.
On 9 Nov 2026, at 23:10, Zi Yan wrote:
> +Luiz, since he is working on "mm: thp: always enable mTHP support"[2].
>
> [2] https://lore.kernel.org/all/cover.1789695931.git.luizcap@redhat.com/
>
> On Mon Nov 9, 2026 at 11:01 PM EST, Zi Yan wrote:
>> +Pankaj, since he was working on this[1].
>>
>> [1] https://lore.kernel.org/all/20251206030858.1418814-1-p.raghav@samsung.com/
>>
>> On Mon Nov 9, 2026 at 10:52 PM EST, Fang Xieyan wrote:
>>> This series makes filesystem page-cache large folios work on kernels
>>> built with CONFIG_TRANSPARENT_HUGEPAGE=n, and keeps them splittable
>>> there.
>>>
>>> 1. Why this is needed
>>>
>>> TRANSPARENT_HUGEPAGE depends on !PREEMPT_RT (mm/Kconfig), so a
>>> PREEMPT_RT kernel always has THP off. Today that also removes
>>> file-backed large folios entirely: the helpers that split them live in
>>> mm/huge_memory.c under CONFIG_TRANSPARENT_HUGEPAGE, and the order
>>> accessors in include/linux/pagemap.h short-circuit to "no large
>>> folios" when THP is off, so every filesystem that opts in silently
>>> ends up with order-0 folios.
>>>
>>> The consequence is not only throughput. XFS allows block sizes up to
>>> 64K and fs/xfs/xfs_super.c refuses to mount anything larger than
>>> mapping_max_folio_size_supported(). With THP=n that helper reports
>>> PAGE_SIZE, so a 64K-block filesystem cannot be mounted at all:
>>>
>>> XFS (vda): block size (65536 bytes) not supported; Only block size (4096) or less is supported
>>>
>>> There is no degraded mode to fall back to, because a block has to fit
>>> in a single page-cache folio - which is why
>>> fs/xfs/libxfs/xfs_ialloc.c derives min_folio_order from
>>> sb_blocklog - PAGE_SHIFT. Either page-cache large folios work without
>>> THP, or the filesystem is unusable.
>>>
>>> 2. What the series does
>>>
>>> The anon and file split helpers were already separated by Kairui
>>> Song's "mm/huge_memory: clean up and decouple the anon and file split
>>> helpers" (v6 04/17, in mm-unstable); that is the base this series
>>> starts from.
>>>
>>> 1/3 moves the folio-splitting implementation out of mm/huge_memory.c
>>> into mm/folio_split.c without changing any behaviour. The new object
>>> is still built by CONFIG_TRANSPARENT_HUGEPAGE, so a THP build keeps
>>> exactly the code it had before; a pure move is also the easiest thing
>>> to rebase on top of further churn in this area.
>>>
>>> 2/3 builds that object for CONFIG_LARGE_FOLIO instead, and drops the
>>> anon, PMD and khugepaged dependencies a file-only build cannot have.
>>> The symbol comes from Matthew Wilcox's "[PATCH v9 12/15]"; if that
>>> series is in first, this patch shrinks to its Kconfig default and the
>>> accessors it already rewrites.
>>>
>>> 3/3 enables filesystem page-cache large folios when THP is off. It
>>> lifts the compile-time short circuits in pagemap.h for
>>> mapping_max_folio_size_supported(), mapping_set_folio_order_range(),
>>> mapping_max_folio_order() and mapping_min_folio_order().
>>> PREFERRED_MAX_PAGECACHE_ORDER is left alone: an LF-only build keeps
>>> the existing cap of order 8 instead of following HPAGE_PMD_ORDER.
>>> Without THP such folios cannot be PMD-mapped, so raising the ceiling
>>> would mainly increase the maximum page-cache allocation size. The
>>> changelog has the arm64 64K-page arithmetic, which is a calculation
>>> and not a measurement.
>>>
>>> The split side is not rewritten: truncate, migration and memory-failure
>>> already call the helpers outside any THP ifdef, so with 2/3 in place
>>> they simply stop resolving to the -EINVAL stubs once 3/3 turns
>>> allocation on. madvise is inside a THP ifdef and does not take part.
>>>
>>> 3/3 also makes the XFS mount failure above say why it failed: when
>>> CONFIG_LARGE_FOLIO is not set, the warning now names the missing
>>> option instead of only reporting the block size as unsupported. With
>>> LF=y the added warning is compiled out completely.
>>>
>>> 3. Testing
>>>
>>> Six configurations build clean as full kernels, bzImage and the
>>> vmlinux link included, with no new warnings: A (THP=y), B (THP=n,
>>> LF=y), C (B plus HUGETLBFS), D (THP=n, LF=n), and RT-B and RT-C with
>>> PREEMPT_RT=y. The PREEMPT_RT configurations were build-tested only.
>>> No runtime PREEMPT_RT stress test or livelock validation was
>>> performed.
>>>
>>> THP=y is unchanged: the .text sections of mm/filemap.o,
>>> mm/readahead.o and mm/shmem.o are byte-identical before and after the
>>> series (md5 equal, 45896, 7139 and 35314 bytes). With THP=n and LF=n
>>> mm/filemap.o and mm/readahead.o stay identical (44584 and 6259 bytes)
>>> and every function in mm/shmem.o keeps its size; the only difference
>>> there is one dead argument load that the folded-out
>>> mapping_set_large_folios() call leaves behind, so the section keeps
>>> its 29778 bytes while its md5 does not match.
>>>
>>> In the new LF=y, THP=n configuration mm/shmem.o keeps its size as well,
>>> 48 bytes shorter at 29730. That difference is not from 3/3: 40 of the
>>> bytes are shmem_writeout(), which shrinks because 2/3 turns
>>> split_folio_to_list() from an inline -EINVAL stub into a real call, and
>>> the other 8 are the padding in front of the following function; the
>>> same 29730 bytes appear with 2/3 alone, and 3/3's own shmem hunk
>>> changes no function size. The new call is unreachable in this
>>> configuration anyway, since shmem_alloc_and_add_folio() still forces
>>> order 0 without THP.
>>>
>>> Where the generated code really changes is the allocation policy:
>>> mm/readahead.o grows by 880 bytes of .text (6259 to 7139) and
>>> mm/filemap.o by 134 (44584 to 44718). nm -u mm/folio_split.o lists no
>>> anon, pmd or khugepaged symbols.
>>>
>>> Runtime, x86_64, 4K pages, THP=n, QEMU/KVM, on a 64K-block XFS image
>>> (mkfs.xfs -b size=64k -s size=4k), with kprobes on
>>> filemap_alloc_folio, filemap_add_folio and __folio_split:
>>>
>>> - writing 64M: 84 allocations - 59 at order-8, 5 at order-7, 5 at
>>> order-6, 5 at order-5, 10 at order-4, i.e. exactly 64M;
>>> - reading the same 64M back after drop_caches: 72 allocations,
>>> 62 at order-8, 1 at order-7, 5 at order-6, 4 at order-4;
>>> - truncating 1M to 700K: one __folio_split, landing at order-4,
>>> which is the min_folio_order XFS asked for; order-0 never appears
>>> in any of the three phases;
>>> - tmpfs writing 32M: 8192 shmem_alloc_folio calls, all order-0;
>>> - shmem write vs F_SEAL_GROW, reproducible as: memfd_create(),
>>> ftruncate() to exactly 4096 bytes, F_ADD_SEALS with F_SEAL_GROW,
>>> then a single pwrite() of 8192 bytes at offset 0, comparing the
>>> syscall's return value. It returns 4096, the same as the
>>> pre-series THP=n kernel. A THP=y kernel returns -EPERM here, and
>>> so did the LF-only build before shmem's mapping was put back
>>> behind THP; that -EPERM is pre-existing behaviour of THP kernels,
>>> this series neither introduces it nor extends it to other
>>> configurations.
>>>
>>> Same image on the pre-series kernel, for reference: it fails to mount
>>> with -ENOSYS before any of the above can run. The whole probe was
>>> re-run on the final revision of the series with identical numbers.
>>>
>>> 4. What this turns on for existing filesystems
>>>
>>> LF=y makes the existing opt-in take effect for every filesystem that
>>> already asks for large folios, and that list is not ext4 only: ext4,
>>> xfs, btrfs, f2fs, fuse, afs, erofs, nfs, cifs and zonefs all call
>>> mapping_set_folio_order_range() or mapping_set_large_folios() from
>>> their inode or superblock setup, unconditionally, today. Those paths
>>> run on any THP=y kernel; the new configuration is only THP=n with
>>> LF=y. With THP off the ceiling is PREFERRED_MAX_PAGECACHE_ORDER,
>>> which stays at order 8.
>>>
>>> On the tested 4K-page configuration, 4K-block ext4 gets min order 0
>>> and max order 8, while 64K-block XFS gets min order 4. Btrfs derives
>>> both ends from the mkfs sectorsize and nodesize. A THP=y kernel keeps
>>> the order-9 ceiling (HPAGE_PMD_ORDER) it has today on 4K pages.
>>>
>>> shmem keeps its THP dependency: its mapping is only marked large-folio
>>> capable when THP is enabled, because shmem_alloc_and_add_folio() still
>>> forces orders to zero without it. This matters beyond cosmetics.
>>> generic_perform_write() sizes its write chunk from
>>> mapping_max_folio_size() and passes that length to ->write_begin()
>>> before clamping it to folio_size(), and shmem_write_begin() rejects
>>> pos + len > i_size for F_SEAL_GROW. On a 4K memfd sealed with
>>> F_SEAL_GROW, an 8K write at offset 0 returns 4096 on the pre-series
>>> THP=n kernel and -EPERM on a THP=y kernel; advertising order 8 while
>>> allocating order 0 reproduced the -EPERM on an LF-only build. Tying
>>> shmem's policy to LARGE_FOLIO means the allocation, swap and split
>>> sides together, which is a separate series.
>>>
>>> 5. What is not covered
>>>
>>> Since PREEMPT_RT is the motivation, the RT side needs to be explicit.
>>> The freeze-to-xarray-update window in __folio_freeze_split_file() is
>>> the fourth freeze site named by Matthew Wilcox in the RT livelock
>>> thread started by Dong Hui
>>> (<c2ff0c66422c4e2d869499a1186ff0ae@xiaomi.com>):
>>>
>>> There's also __folio_freeze_split_file(). I know PREEMPT_RT
>>> disables TRANSPARENT_HUGEPAGE at the moment, but hopefully that
>>> restriction will be lifted.
>>>
>>> This series does not fix that problem and does not claim to be
>>> RT-safe. It only makes a code path that already exists on THP kernels
>>> reachable in a configuration that has no THP, which is the same
>>> exposure Kairui described in 06/17 as "should be fixed generically".
>>> One detail that may matter if a reader-side fix is chosen: the
>>> function already holds the xarray lock until the folio is no longer
>>> frozen and visible, since folio_ref_unfreeze() happens before
>>> xas_unlock_irq(), so a generic fix of that shape would cover this path
>>> without touching it.
>>>
>>> Also not tested here: the migration, memory-failure and madvise split
>>> entries at runtime (only truncate was exercised end to end), arm64
>>> with 64K pages, nommu, KASAN and lockdep, xfstests, and the btrfs
>>> order range.
>>>
>>> 6. Questions
>>>
>>> (a) Non-THP split accounting. count_mthp_stat() is a no-op without
>>> THP, so in an LF-only build a file split updates no counter at all and
>>> THP_SPLIT_PAGE does not even appear in /proc/vmstat. I have a
>>> follow-up patch that gives the file path its own counter. Would you
>>> rather see a new vmstat item, an existing one reused, or nothing?
>>>
>>> (b) Should XFS select LARGE_FOLIO itself? Three lines of
>>> "select LARGE_FOLIO if MMU" in fs/xfs/Kconfig make 64K-block XFS
>>> mountable on a THP=n kernel with no manual configuration; I built such
>>> a kernel and mounted the 64K image. The cost is mm/folio_split.o: 3.2K
>>> of .text in a THP=n build, code that every THP=y build already carries,
>>> paid also by XFS builds that will never use a block size above the page
>>> size. Kconfig cannot express "only when a 64K filesystem is mounted".
>>> I left it out because it changes default coverage for a decision XFS
>>> maintainers should make, but I can add it as 4/4 if that is preferred.
>>>
>>> Comments welcome.
>>>
>>> Fang Xieyan (3):
>>> mm: move folio split code into mm/folio_split.c
>>> mm: build file-backed folio splitting with CONFIG_LARGE_FOLIO
>>> mm: enable filesystem page-cache large folios without THP
>>>
>>> MAINTAINERS | 1 +
>>> fs/xfs/xfs_super.c | 2 +
>>> include/linux/huge_mm.h | 60 ++-
>>> include/linux/page-flags.h | 5 +-
>>> include/linux/pagemap.h | 17 +-
>>> mm/Kconfig | 19 +
>>> mm/Makefile | 1 +
>>> mm/folio_split.c | 969 +++++++++++++++++++++++++++++++++++++
>>> mm/huge_memory.c | 920 -----------------------------------
>>> mm/internal.h | 1 +
>>> mm/shmem.c | 11 +-
>>> 11 files changed, 1054 insertions(+), 952 deletions(-)
>>> create mode 100644 mm/folio_split.c
>>>
>>>
>>> base-commit: b024a51b0184b1efb6f55b11aaf7b15a490a6307
>
>
>
>
> --
> Best Regards,
> Yan, Zi
Best Regards,
Yan, Zi
prev parent reply other threads:[~2026-11-10 4:11 UTC|newest]
Thread overview: 7+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-10 3:52 Fang Xieyan
2026-11-10 3:52 ` [RFC PATCH 1/3] mm: move folio split code into mm/folio_split.c Fang Xieyan
2026-11-10 3:52 ` [RFC PATCH 2/3] mm: build file-backed folio splitting with CONFIG_LARGE_FOLIO Fang Xieyan
2026-11-10 3:52 ` [RFC PATCH 3/3] mm: enable filesystem page-cache large folios without THP Fang Xieyan
2026-11-10 4:01 ` [RFC PATCH 0/3] " Zi Yan
2026-11-10 4:10 ` Zi Yan
2026-11-10 4:11 ` Zi Yan [this message]
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=FB183619-34C4-4B9E-9BFD-D5F9233E9C13@nvidia.com \
--to=ziy@nvidia.com \
--cc=akpm@linux-foundation.org \
--cc=baohua@kernel.org \
--cc=baolin.wang@linux.alibaba.com \
--cc=cem@kernel.org \
--cc=david@kernel.org \
--cc=dev.jain@arm.com \
--cc=fangxy@xiaopeng.com \
--cc=hughd@google.com \
--cc=jack@suse.cz \
--cc=kas@kernel.org \
--cc=kasong@tencent.com \
--cc=lance.yang@linux.dev \
--cc=liam@infradead.org \
--cc=linux-fsdevel@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-mm@kvack.org \
--cc=linux-xfs@vger.kernel.org \
--cc=ljs@kernel.org \
--cc=luizcap@redhat.com \
--cc=mhocko@suse.com \
--cc=muchun.song@linux.dev \
--cc=nico.pache@linux.dev \
--cc=p.raghav@samsung.com \
--cc=rppt@kernel.org \
--cc=ryan.roberts@arm.com \
--cc=surenb@google.com \
--cc=usama.arif@linux.dev \
--cc=vbabka@kernel.org \
--cc=willy@infradead.org \
/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®