All of lore.kernel.org
 help / color / mirror / Atom feed
From: Usama Arif <usama.arif@linux.dev>
To: Kiryl Shutsemau <kirill@shutemov.name>
Cc: Usama Arif <usama.arif@linux.dev>,
	akpm@linux-foundation.org,
	"Matthew Wilcox (Oracle)" <willy@infradead.org>,
	David Hildenbrand <david@kernel.org>,
	Lorenzo Stoakes <ljs@kernel.org>,
	"Liam R. Howlett" <liam@infradead.org>,
	Vlastimil Babka <vbabka@kernel.org>,
	Mike Rapoport <rppt@kernel.org>,
	Suren Baghdasaryan <surenb@google.com>,
	Michal Hocko <mhocko@suse.com>, Jan Kara <jack@suse.cz>,
	Rik van Riel <riel@surriel.com>, Harry Yoo <harry@kernel.org>,
	Lance Yang <lance.yang@linux.dev>, Jann Horn <jannh@google.com>,
	Alexander Viro <viro@zeniv.linux.org.uk>,
	Christian Brauner <brauner@kernel.org>,
	"Darrick J. Wong" <djwong@kernel.org>,
	Carlos Maiolino <cem@kernel.org>,
	Pedro Falcato <pfalcato@suse.de>,
	linux-mm@kvack.org, linux-fsdevel@vger.kernel.org,
	linux-xfs@vger.kernel.org, linux-kernel@vger.kernel.org,
	kernel-team@meta.com, "Kiryl Shutsemau (Meta)" <kas@kernel.org>
Subject: Re: [RFC PATCH 1/5] mm: let folio_mkclean() report which pages had dirty PTEs
Date: Wed,  9 Sep 2026 03:12:10 -0700	[thread overview]
Message-ID: <20260909101212.894871-1-usama.arif@linux.dev> (raw)
In-Reply-To: <20260903182943.662461-2-kirill@shutemov.name>

On Thu,  3 Sep 2026 19:29:39 +0100 Kiryl Shutsemau <kirill@shutemov.name> wrote:

> From: "Kiryl Shutsemau (Meta)" <kas@kernel.org>
> 
> folio_mkclean() walks every mapping of a folio and clears the dirty and
> write bits of each page table entry. It has the per-entry dirty bit in
> hand while doing that, but only counts how many entries it cleaned.
> 
> For a large folio those bits are the only record of which parts of the
> folio were written through a mapping. Everything downstream has to
> assume the whole folio changed because that information is dropped here.
> 
> Add folio_mkclean_dirtymap(), which takes a bitmap and sets a bit for every
> page of the folio whose entry was dirty. folio_mkclean() becomes a
> wrapper that passes no bitmap, so there is no change in behaviour yet.
> 
> A PMD entry has one dirty bit for the whole folio, so a PMD-mapped folio
> reports all of its pages as dirty. That is the best that can be done:
> the hardware does not track anything finer for a PMD.
> 
> Assisted-by: Claude:claude-opus-5
> Signed-off-by: Kiryl Shutsemau (Meta) <kas@kernel.org>
> ---
>  include/linux/rmap.h |  7 ++++++
>  mm/rmap.c            | 56 +++++++++++++++++++++++++++++++++++---------
>  2 files changed, 52 insertions(+), 11 deletions(-)
> 
> diff --git a/include/linux/rmap.h b/include/linux/rmap.h
> index 8dc0871e5f00..6fc0a6020252 100644
> --- a/include/linux/rmap.h
> +++ b/include/linux/rmap.h
> @@ -927,6 +927,7 @@ unsigned long page_address_in_vma(const struct folio *folio,
>   * returns the number of cleaned PTEs.
>   */
>  int folio_mkclean(struct folio *);
> +int folio_mkclean_dirtymap(struct folio *folio, unsigned long *dirty_map);
>  
>  int mapping_wrprotect_range(struct address_space *mapping, pgoff_t pgoff,
>  		unsigned long pfn, unsigned long nr_pages);
> @@ -990,6 +991,12 @@ static inline int folio_mkclean(struct folio *folio)
>  {
>  	return 0;
>  }
> +
> +static inline int folio_mkclean_dirtymap(struct folio *folio,
> +					 unsigned long *dirty_map)
> +{
> +	return 0;
> +}
>  #endif	/* CONFIG_MMU */
>  
>  #endif	/* _LINUX_RMAP_H */
> diff --git a/mm/rmap.c b/mm/rmap.c
> index 1f72d279ba68..aaf45ac79fa8 100644
> --- a/mm/rmap.c
> +++ b/mm/rmap.c
> @@ -1100,12 +1100,18 @@ int folio_referenced(struct folio *folio, int is_locked,
>  	return rwc.contended ? -1 : pra.referenced;
>  }
>  
> -static int page_vma_mkclean_one(struct page_vma_mapped_walk *pvmw)
> +struct mkclean_state {
> +	unsigned long *dirty_map;
> +	int cleaned;
> +};
> +
> +static int page_vma_mkclean_one(struct page_vma_mapped_walk *pvmw,
> +				unsigned long *dirty_map)
>  {
> -	int cleaned = 0;
>  	struct vm_area_struct *vma = pvmw->vma;
> -	struct mmu_notifier_range range;
>  	unsigned long address = pvmw->address;
> +	struct mmu_notifier_range range;
> +	int cleaned = 0;
>  
>  	/*
>  	 * We have to assume the worse case ie pmd for invalidation. Note that
> @@ -1134,6 +1140,15 @@ static int page_vma_mkclean_one(struct page_vma_mapped_walk *pvmw)
>  			if (!pte_dirty(entry) && !pte_write(entry))
>  				continue;
>  
> +			if (dirty_map && pte_dirty(entry)) {
> +				pgoff_t idx = linear_page_index(vma, address) -
> +					      pvmw->pgoff;
> +
> +				/* The walk only visits pages of this folio */
> +				VM_WARN_ON_ONCE(idx >= pvmw->nr_pages);
> +				__set_bit(idx, dirty_map);
> +			}
> +

The patch reads the PTE first and then records pte_dirty(entry) in the bitmap
before invalidating the PTE.

Holding the page-table lock prevents another kernel thread from changing
the PTE, but it does not prevent the CPU from setting the hardware dirty
bit.

This sequence is possible:

  Writeback CPU                  Application CPU
  -------------                  ---------------
  ptep_get(): writable, clean
                                 store through the PTE
                                 hardware sets dirty
  ptep_clear_flush(): returns dirty PTE
  pte_mkclean()
  reinstall clean, read-only PTE

The bitmap was populated from the first, clean snapshot. The dirty state
returned by ptep_clear_flush() is discarded.

With range-aware iomap writeback:

- The folio can still be selected for writeback.
- The affected filesystem block is absent from the iomap dirty bitmap.
- Writeback clears the folio dirty state without writing that block.
- Later eviction can discard the modified data.

The old whole-folio behavior did not need to know which PTE became
dirty, so this timing window was harmless. Sub-folio tracking makes the
final dirty state correctness-critical.

The PMD path has the identical race between pmdp_get() and
pmdp_invalidate().


>  			flush_cache_page(vma, address, pte_pfn(entry));
>  			entry = ptep_clear_flush(vma, address, pte);
>  			entry = pte_wrprotect(entry);
> @@ -1155,6 +1170,9 @@ static int page_vma_mkclean_one(struct page_vma_mapped_walk *pvmw)
>  			if (!pmd_dirty(entry) && !pmd_write(entry))
>  				continue;
>  
> +			if (dirty_map && pmd_dirty(entry))
> +				bitmap_set(dirty_map, 0, pvmw->nr_pages);
> +
>  			flush_cache_range(vma, address,
>  					  address + HPAGE_PMD_SIZE);
>  			entry = pmdp_invalidate(vma, address, pmd);
> @@ -1181,9 +1199,9 @@ static bool page_mkclean_one(struct folio *folio, struct vm_area_struct *vma,
>  			     unsigned long address, void *arg)
>  {
>  	DEFINE_FOLIO_VMA_WALK(pvmw, folio, vma, address, PVMW_SYNC);
> -	int *cleaned = arg;
> +	struct mkclean_state *state = arg;
>  
> -	*cleaned += page_vma_mkclean_one(&pvmw);
> +	state->cleaned += page_vma_mkclean_one(&pvmw, state->dirty_map);
>  
>  	return true;
>  }
> @@ -1196,12 +1214,23 @@ static bool invalid_mkclean_vma(struct vm_area_struct *vma, void *arg)
>  	return true;
>  }
>  
> -int folio_mkclean(struct folio *folio)
> +/**
> + * folio_mkclean_dirtymap - Write-protect a folio and report what was dirty.
> + * @folio: The folio to clean.
> + * @dirty_map: Bitmap of at least folio_nr_pages(@folio) bits, or NULL.
> + *
> + * Write-protects and cleans every mapping of @folio.  With @dirty_map, sets a
> + * bit for each page whose entry was dirty; a PMD-mapped folio has one dirty
> + * bit for all of it, so every page is reported.
> + *
> + * Return: the number of page table entries cleaned.
> + */
> +int folio_mkclean_dirtymap(struct folio *folio, unsigned long *dirty_map)
>  {
> -	int cleaned = 0;
> +	struct mkclean_state state = { .dirty_map = dirty_map };
>  	struct address_space *mapping;
>  	struct rmap_walk_control rwc = {
> -		.arg = (void *)&cleaned,
> +		.arg = (void *)&state,
>  		.rmap_one = page_mkclean_one,
>  		.invalid_vma = invalid_mkclean_vma,
>  	};
> @@ -1217,7 +1246,12 @@ int folio_mkclean(struct folio *folio)
>  
>  	rmap_walk(folio, &rwc);
>  
> -	return cleaned;
> +	return state.cleaned;
> +}
> +
> +int folio_mkclean(struct folio *folio)
> +{
> +	return folio_mkclean_dirtymap(folio, NULL);
>  }
>  EXPORT_SYMBOL_GPL(folio_mkclean);
>  
> @@ -1241,7 +1275,7 @@ static bool mapping_wrprotect_range_one(struct folio *folio,
>  		.flags		= PVMW_SYNC,
>  	};
>  
> -	state->cleaned += page_vma_mkclean_one(&pvmw);
> +	state->cleaned += page_vma_mkclean_one(&pvmw, NULL);
>  
>  	return true;
>  }
> @@ -1324,7 +1358,7 @@ int pfn_mkclean_range(unsigned long pfn, unsigned long nr_pages, pgoff_t pgoff,
>  	pvmw.address = vma_address(vma, pgoff, nr_pages);
>  	VM_BUG_ON_VMA(pvmw.address == -EFAULT, vma);
>  
> -	return page_vma_mkclean_one(&pvmw);
> +	return page_vma_mkclean_one(&pvmw, NULL);
>  }
>  
>  static void __folio_mod_stat(struct folio *folio, int nr, int nr_pmdmapped)
> -- 
> 2.54.0
> 
> 

  reply	other threads:[~2026-09-09 10:12 UTC|newest]

Thread overview: 15+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-03 18:29 [RFC PATCH 0/5] mm: sub-folio dirty tracking for PTE-mapped mmap writes Kiryl Shutsemau
2026-09-03 18:29 ` [RFC PATCH 1/5] mm: let folio_mkclean() report which pages had dirty PTEs Kiryl Shutsemau
2026-09-09 10:12   ` Usama Arif [this message]
2026-09-10 13:36     ` Kiryl Shutsemau
2026-09-03 18:29 ` [RFC PATCH 2/5] mm: add a_ops->dirty_folio_range() and use the mkclean dirty harvest Kiryl Shutsemau
2026-09-09 10:51   ` Usama Arif
2026-09-10 14:54     ` Kiryl Shutsemau
2026-09-03 18:29 ` [RFC PATCH 3/5] mm: keep the mmap dirty range down to the faulting page Kiryl Shutsemau
2026-09-03 18:29 ` [RFC PATCH 4/5] iomap: narrow page_mkwrite() dirtying " Kiryl Shutsemau
2026-09-03 18:29 ` [RFC PATCH 5/5] xfs: track mmap dirty state per block Kiryl Shutsemau
2026-09-03 19:55 ` [RFC PATCH 0/5] mm: sub-folio dirty tracking for PTE-mapped mmap writes Pedro Falcato
2026-09-03 21:18   ` Kiryl Shutsemau
2026-09-07 10:15 ` Kiryl Shutsemau
2026-09-09 10:02 ` Usama Arif
2026-09-09 10:15   ` Kiryl Shutsemau

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=20260909101212.894871-1-usama.arif@linux.dev \
    --to=usama.arif@linux.dev \
    --cc=akpm@linux-foundation.org \
    --cc=brauner@kernel.org \
    --cc=cem@kernel.org \
    --cc=david@kernel.org \
    --cc=djwong@kernel.org \
    --cc=harry@kernel.org \
    --cc=jack@suse.cz \
    --cc=jannh@google.com \
    --cc=kas@kernel.org \
    --cc=kernel-team@meta.com \
    --cc=kirill@shutemov.name \
    --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=mhocko@suse.com \
    --cc=pfalcato@suse.de \
    --cc=riel@surriel.com \
    --cc=rppt@kernel.org \
    --cc=surenb@google.com \
    --cc=vbabka@kernel.org \
    --cc=viro@zeniv.linux.org.uk \
    --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 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.