<feed xmlns='http://www.w3.org/2005/Atom'>
<title>linux.git/fs/btrfs/extent_io.c, branch master</title>
<subtitle>Linux kernel source tree.</subtitle>
<id>https://git.ilvokhin.com/linux.git/atom/fs/btrfs/extent_io.c?h=master</id>
<link rel='self' href='https://git.ilvokhin.com/linux.git/atom/fs/btrfs/extent_io.c?h=master'/>
<link rel='alternate' type='text/html' href='https://git.ilvokhin.com/linux.git/'/>
<updated>2026-07-14T05:04:19Z</updated>
<entry>
<title>btrfs: fix u32 to s64 type conversion in dirty_metadata_bytes accounting</title>
<updated>2026-07-14T05:04:19Z</updated>
<author>
<name>Dave Chen</name>
<email>davechen@synology.com</email>
</author>
<published>2026-06-29T07:08:43Z</published>
<link rel='alternate' type='text/html' href='https://git.ilvokhin.com/linux.git/commit/?id=8b5a09ceb61b18b1f0797cd30a549d7dc85d8d50'/>
<id>urn:sha1:8b5a09ceb61b18b1f0797cd30a549d7dc85d8d50</id>
<content type='text'>
The percpu_counter dirty_metadata_bytes is updated by negating eb-&gt;len
and passing it to percpu_counter_add_batch(), whose amount parameter is
s64.  Since commit 84cda1a6087d ("btrfs: cache folio size and shift in
extent_buffer"), eb-&gt;len is u32.  The u32 result of -eb-&gt;len, when
widened to the s64 parameter, becomes a large positive value instead of
the intended negative value.  For eb-&gt;len == 16384 the counter adds
+4294950912 instead of subtracting 16384.

The counter therefore grows on every metadata writeback instead of
shrinking by the extent buffer size, permanently exceeding
BTRFS_DIRTY_METADATA_THRESH and causing __btrfs_btree_balance_dirty()
to trigger balance_dirty_pages_ratelimited() unconditionally, adding
unnecessary writeback pressure.

Cast eb-&gt;len to s64 before negation at both call sites so the
subtraction is performed in signed 64-bit arithmetic.

Reviewed-by: Filipe Manana &lt;fdmanana@suse.com&gt;
Fixes: 84cda1a6087d ("btrfs: cache folio size and shift in extent_buffer")
Signed-off-by: Dave Chen &lt;davechen@synology.com&gt;
Signed-off-by: Filipe Manana &lt;fdmanana@suse.com&gt;
Signed-off-by: David Sterba &lt;dsterba@suse.com&gt;
</content>
</entry>
<entry>
<title>btrfs: switch local indicator variables to bools</title>
<updated>2026-06-09T16:22:46Z</updated>
<author>
<name>David Sterba</name>
<email>dsterba@suse.com</email>
</author>
<published>2026-05-26T11:33:21Z</published>
<link rel='alternate' type='text/html' href='https://git.ilvokhin.com/linux.git/commit/?id=79bdd8846317f3dea26c53d75700045f62265557'/>
<id>urn:sha1:79bdd8846317f3dea26c53d75700045f62265557</id>
<content type='text'>
For all local indicator variables do simple switch to bool, done on all
files.

Signed-off-by: David Sterba &lt;dsterba@suse.com&gt;
</content>
</entry>
<entry>
<title>btrfs: allocate eb-attached btree pages as movable</title>
<updated>2026-06-09T16:22:45Z</updated>
<author>
<name>Rik van Riel</name>
<email>riel@surriel.com</email>
</author>
<published>2026-05-26T22:37:39Z</published>
<link rel='alternate' type='text/html' href='https://git.ilvokhin.com/linux.git/commit/?id=23fd95663b070cf781f37b8058a8c055f168110b'/>
<id>urn:sha1:23fd95663b070cf781f37b8058a8c055f168110b</id>
<content type='text'>
Extent buffer pages allocated by alloc_extent_buffer() are attached to
btree_inode-&gt;i_mapping (the buffer_tree path), reach the LRU, and are
served by the btree_migrate_folio aops in fs/btrfs/disk-io.c. They are
migratable in practice once their owning extent buffer hits refs == 1,
which happens naturally. The buddy allocator classifies them by GFP,
however, and bare GFP_NOFS lands them in MIGRATE_UNMOVABLE pageblocks.

The result: every btree_inode page we read in pins an unmovable pageblock
from the page-superblock allocator's perspective, even though the page
itself can be moved.

Have each caller of btrfs_alloc_page_array, btrfs_alloc_folio_array,
and alloc_eb_folio_array pass in the full GFP mask directly, instead
of having the functions calculate it from boolean flags.

The alloc_extent_buffer call site passes GFP_NOFS | __GFP_NOFAIL |
__GFP_MOVABLE. All other call sites pass plain GFP_NOFS.

Three categories of caller stay on bare GFP_NOFS, deliberately:

  - alloc_dummy_extent_buffer / btrfs_clone_extent_buffer: the
    resulting eb is EXTENT_BUFFER_UNMAPPED, folio-&gt;mapping stays NULL,
    the folios never enter LRU, never get migrate_folio aops. Tagging
    them __GFP_MOVABLE would violate the page allocator's migrability
    contract and they would defeat compaction in MOVABLE pageblocks
    where isolate_migratepages_block skips non-LRU non-movable_ops
    pages outright.

  - btrfs_alloc_page_array callers in fs/btrfs/raid56.c (stripe
    pages), fs/btrfs/inode.c (encoded reads), fs/btrfs/ioctl.c (io_uring
    encoded reads), fs/btrfs/relocation.c (relocation buffers): same
    contract violation. raid56 stripe_pages additionally persist in
    the stripe cache (RBIO_CACHE_SIZE=1024) well beyond a single I/O,
    so they are not transient enough to hand-wave the contract.

  - btrfs_alloc_folio_array caller in fs/btrfs/scrub.c (stripe
    folios): same -- stripe-&gt;folios[] are private buffers freed via
    folio_put in release_scrub_stripe.

This change targets the dominant fragmentation source observed on the
page-superblock series: ~28 GB of btree_inode pages parked across
many tainted superpageblocks on a 250 GB test system with btrfs root,
preventing 1 GiB hugepage allocation from those regions. With the
movable hint, those pages now land in MOVABLE pageblocks where the
existing background defragger drains them through the standard
PB_has_movable gate, no LRU-sample fallback needed.

Assisted-by: Claude:claude-opus-4-6
Signed-off-by: Rik van Riel &lt;riel@surriel.com&gt;
Signed-off-by: David Sterba &lt;dsterba@suse.com&gt;
</content>
</entry>
<entry>
<title>btrfs: replace __free_page with folio_put() in attach_eb_folio_to_filemap()</title>
<updated>2026-06-09T16:22:44Z</updated>
<author>
<name>Matthew Wilcox (Oracle)</name>
<email>willy@infradead.org</email>
</author>
<published>2026-05-22T18:14:08Z</published>
<link rel='alternate' type='text/html' href='https://git.ilvokhin.com/linux.git/commit/?id=03f1fc95a7f7857496ec3d7b283a8487a3d3c341'/>
<id>urn:sha1:03f1fc95a7f7857496ec3d7b283a8487a3d3c341</id>
<content type='text'>
Calling __free_page() on folio_page() happens to work today, but
won't always.  Besides, it's far simpler to call folio_put().

Reviewed-by: Boris Burkov &lt;boris@bur.io&gt;
Reviewed-by: Qu Wenruo &lt;wqu@suse.com&gt;
Tested-by: Boris Burkov &lt;boris@bur.io&gt;
Signed-off-by: Matthew Wilcox (Oracle) &lt;willy@infradead.org&gt;
Signed-off-by: David Sterba &lt;dsterba@suse.com&gt;
</content>
</entry>
<entry>
<title>btrfs: introduce support for huge folios</title>
<updated>2026-06-09T10:49:26Z</updated>
<author>
<name>Qu Wenruo</name>
<email>wqu@suse.com</email>
</author>
<published>2026-05-13T04:36:21Z</published>
<link rel='alternate' type='text/html' href='https://git.ilvokhin.com/linux.git/commit/?id=0eded739d8127d5a8c5cf370d3156b142383c6ed'/>
<id>urn:sha1:0eded739d8127d5a8c5cf370d3156b142383c6ed</id>
<content type='text'>
With all the previous preparations, it's finally time to enable the
huge folio support.

- The max folio size
  Here we define BTRFS_MAX_FOLIO_SIZE, which is fixed at 2MiB.

  This will ensure we have a large enough but not too large folio for
  btrfs.  This limit applies to all systems regardless of page size.

  Then we also define BTRFS_MAX_BLOCKS_PER_FOLIO, which depends on
  CONFIG_BTRFS_EXPERIMENTAL.

  If it's an experimental build, BTRFS_MAX_BLOCKS_PER_FOLIO is 512,
  otherwise it's BITS_PER_LONG.

  The filemap max order will be calculated using both
  BTRFS_MAX_FOLIO_SIZE and BTRFS_MAX_BLOCKS_PER_FOLIO.

  E.g. for 64K page size with 64K fs block size, the limit will be
  BTRFS_MAX_FOLIO_SIZE (2M), which limits the filemap max order to 5.
  This will be lower than the old order (6), but folios larger than 2M
  are rarely any better for IO performance. Meanwhile excessively large
  folios can cause other problems like stalling the IO pipeline for too
  long.

  For 4K page size and 4K fs block size, the limit will be increased to
  2M from the old 256K.
  This new size is constrained by both BTRFS_MAX_FOLIO_SIZE (2M) and
  BTRFS_MAX_BLOCKS_PER_FOLIO (512 * 4K), allowing x86_64 to achieve huge
  folio support, and the filemap max order will be 9.

- btrfs_bio_ctrl::submit_bitmap
  This will be enlarged to contain BTRFS_MAX_BLOCKS_PER_FOLIO bits, and
  this will be on-stack memory.
  This will increase on-stack memory usage by 56 bytes compared to the
  baseline (before the first patch in the series).

- Local @delalloc_bitmap inside writepage_delalloc()
  Unfortunately we cannot afford to handle an allocation error here, thus
  again we use on-stack memory.
  Thus this will increase on-stack memory usage by 56 bytes again.

So unfortunately this means during the delalloc window, the writeback path
will have +112 bytes on-stack memory usage, and for other cases the
writeback path will have +56 bytes on-stack memory usage.

The +56 bytes (btrfs_bio_ctrl::submit_bitmap) can be removed
after we have reworked the compression submission, so the current
on-stack submit_bitmap is mostly a workaround until then.

Signed-off-by: Qu Wenruo &lt;wqu@suse.com&gt;
Signed-off-by: David Sterba &lt;dsterba@suse.com&gt;
</content>
</entry>
<entry>
<title>btrfs: migrate btrfs_bio_ctrl::submit_bitmap to support larger bitmaps</title>
<updated>2026-06-09T10:49:26Z</updated>
<author>
<name>Qu Wenruo</name>
<email>wqu@suse.com</email>
</author>
<published>2026-05-13T04:36:20Z</published>
<link rel='alternate' type='text/html' href='https://git.ilvokhin.com/linux.git/commit/?id=ea1ab09df95c44ba1738237eb3360bdd59c566eb'/>
<id>urn:sha1:ea1ab09df95c44ba1738237eb3360bdd59c566eb</id>
<content type='text'>
[CURRENT LIMIT]
Btrfs currently only supports sub-bitmaps (e.g. dirty bitmap) no larger
than BITS_PER_LONG.

One call site that utilizes this limit is btrfs_bio_ctrl::submit_bitmap,
which makes it very simple and straightforward to just grab an unsigned
long value and assign it to submit_bitmap.

Unfortunately that limit prevents us from supporting huge folios.
For 4K page size and block size, a huge folio (order 9) means 512 blocks
inside a 2M folio.

[ENHANCEMENT]
Instead of using a fixed unsigned long value, change
btrfs_bio_ctrl::submit_bitmap to an unsigned long pointer.

And for cases where an unsigned long can hold the whole bitmap,
introduce @submit_bitmap_value, and just point that pointer to that
unsigned long.

Then update all direct users of bio_ctrl-&gt;submit_bitmap to use the
pointer version.

There are several call sites that get extra changes:

- @range_bitmap inside extent_writepage_io()
  Which is only utilized to truncate the bitmap.
  Since we do not want to allocate new memory just for such temporary
  usage, change the original bitmap_set() and bitmap_and() into
  bitmap_clear() for the ranges outside of the target range.

- Getting dirty subpage bitmap inside writepage_delalloc()
  Since we're passing an unsigned long pointer now, we need to go with
  different handling (bs == ps, blocks_per_folio &lt;= BITS_PER_LONG,
  blocks_per_folio &gt; BITS_PER_LONG).

Signed-off-by: Qu Wenruo &lt;wqu@suse.com&gt;
Signed-off-by: David Sterba &lt;dsterba@suse.com&gt;
</content>
</entry>
<entry>
<title>btrfs: prepare subpage operations to support more than BITS_PER_LONG sub-bitmaps</title>
<updated>2026-06-09T10:49:26Z</updated>
<author>
<name>Qu Wenruo</name>
<email>wqu@suse.com</email>
</author>
<published>2026-05-13T04:36:19Z</published>
<link rel='alternate' type='text/html' href='https://git.ilvokhin.com/linux.git/commit/?id=eb6915bb86438a7370c84ef666a06b45c4f48627'/>
<id>urn:sha1:eb6915bb86438a7370c84ef666a06b45c4f48627</id>
<content type='text'>
[CURRENT LIMIT]
Btrfs currently only supports sub-bitmaps (e.g. dirty bitmap) no larger
than BITS_PER_LONG.

That limit allows us to easily grab an unsigned long without the need to
properly allocate memory for a larger bitmap.

Unfortunately that limit prevents us from supporting huge folios.
For 4K page size and block size, a huge folio (order 9) means 512 blocks
inside a 2M folio.

[ENHANCEMENT]
To allow direct bitmap operations without allocating new memory,
introduce two different ways to access the subpage bitmaps:

- Return an unsigned long value
  This only happens if blocks_per_folio &lt;= BITS_PER_LONG.

  We read out the sub-bitmap into an unsigned long, and return the
  value.
  This is the old existing method.

  This involves get_bitmap_value_##name() helper functions.
  And this time the helper functions are defined as inline functions
  instead of macros to provide better type checks.

- Return a pointer where the sub-bitmap starts
  This only happens if blocks_per_folio &gt;= BITS_PER_LONG.

  This is the new method for sub-bitmaps larger than BITS_PER_LONG.
  Since the sizes of sub-bitmaps are all aligned to BITS_PER_LONG, we
  can directly access the start word of the sub-bitmap.

  This involves get_bitmap_pointer_##name() helper functions.

Then change the existing sub-bitmaps users to use the new helpers:

- Bitmap dumping
  Switch between get_bitmap_value_##name() and
  get_bitmap_pointer_##name() depending on the sub-bitmap size.

- btrfs_get_subpage_dirty_bitmap()
  Rename it to btrfs_get_subpage_dirty_bitmap_value() to follow the new
  value/pointer naming.
  Since we do not support huge folios yet, there is no pointer version
  for the dirty bitmap.

  Furthermore, add the support for block size == page size cases for
  btrfs_get_subpage_dirty_bitmap_value(), so that the caller no longer
  needs to check if the folio needs subpage handling.

Signed-off-by: Qu Wenruo &lt;wqu@suse.com&gt;
Signed-off-by: David Sterba &lt;dsterba@suse.com&gt;
</content>
</entry>
<entry>
<title>btrfs: remove folio ordered flag and subpage bitmap</title>
<updated>2026-06-08T13:53:32Z</updated>
<author>
<name>Qu Wenruo</name>
<email>wqu@suse.com</email>
</author>
<published>2026-05-12T22:36:38Z</published>
<link rel='alternate' type='text/html' href='https://git.ilvokhin.com/linux.git/commit/?id=4927b141877c35b1af4e32c7876cd2e0a0f16196'/>
<id>urn:sha1:4927b141877c35b1af4e32c7876cd2e0a0f16196</id>
<content type='text'>
Btrfs has an internal flag/subpage bitmap called ordered, which is to
indicate that a block has corresponding ordered extent covering it.

However this requires extra synchronization between the inode ordered
tree, and the folio flag/subpage bitmap, not to mention we need to
maintain the extra folio flag with subpage bitmap.

As a step to align btrfs_folio_state more closely to iomap_folio_state,
remove the btrfs specific ordered flag/bitmap.

This will also save us 64 bytes for the bitmap of a huge folio.

Since we're here, also update the ASCII graph of the bitmap, as there
are only 3 sub-bitmaps now, show all sub-bitmaps directly.

Signed-off-by: Qu Wenruo &lt;wqu@suse.com&gt;
Signed-off-by: David Sterba &lt;dsterba@suse.com&gt;
</content>
</entry>
<entry>
<title>btrfs: remove folio_test_ordered() usage</title>
<updated>2026-06-08T13:53:32Z</updated>
<author>
<name>Qu Wenruo</name>
<email>wqu@suse.com</email>
</author>
<published>2026-05-07T05:29:20Z</published>
<link rel='alternate' type='text/html' href='https://git.ilvokhin.com/linux.git/commit/?id=aea704836e47decbdcac77872385c9a3fb51da42'/>
<id>urn:sha1:aea704836e47decbdcac77872385c9a3fb51da42</id>
<content type='text'>
This involves:

- The ASSERT() inside end_bbio_data_write()
  It's only an ASSERT() and it has never been triggered as far as I
  know.

- btrfs_migrate_folio()
  Since all folio_test_ordered() usage will be removed, there is no need to
  copy the folio ordered flag.

- The ASSERT() inside btrfs_invalidate_folio()
  This one has its usefulness as it indeed caught some bugs during
  development.
  But that's the last user and will not be worth the folio flag or the
  subpage bitmap.

This will allow btrfs to finally remove the ordered flags.

Signed-off-by: Qu Wenruo &lt;wqu@suse.com&gt;
Signed-off-by: David Sterba &lt;dsterba@suse.com&gt;
</content>
</entry>
<entry>
<title>btrfs: unify folio dirty flag clearing</title>
<updated>2026-06-08T13:53:32Z</updated>
<author>
<name>Qu Wenruo</name>
<email>wqu@suse.com</email>
</author>
<published>2026-05-07T05:29:18Z</published>
<link rel='alternate' type='text/html' href='https://git.ilvokhin.com/linux.git/commit/?id=095be159f3eb4670fab75f795ce9539a381ebd3f'/>
<id>urn:sha1:095be159f3eb4670fab75f795ce9539a381ebd3f</id>
<content type='text'>
Currently during folio writeback, we call folio_clear_dirty_for_io()
before extent_writepage(), which causes folio dirty flag to be cleared,
but without touching the subpage bitmaps.

This works fine for the bio submission path, as we always call
btrfs_folio_clear_dirty() to clear the subpage bitmap.

But this is far from consistent, thus this patch is going to unify the
behavior to always use btrfs_folio_clear_dirty() helper to clear both
folio flag and subpage bitmap.

This involves:

- Replace folio_clear_dirty_for_io() with folio_test_dirty()
  There is only one call site calling folio_clear_dirty_for_io() outside
  of subpage.c, that's inside extent_write_cache_pages() just before
  extent_writepage().

- Make btrfs_invalidate_folio() clear dirty range for the whole folio
  The function btrfs_invalidate_folio() is also called during
  extent_writepage().

  If we had a folio completely beyond isize, we call
  folio_invalidate() -&gt; btrfs_invalidate_folio() to free the folio.

  Since we no longer have folio_clear_dirty_for_io() to clear the folio
  dirty flag, we must manually clear the folio dirty flag for the
  to-be-invalidated folio, and also clear the PAGECACHE_TAG_DIRTY tag.

  The tag clearing is done using a new helper,
  btrfs_clear_folio_dirty_tag(), which is almost the same as the old
  btree_clear_folio_dirty_tag(), but with minor improvements including:

  * Remove the folio_test_dirty() check
    We have already done an ASSERT().

  * Add an ASSERT() to make sure folio is mapped

- Add extra ASSERT()s before clearing folio private
  During development I hit dirty folios without the private flag set,
  and that caused a lot of ASSERT()s.
  The reason is that btrfs_invalidate_folio() is relying on the dirty
  flag being cleared when it's called from extent_writepage().

  Add extra ASSERT()s inside clear_folio_extent_mapped() to catch
  wild dirty/writeback flags.

Signed-off-by: Qu Wenruo &lt;wqu@suse.com&gt;
Signed-off-by: David Sterba &lt;dsterba@suse.com&gt;
</content>
</entry>
</feed>
