[RFC PATCH 0/5] mm: sub-folio dirty tracking for PTE-mapped mmap writes

Kiryl Shutsemau posted 5 patches 3 weeks, 1 day ago
fs/iomap/buffered-io.c | 37 +++++++++++++-----
fs/xfs/xfs_aops.c      |  2 +-
include/linux/fs.h     |  3 ++
include/linux/iomap.h  |  2 +
include/linux/mm.h     |  1 +
include/linux/rmap.h   |  7 ++++
mm/memory.c            | 58 ++++++++++++++++++++++++++--
mm/page-writeback.c    | 87 +++++++++++++++++++++++++++++++++++++++---
mm/rmap.c              | 56 +++++++++++++++++++++------
9 files changed, 222 insertions(+), 31 deletions(-)
[RFC PATCH 0/5] mm: sub-folio dirty tracking for PTE-mapped mmap writes
Posted by Kiryl Shutsemau 3 weeks, 1 day ago
From: "Kiryl Shutsemau (Meta)" <kas@kernel.org>

A store through a shared file mapping dirties the whole folio. With large
page cache folios that turns a 4K store into 2M of writeback: one dirty
bit per folio, and writeback has no way to know which part changed.

XFS already knows better. iomap tracks dirty state per block and
iomap_writeback_folio() submits only the dirty ranges, and the buffered
write path sets just the range it copied. Only the mmap path throws that
away, because iomap_dirty_folio() covers the whole folio.

Narrowing the dirtying at page_mkwrite() time does not work on its own:
set_pte_range() batch-maps a whole folio writable on the first shared
write fault, so the stores that follow never fault and never reach the
filesystem.

So harvest the hardware instead. folio_clear_dirty_for_io() already calls
folio_mkclean(), whose rmap walk reads pte_dirty() for every entry of the
folio and throws it away. Those bits are the only record of which parts
of a large folio were written through a mapping. Collect them there and
hand the filesystem the runs that were dirty, through a new
a_ops->dirty_folio_range().

All of this is about PTE-mapped folios. A PMD-mapped folio has a single
dirty bit for the 2M it maps, so there is nothing finer to harvest, and
it keeps writing back whole. Keeping shared write faults off PMDs is a
separate patch and not part of this posting.

On a 512M file in 2M folios on XFS, storing one byte per folio and
calling msync() wrote 512M before and writes 1M after, with identical
minor fault counts.

Not addressed here:

 - Dirty accounting stays folio-granular. A 4K store still counts as 2M
   against dirty_ratio and balance_dirty_pages().
 - iomap_page_mkwrite() still allocates blocks for the whole folio.
 - Filesystems without per-block dirty state see no change.

Kiryl Shutsemau (Meta) (5):
  mm: let folio_mkclean() report which pages had dirty PTEs
  mm: add a_ops->dirty_folio_range() and use the mkclean dirty harvest
  mm: keep the mmap dirty range down to the faulting page
  iomap: narrow page_mkwrite() dirtying to the faulting page
  xfs: track mmap dirty state per block

 fs/iomap/buffered-io.c | 37 +++++++++++++-----
 fs/xfs/xfs_aops.c      |  2 +-
 include/linux/fs.h     |  3 ++
 include/linux/iomap.h  |  2 +
 include/linux/mm.h     |  1 +
 include/linux/rmap.h   |  7 ++++
 mm/memory.c            | 58 ++++++++++++++++++++++++++--
 mm/page-writeback.c    | 87 +++++++++++++++++++++++++++++++++++++++---
 mm/rmap.c              | 56 +++++++++++++++++++++------
 9 files changed, 222 insertions(+), 31 deletions(-)


base-commit: 0a0d1d55dad570724bf8c7ea83409639cfb4be9b
-- 
2.54.0
Re: [RFC PATCH 0/5] mm: sub-folio dirty tracking for PTE-mapped mmap writes
Posted by Usama Arif 2 weeks, 2 days ago
On Thu,  3 Sep 2026 19:29:38 +0100 Kiryl Shutsemau <kirill@shutemov.name> wrote:

> From: "Kiryl Shutsemau (Meta)" <kas@kernel.org>
> 
> A store through a shared file mapping dirties the whole folio. With large
> page cache folios that turns a 4K store into 2M of writeback: one dirty
> bit per folio, and writeback has no way to know which part changed.
> 
> XFS already knows better. iomap tracks dirty state per block and
> iomap_writeback_folio() submits only the dirty ranges, and the buffered
> write path sets just the range it copied. Only the mmap path throws that
> away, because iomap_dirty_folio() covers the whole folio.
> 
> Narrowing the dirtying at page_mkwrite() time does not work on its own:
> set_pte_range() batch-maps a whole folio writable on the first shared
> write fault, so the stores that follow never fault and never reach the
> filesystem.
> 
> So harvest the hardware instead. folio_clear_dirty_for_io() already calls
> folio_mkclean(), whose rmap walk reads pte_dirty() for every entry of the
> folio and throws it away. Those bits are the only record of which parts
> of a large folio were written through a mapping. Collect them there and
> hand the filesystem the runs that were dirty, through a new
> a_ops->dirty_folio_range().
> 
> All of this is about PTE-mapped folios. A PMD-mapped folio has a single
> dirty bit for the 2M it maps, so there is nothing finer to harvest, and
> it keeps writing back whole. Keeping shared write faults off PMDs is a
> separate patch and not part of this posting.
> 
> On a 512M file in 2M folios on XFS, storing one byte per folio and
> calling msync() wrote 512M before and writes 1M after, with identical
> minor fault counts.

Hi Kiryl,

The motivation makes sense to me. I will look into the patches.

Just wanted to check, the above xfs example, is that on an ARM host?

Thanks,
Usama

> 
> Not addressed here:
> 
>  - Dirty accounting stays folio-granular. A 4K store still counts as 2M
>    against dirty_ratio and balance_dirty_pages().
>  - iomap_page_mkwrite() still allocates blocks for the whole folio.
>  - Filesystems without per-block dirty state see no change.
> 
> Kiryl Shutsemau (Meta) (5):
>   mm: let folio_mkclean() report which pages had dirty PTEs
>   mm: add a_ops->dirty_folio_range() and use the mkclean dirty harvest
>   mm: keep the mmap dirty range down to the faulting page
>   iomap: narrow page_mkwrite() dirtying to the faulting page
>   xfs: track mmap dirty state per block
> 
>  fs/iomap/buffered-io.c | 37 +++++++++++++-----
>  fs/xfs/xfs_aops.c      |  2 +-
>  include/linux/fs.h     |  3 ++
>  include/linux/iomap.h  |  2 +
>  include/linux/mm.h     |  1 +
>  include/linux/rmap.h   |  7 ++++
>  mm/memory.c            | 58 ++++++++++++++++++++++++++--
>  mm/page-writeback.c    | 87 +++++++++++++++++++++++++++++++++++++++---
>  mm/rmap.c              | 56 +++++++++++++++++++++------
>  9 files changed, 222 insertions(+), 31 deletions(-)
> 
> 
> base-commit: 0a0d1d55dad570724bf8c7ea83409639cfb4be9b
> -- 
> 2.54.0
> 
>
Re: [RFC PATCH 0/5] mm: sub-folio dirty tracking for PTE-mapped mmap writes
Posted by Kiryl Shutsemau 2 weeks, 2 days ago
On Wed, Sep 09, 2026 at 03:02:39AM -0700, Usama Arif wrote:
> On Thu,  3 Sep 2026 19:29:38 +0100 Kiryl Shutsemau <kirill@shutemov.name> wrote:
> 
> > From: "Kiryl Shutsemau (Meta)" <kas@kernel.org>
> > 
> > A store through a shared file mapping dirties the whole folio. With large
> > page cache folios that turns a 4K store into 2M of writeback: one dirty
> > bit per folio, and writeback has no way to know which part changed.
> > 
> > XFS already knows better. iomap tracks dirty state per block and
> > iomap_writeback_folio() submits only the dirty ranges, and the buffered
> > write path sets just the range it copied. Only the mmap path throws that
> > away, because iomap_dirty_folio() covers the whole folio.
> > 
> > Narrowing the dirtying at page_mkwrite() time does not work on its own:
> > set_pte_range() batch-maps a whole folio writable on the first shared
> > write fault, so the stores that follow never fault and never reach the
> > filesystem.
> > 
> > So harvest the hardware instead. folio_clear_dirty_for_io() already calls
> > folio_mkclean(), whose rmap walk reads pte_dirty() for every entry of the
> > folio and throws it away. Those bits are the only record of which parts
> > of a large folio were written through a mapping. Collect them there and
> > hand the filesystem the runs that were dirty, through a new
> > a_ops->dirty_folio_range().
> > 
> > All of this is about PTE-mapped folios. A PMD-mapped folio has a single
> > dirty bit for the 2M it maps, so there is nothing finer to harvest, and
> > it keeps writing back whole. Keeping shared write faults off PMDs is a
> > separate patch and not part of this posting.
> > 
> > On a 512M file in 2M folios on XFS, storing one byte per folio and
> > calling msync() wrote 512M before and writes 1M after, with identical
> > minor fault counts.
> 
> Hi Kiryl,
> 
> The motivation makes sense to me. I will look into the patches.
> 
> Just wanted to check, the above xfs example, is that on an ARM host?

No, that was x86. But the math is the same on any arch with 2M THP/mTHP
in page cache and 4k PAGE_SIZE.

-- 
  Kiryl Shutsemau / Kirill A. Shutemov
Re: [RFC PATCH 0/5] mm: sub-folio dirty tracking for PTE-mapped mmap writes
Posted by Kiryl Shutsemau 2 weeks, 4 days ago
On Thu, Sep 03, 2026 at 07:29:38PM +0100, Kiryl Shutsemau wrote:
> From: "Kiryl Shutsemau (Meta)" <kas@kernel.org>
> 
> A store through a shared file mapping dirties the whole folio. With large
> page cache folios that turns a 4K store into 2M of writeback: one dirty
> bit per folio, and writeback has no way to know which part changed.
> 
> XFS already knows better. iomap tracks dirty state per block and
> iomap_writeback_folio() submits only the dirty ranges, and the buffered
> write path sets just the range it copied. Only the mmap path throws that
> away, because iomap_dirty_folio() covers the whole folio.
> 
> Narrowing the dirtying at page_mkwrite() time does not work on its own:
> set_pte_range() batch-maps a whole folio writable on the first shared
> write fault, so the stores that follow never fault and never reach the
> filesystem.
> 
> So harvest the hardware instead. folio_clear_dirty_for_io() already calls
> folio_mkclean(), whose rmap walk reads pte_dirty() for every entry of the
> folio and throws it away. Those bits are the only record of which parts
> of a large folio were written through a mapping. Collect them there and
> hand the filesystem the runs that were dirty, through a new
> a_ops->dirty_folio_range().

Boris pointed me to Matthew's proposal to remove ->dirty_folio:

https://lore.kernel.org/all/aoyWln-Gt-yvZQkE@casper.infradead.org

I agree that the current ->dirty_folio() makes little sense and that
dirtying the folio can be bundled into ->page_mkwrite(), as they are
matched 1-to-1.

My proposal makes the distinction between making the folio writable and
making it dirty meaningful. ->page_mkwrite() allocates whatever is needed
on the filesystem side to track dirty state and drive writeback for the
*folio*, while ->dirty_folio_range() marks part of the folio dirty.

We can still drop ->dirty_folio(). A filesystem can provide
->dirty_folio_range() if it wants fine-grained (sub-folio) dirty
tracking.

A separate question is whether we want to avoid installing a writable PMD
entry for filesystems that want fine-grained dirty tracking. I have a
patch for this, but it deserves a separate discussion once we agree that we
want this for PTE-mapped folios first.

Any feedback?

> All of this is about PTE-mapped folios. A PMD-mapped folio has a single
> dirty bit for the 2M it maps, so there is nothing finer to harvest, and
> it keeps writing back whole. Keeping shared write faults off PMDs is a
> separate patch and not part of this posting.
> 
> On a 512M file in 2M folios on XFS, storing one byte per folio and
> calling msync() wrote 512M before and writes 1M after, with identical
> minor fault counts.
> 
> Not addressed here:
> 
>  - Dirty accounting stays folio-granular. A 4K store still counts as 2M
>    against dirty_ratio and balance_dirty_pages().
>  - iomap_page_mkwrite() still allocates blocks for the whole folio.
>  - Filesystems without per-block dirty state see no change.

-- 
  Kiryl Shutsemau / Kirill A. Shutemov
Re: [RFC PATCH 0/5] mm: sub-folio dirty tracking for PTE-mapped mmap writes
Posted by Matthew Wilcox 1 week, 2 days ago
On Mon, Sep 07, 2026 at 11:15:15AM +0100, Kiryl Shutsemau wrote:
> Boris pointed me to Matthew's proposal to remove ->dirty_folio:
> 
> https://lore.kernel.org/all/aoyWln-Gt-yvZQkE@casper.infradead.org

Thanks, Boris ;-)

> I agree that the current ->dirty_folio() makes little sense and that
> dirtying the folio can be bundled into ->page_mkwrite(), as they are
> matched 1-to-1.
> 
> My proposal makes the distinction between making the folio writable and
> making it dirty meaningful. ->page_mkwrite() allocates whatever is needed
> on the filesystem side to track dirty state and drive writeback for the
> *folio*, while ->dirty_folio_range() marks part of the folio dirty.

Why do you think that's a meaningful distinction?  We create a writable
PTE because we've taken a page fault for write.  There's probably a few
naoseconds where the PTE is writable+clean before it becomes
writable+dirty, but even then sometimes we do both pte_mkwrite() and
pte_mkdirty() as an optimisation in the write fault path.

> We can still drop ->dirty_folio(). A filesystem can provide
> ->dirty_folio_range() if it wants fine-grained (sub-folio) dirty
> tracking.
> 
> A separate question is whether we want to avoid installing a writable PMD
> entry for filesystems that want fine-grained dirty tracking. I have a
> patch for this, but it deserves a separate discussion once we agree that we
> want this for PTE-mapped folios first.
> 
> Any feedback?

It's very odd to be optimising for shared-writable-mmap.  This is a
horrid model for I/O.  https://cs.brown.edu/people/acrotty/pubs/p13-crotty.pdf
Re: [RFC PATCH 0/5] mm: sub-folio dirty tracking for PTE-mapped mmap writes
Posted by Kiryl Shutsemau 1 week, 2 days ago
On Wed, Sep 16, 2026 at 05:27:42PM +0100, Matthew Wilcox wrote:
> On Mon, Sep 07, 2026 at 11:15:15AM +0100, Kiryl Shutsemau wrote:
> > Boris pointed me to Matthew's proposal to remove ->dirty_folio:
> > 
> > https://lore.kernel.org/all/aoyWln-Gt-yvZQkE@casper.infradead.org
> 
> Thanks, Boris ;-)
> 
> > I agree that the current ->dirty_folio() makes little sense and that
> > dirtying the folio can be bundled into ->page_mkwrite(), as they are
> > matched 1-to-1.
> > 
> > My proposal makes the distinction between making the folio writable and
> > making it dirty meaningful. ->page_mkwrite() allocates whatever is needed
> > on the filesystem side to track dirty state and drive writeback for the
> > *folio*, while ->dirty_folio_range() marks part of the folio dirty.
> 
> Why do you think that's a meaningful distinction?  We create a writable
> PTE because we've taken a page fault for write.  There's probably a few
> naoseconds where the PTE is writable+clean before it becomes
> writable+dirty, but even then sometimes we do both pte_mkwrite() and
> pte_mkdirty() as an optimisation in the write fault path.

This is true for the PTE that the fault was for. But we don't necessarily
want to dirty the other 511 pages at the same time.

The basic idea is to make the whole folio writable at fault and shift
dirtying to be per-PTE on write to it.

> > We can still drop ->dirty_folio(). A filesystem can provide
> > ->dirty_folio_range() if it wants fine-grained (sub-folio) dirty
> > tracking.
> > 
> > A separate question is whether we want to avoid installing a writable PMD
> > entry for filesystems that want fine-grained dirty tracking. I have a
> > patch for this, but it deserves a separate discussion once we agree that we
> > want this for PTE-mapped folios first.
> > 
> > Any feedback?
> 
> It's very odd to be optimising for shared-writable-mmap.  This is a
> horrid model for I/O.  https://cs.brown.edu/people/acrotty/pubs/p13-crotty.pdf

Sure.

But not everybody got the memo[1] :P

I think it worth considering if we want to make large folio adoption
smoother.

[1] https://www.reddit.com/r/bcachefs/comments/1vepk4a/comment/p1sn4v1/

-- 
  Kiryl Shutsemau / Kirill A. Shutemov
Re: [RFC PATCH 0/5] mm: sub-folio dirty tracking for PTE-mapped mmap writes
Posted by Matthew Wilcox 1 week, 2 days ago
On Wed, Sep 16, 2026 at 06:09:17PM +0100, Kiryl Shutsemau wrote:
> On Wed, Sep 16, 2026 at 05:27:42PM +0100, Matthew Wilcox wrote:
> > On Mon, Sep 07, 2026 at 11:15:15AM +0100, Kiryl Shutsemau wrote:
> > > My proposal makes the distinction between making the folio writable and
> > > making it dirty meaningful. ->page_mkwrite() allocates whatever is needed
> > > on the filesystem side to track dirty state and drive writeback for the
> > > *folio*, while ->dirty_folio_range() marks part of the folio dirty.
> > 
> > Why do you think that's a meaningful distinction?  We create a writable
> > PTE because we've taken a page fault for write.  There's probably a few
> > naoseconds where the PTE is writable+clean before it becomes
> > writable+dirty, but even then sometimes we do both pte_mkwrite() and
> > pte_mkdirty() as an optimisation in the write fault path.
> 
> This is true for the PTE that the fault was for. But we don't necessarily
> want to dirty the other 511 pages at the same time.
> 
> The basic idea is to make the whole folio writable at fault and shift
> dirtying to be per-PTE on write to it.

Hmm.  Is that something that filesystem people want?  I suspect not.
I think they'd prefer to be told when we write-fault on each page.
But let's see if we can gather feedback from FS people.

> > It's very odd to be optimising for shared-writable-mmap.  This is a
> > horrid model for I/O.  https://cs.brown.edu/people/acrotty/pubs/p13-crotty.pdf
> 
> Sure.
> 
> But not everybody got the memo[1] :P
> 
> I think it worth considering if we want to make large folio adoption
> smoother.
> 
> [1] https://www.reddit.com/r/bcachefs/comments/1vepk4a/comment/p1sn4v1/

Since I'm an idiot, I'm not inclined to be helpful.  But I think my
proposal does fix this; we call page_mkwrite() for each page fault and
then the filesystem knows which pages are actually dirty without having
to be told under the PTL when they can't reserve space.
Re: [RFC PATCH 0/5] mm: sub-folio dirty tracking for PTE-mapped mmap writes
Posted by Jan Kara 4 days, 22 hours ago
On Wed 16-09-26 18:19:29, Matthew Wilcox wrote:
> On Wed, Sep 16, 2026 at 06:09:17PM +0100, Kiryl Shutsemau wrote:
> > On Wed, Sep 16, 2026 at 05:27:42PM +0100, Matthew Wilcox wrote:
> > > On Mon, Sep 07, 2026 at 11:15:15AM +0100, Kiryl Shutsemau wrote:
> > > > My proposal makes the distinction between making the folio writable and
> > > > making it dirty meaningful. ->page_mkwrite() allocates whatever is needed
> > > > on the filesystem side to track dirty state and drive writeback for the
> > > > *folio*, while ->dirty_folio_range() marks part of the folio dirty.
> > > 
> > > Why do you think that's a meaningful distinction?  We create a writable
> > > PTE because we've taken a page fault for write.  There's probably a few
> > > naoseconds where the PTE is writable+clean before it becomes
> > > writable+dirty, but even then sometimes we do both pte_mkwrite() and
> > > pte_mkdirty() as an optimisation in the write fault path.
> > 
> > This is true for the PTE that the fault was for. But we don't necessarily
> > want to dirty the other 511 pages at the same time.
> > 
> > The basic idea is to make the whole folio writable at fault and shift
> > dirtying to be per-PTE on write to it.
> 
> Hmm.  Is that something that filesystem people want?  I suspect not.
> I think they'd prefer to be told when we write-fault on each page.
> But let's see if we can gather feedback from FS people.

Well, learning about write-fault of each page is certainly a simpler model
for filesystems to deal with. What Kirill currently does is that the fs
learns about write-fault of the whole folio (so it prepares space for it)
but then dirtying happens only for some pages (so under the rest of the
folio just remain unwritten extents / delalloc reservations). Filesystems
can accommodate that although it will currently somewhat upset ext4's
delayed allocation reservation system. We will have there unreclaimable
in-memory extents tracking these reservations and on inode eviction we'll
complain they were never used.

What might get ugly with current Kirill's approach is if some simpler
filesystem doesn't have a concept of unwritten extents and delayed
allocation (like old ext2 or vfat) but still allocates blocks in
.page_mkwrite to make sure we don't hit ENOSPC / EDQUOT during writeback.
Then it implicitely relies on current MM behavior of calling .page_mkwrite
only on pages that cannot be evicted without writeback - if the eviction
happens, we've just exposed uninitialized on disk data. In fact this is
what would happen with current ext4 when you turn off delalloc and have old
indirect-block based inode format, except that ext4 is careful enough to
dirty all the buffers underlying the folio (and the folio itself) on its
own so there's no data exposure, we just loose Kirill's optimization.

So overall I don't find unsolvable issues with this but it will certainly
require some tweaking on fs side.

								Honza
-- 
Jan Kara <jack@suse.com>
SUSE Labs, CR
Re: [RFC PATCH 0/5] mm: sub-folio dirty tracking for PTE-mapped mmap writes
Posted by Pedro Falcato 3 weeks, 1 day ago
On Thu, Sep 03, 2026 at 07:29:38PM +0100, Kiryl Shutsemau wrote:
> From: "Kiryl Shutsemau (Meta)" <kas@kernel.org>
> 
> A store through a shared file mapping dirties the whole folio. With large
> page cache folios that turns a 4K store into 2M of writeback: one dirty
> bit per folio, and writeback has no way to know which part changed.
> 
> XFS already knows better. iomap tracks dirty state per block and
> iomap_writeback_folio() submits only the dirty ranges, and the buffered
> write path sets just the range it copied. Only the mmap path throws that
> away, because iomap_dirty_folio() covers the whole folio.
> 

Hmm, I believe Willy already had some patches; I don't know what came of
them (I think he's out for now).

> Narrowing the dirtying at page_mkwrite() time does not work on its own:
> set_pte_range() batch-maps a whole folio writable on the first shared
> write fault, so the stores that follow never fault and never reach the
> filesystem.

Help me out here: in which case does this happen? page fault handling is a
mess... I think page_mkwrite is always called, no? in do_shared_fault().

> 
> So harvest the hardware instead. folio_clear_dirty_for_io() already calls
> folio_mkclean(), whose rmap walk reads pte_dirty() for every entry of the
> folio and throws it away. Those bits are the only record of which parts
> of a large folio were written through a mapping. Collect them there and
> hand the filesystem the runs that were dirty, through a new
> a_ops->dirty_folio_range().

I think this goes against what we're trying to pull off. We want a single
method of dirtying folios, not 3.

Ideally(tm) we would have a single mkwrite interface.

> 
> All of this is about PTE-mapped folios. A PMD-mapped folio has a single
> dirty bit for the 2M it maps, so there is nothing finer to harvest, and
> it keeps writing back whole. Keeping shared write faults off PMDs is a
> separate patch and not part of this posting.
> 
> On a 512M file in 2M folios on XFS, storing one byte per folio and
> calling msync() wrote 512M before and writes 1M after, with identical
> minor fault counts.

But the effects are definitely nice though :)

> 
> Not addressed here:
> 
>  - Dirty accounting stays folio-granular. A 4K store still counts as 2M
>    against dirty_ratio and balance_dirty_pages().

That is correct; I don't think doing subpage accounting makes any sense,
the whole folio is still dirty, and for reclaim purposes can't be thrown
away (unless you added gnarly split logic for file folios as well).

>  - iomap_page_mkwrite() still allocates blocks for the whole folio.
>  - Filesystems without per-block dirty state see no change.
> 
> Kiryl Shutsemau (Meta) (5):
>   mm: let folio_mkclean() report which pages had dirty PTEs
>   mm: add a_ops->dirty_folio_range() and use the mkclean dirty harvest
>   mm: keep the mmap dirty range down to the faulting page
>   iomap: narrow page_mkwrite() dirtying to the faulting page
>   xfs: track mmap dirty state per block
> 
>  fs/iomap/buffered-io.c | 37 +++++++++++++-----
>  fs/xfs/xfs_aops.c      |  2 +-
>  include/linux/fs.h     |  3 ++
>  include/linux/iomap.h  |  2 +
>  include/linux/mm.h     |  1 +
>  include/linux/rmap.h   |  7 ++++
>  mm/memory.c            | 58 ++++++++++++++++++++++++++--
>  mm/page-writeback.c    | 87 +++++++++++++++++++++++++++++++++++++++---
>  mm/rmap.c              | 56 +++++++++++++++++++++------
>  9 files changed, 222 insertions(+), 31 deletions(-)
> 
> 
> base-commit: 0a0d1d55dad570724bf8c7ea83409639cfb4be9b
> -- 
> 2.54.0
> 

-- 
Pedro
Re: [RFC PATCH 0/5] mm: sub-folio dirty tracking for PTE-mapped mmap writes
Posted by Kiryl Shutsemau 3 weeks, 1 day ago
On Thu, Sep 03, 2026 at 08:55:36PM +0100, Pedro Falcato wrote:
> On Thu, Sep 03, 2026 at 07:29:38PM +0100, Kiryl Shutsemau wrote:
> > Narrowing the dirtying at page_mkwrite() time does not work on its own:
> > set_pte_range() batch-maps a whole folio writable on the first shared
> > write fault, so the stores that follow never fault and never reach the
> > filesystem.
> 
> Help me out here: in which case does this happen? page fault handling is a
> mess... I think page_mkwrite is always called, no? in do_shared_fault().

It is always called, but once per folio, not once per page. It is the
filesystem's chance to preallocate whatever it needs to track dirty
state for the *folio*.

Later finish_fault() maps the folio. It tries to map it fully when it
can, so a write fault creates up to 512 writable PTEs on x86. And we
really do need to map the folio fully whenever we can. Otherwise we
significantly undercount mlocked memory. See commit 19773df031bc
("mm/fault: try to map the entire file folio in finish_fault()").

And once the whole folio is mapped with writable PTEs we cannot narrow
the dirtying to a subset of pages. Any of them can turn dirty at any
time, with nothing to record it.

-- 
  Kiryl Shutsemau / Kirill A. Shutemov
Re: [RFC PATCH 0/5] mm: sub-folio dirty tracking for PTE-mapped mmap writes
Posted by David Hildenbrand (Arm) 1 day, 10 hours ago
On 9/3/26 20:29, Kiryl Shutsemau wrote:
> From: "Kiryl Shutsemau (Meta)" <kas@kernel.org>
> 
> A store through a shared file mapping dirties the whole folio. With large
> page cache folios that turns a 4K store into 2M of writeback: one dirty
> bit per folio, and writeback has no way to know which part changed.
> 
> XFS already knows better. iomap tracks dirty state per block and
> iomap_writeback_folio() submits only the dirty ranges, and the buffered
> write path sets just the range it copied. Only the mmap path throws that
> away, because iomap_dirty_folio() covers the whole folio.
> 
> Narrowing the dirtying at page_mkwrite() time does not work on its own:
> set_pte_range() batch-maps a whole folio writable on the first shared
> write fault, so the stores that follow never fault and never reach the
> filesystem.
> 
> So harvest the hardware instead. folio_clear_dirty_for_io() already calls
> folio_mkclean(), whose rmap walk reads pte_dirty() for every entry of the
> folio and throws it away. Those bits are the only record of which parts
> of a large folio were written through a mapping. Collect them there and
> hand the filesystem the runs that were dirty, through a new
> a_ops->dirty_folio_range().
> 
> All of this is about PTE-mapped folios. A PMD-mapped folio has a single
> dirty bit for the 2M it maps, so there is nothing finer to harvest, and
> it keeps writing back whole. Keeping shared write faults off PMDs is a
> separate patch and not part of this posting.
> 
> On a 512M file in 2M folios on XFS, storing one byte per folio and
> calling msync() wrote 512M before and writes 1M after, with identical
> minor fault counts.

Just a note that with things like cont-pte we see the trend that we only have a
single logical dirty bit for the entire coalesced PTE. Similar to having only a
single dirty bit for a PMD-mapped THP.

-- 
Cheers,

David