Add f2fs's own per-folio structure to track per-block dirty state of a folio.
The reason for introducing this structure is that f2fs's private flag would conflict with iomap_folio_state's use of the folio->private field. Thanks to Mr. Matthew for providing the idea. See for details: [https://lore.kernel.org/linux-f2fs-devel/Z-oPTUrF7kkhzJg_ @casper.infradead.org/] The memory layout of this structure is the same as iomap_folio_state, except that we set read_bytes_pending to a magic number. This is because we need to be able to distinguish it from the original iomap_folio_state. We additionally allocate an unsigned long at the end of the state array to store f2fs-specific flags. This implementation is compatible with high-order folios, order-0 folios, and metadata folios. However, it does not support compressed data folios. Introduction to related functions: - f2fs_ifs_alloc: Allocates f2fs's own f2fs_iomap_folio_state. If it detects that folio->private already has a value, we distinguish whether it is f2fs's own flag value or an iomap_folio_state. If it is the latter, we will copy its content to our f2fs_iomap_folio_state and then free it. - folio_detach_f2fs_private: Serves as a unified interface to release f2fs's private resources, no matter what it is. - f2fs_ifs_clear_range_uptodate && f2fs_ifs_set_range_dirty: Helper functions copied and slightly modified from fs/iomap. - folio_get_f2fs_ifs: Specifically used to get f2fs_iomap_folio_state. It cannot be used to get f2fs's own fields used on compressed folios. For the former, we return a null pointer to indicate that the current folio does not hold an f2fs_iomap_folio_state. For the latter, we directly BUG_ON. Signed-off-by: Nanzhe Zhao <nzz...@126.com> --- fs/f2fs/Kconfig | 10 ++ fs/f2fs/Makefile | 1 + fs/f2fs/f2fs_ifs.c | 221 +++++++++++++++++++++++++++++++++++++++++++++ fs/f2fs/f2fs_ifs.h | 79 ++++++++++++++++ 4 files changed, 311 insertions(+) create mode 100644 fs/f2fs/f2fs_ifs.c create mode 100644 fs/f2fs/f2fs_ifs.h diff --git a/fs/f2fs/Kconfig b/fs/f2fs/Kconfig index 5916a02fb46d..480b8536fa39 100644 --- a/fs/f2fs/Kconfig +++ b/fs/f2fs/Kconfig @@ -150,3 +150,13 @@ config F2FS_UNFAIR_RWSEM help Use unfair rw_semaphore, if system configured IO priority by block cgroup. + +config F2FS_IOMAP_FOLIO_STATE + bool "F2FS folio per-block I/O state tracking" + depends on F2FS_FS && FS_IOMAP + help + Enable a custom F2FS structure for tracking the I/O state + (up-to-date, dirty) on a per-block basis within a memory folio. + This structure stores F2FS private flag in its state flexible + array while keeping compatibility with generic iomap_folio_state. + Must be enabled if using iomap large folios support in F2FS. \ No newline at end of file diff --git a/fs/f2fs/Makefile b/fs/f2fs/Makefile index 8a7322d229e4..3b9270d774e8 100644 --- a/fs/f2fs/Makefile +++ b/fs/f2fs/Makefile @@ -10,3 +10,4 @@ f2fs-$(CONFIG_F2FS_FS_POSIX_ACL) += acl.o f2fs-$(CONFIG_FS_VERITY) += verity.o f2fs-$(CONFIG_F2FS_FS_COMPRESSION) += compress.o f2fs-$(CONFIG_F2FS_IOSTAT) += iostat.o +f2fs-$(CONFIG_F2FS_IOMAP_FOLIO_STATE) += f2fs_ifs.o diff --git a/fs/f2fs/f2fs_ifs.c b/fs/f2fs/f2fs_ifs.c new file mode 100644 index 000000000000..6b7503474580 --- /dev/null +++ b/fs/f2fs/f2fs_ifs.c @@ -0,0 +1,221 @@ +// SPDX-License-Identifier: GPL-2.0 +#include <linux/fs.h> +#include <linux/f2fs_fs.h> + +#include "f2fs.h" +#include "f2fs_ifs.h" + +/* + * Have to set parameter ifs's type to void* + * and have to interpret ifs as f2fs_ifs to access its fields because + * we cannot see iomap_folio_state definition + */ +static void ifs_to_f2fs_ifs(void *ifs, struct f2fs_iomap_folio_state *fifs, + struct folio *folio) +{ + struct f2fs_iomap_folio_state *src_ifs = + (struct f2fs_iomap_folio_state *)ifs; + size_t iomap_longs = f2fs_ifs_iomap_longs(folio); + + fifs->read_bytes_pending = READ_ONCE(src_ifs->read_bytes_pending); + atomic_set(&fifs->write_bytes_pending, + atomic_read(&src_ifs->write_bytes_pending)); + memcpy(fifs->state, src_ifs->state, + iomap_longs * sizeof(unsigned long)); +} + +static inline bool is_f2fs_ifs(struct folio *folio) +{ + struct f2fs_iomap_folio_state *fifs; + + if (!folio_test_private(folio)) + return false; + + // first directly test no pointer flag is set or not + if (test_bit(PAGE_PRIVATE_NOT_POINTER, + (unsigned long *)&folio->private)) + return false; + + fifs = (struct f2fs_iomap_folio_state *)folio->private; + if (!fifs) + return false; + + if (READ_ONCE(fifs->read_bytes_pending) == F2FS_IFS_MAGIC) + return true; + + return false; +} + +struct f2fs_iomap_folio_state *f2fs_ifs_alloc(struct folio *folio, gfp_t gfp, + bool force_alloc) +{ + struct inode *inode = folio->mapping->host; + size_t alloc_size = 0; + + if (!folio_test_large(folio)) { + if (!force_alloc) { + WARN_ON_ONCE(1); + return NULL; + } + /* + * GC can store private flag in 0 order folio's folio->private + * causes iomap buffered write mistakenly interpret as a pointer + * we add a bool force_alloc to deal with this case + */ + struct f2fs_iomap_folio_state *fifs; + + alloc_size = sizeof(*fifs) + 2 * sizeof(unsigned long); + fifs = kmalloc(alloc_size, gfp); + if (!fifs) + return NULL; + spin_lock_init(&fifs->state_lock); + WRITE_ONCE(fifs->read_bytes_pending, F2FS_IFS_MAGIC); + atomic_set(&fifs->write_bytes_pending, 0); + unsigned int nr_blocks = + i_blocks_per_folio(inode, folio); + if (folio_test_uptodate(folio)) + bitmap_set(fifs->state, 0, nr_blocks); + if (folio_test_dirty(folio)) + bitmap_set(fifs->state, nr_blocks, nr_blocks); + *f2fs_ifs_private_flags_ptr(fifs, folio) = 0; + folio_attach_private(folio, fifs); + return fifs; + } + + struct f2fs_iomap_folio_state *fifs; + void *old_private; + size_t iomap_longs; + size_t total_longs; + + WARN_ON_ONCE(!inode); // Should have an inode + + old_private = folio_get_private(folio); + + if (old_private) { + // Check if it's already our type using the magic number directly + if (READ_ONCE(((struct f2fs_iomap_folio_state *)old_private) + ->read_bytes_pending) == F2FS_IFS_MAGIC) { + return (struct f2fs_iomap_folio_state *) + old_private; // Already ours + } + // Non-NULL, not ours -> Allocate, Copy, Replace path + total_longs = f2fs_ifs_total_longs(folio); + alloc_size = sizeof(*fifs) + + total_longs * sizeof(unsigned long); + + fifs = kmalloc(alloc_size, gfp); + if (!fifs) + return NULL; + + spin_lock_init(&fifs->state_lock); + *f2fs_ifs_private_flags_ptr(fifs, folio) = 0; + // Copy data from the presumed iomap_folio_state (old_private) + ifs_to_f2fs_ifs(old_private, fifs, folio); + WRITE_ONCE(fifs->read_bytes_pending, F2FS_IFS_MAGIC); + folio_change_private(folio, fifs); + kfree(old_private); + return fifs; + } + + iomap_longs = f2fs_ifs_iomap_longs(folio); + total_longs = iomap_longs + 1; + alloc_size = + sizeof(*fifs) + total_longs * sizeof(unsigned long); + + fifs = kzalloc(alloc_size, gfp); + if (!fifs) + return NULL; + + spin_lock_init(&fifs->state_lock); + + unsigned int nr_blocks = i_blocks_per_folio(inode, folio); + + if (folio_test_uptodate(folio)) + bitmap_set(fifs->state, 0, nr_blocks); + if (folio_test_dirty(folio)) + bitmap_set(fifs->state, nr_blocks, nr_blocks); + WRITE_ONCE(fifs->read_bytes_pending, F2FS_IFS_MAGIC); + atomic_set(&fifs->write_bytes_pending, 0); + folio_attach_private(folio, fifs); + return fifs; +} + +void folio_detach_f2fs_private(struct folio *folio) +{ + struct f2fs_iomap_folio_state *fifs; + + if (!folio_test_private(folio)) + return; + + // Check if it's using direct flags + if (test_bit(PAGE_PRIVATE_NOT_POINTER, + (unsigned long *)&folio->private)) { + folio_detach_private(folio); + return; + } + + fifs = folio_detach_private(folio); + if (!fifs) + return; + + if (is_f2fs_ifs(folio)) { + WARN_ON_ONCE(READ_ONCE(fifs->read_bytes_pending) != + F2FS_IFS_MAGIC); + WARN_ON_ONCE(atomic_read(&fifs->write_bytes_pending)); + } else { + WARN_ON_ONCE(READ_ONCE(fifs->read_bytes_pending) != 0); + WARN_ON_ONCE(atomic_read(&fifs->write_bytes_pending)); + } + + kfree(fifs); +} + +struct f2fs_iomap_folio_state *folio_get_f2fs_ifs(struct folio *folio) +{ + if (!folio_test_private(folio)) + return NULL; + + if (test_bit(PAGE_PRIVATE_NOT_POINTER, + (unsigned long *)&folio->private)) + return NULL; + /* + * Note we assume folio->private can be either ifs or f2fs_ifs here. + * Compresssed folios should not call this function + */ + f2fs_bug_on(F2FS_F_SB(folio), + *((u32 *)folio->private) == F2FS_COMPRESSED_PAGE_MAGIC); + return folio->private; +} + +void f2fs_ifs_clear_range_uptodate(struct folio *folio, + struct f2fs_iomap_folio_state *fifs, + size_t off, size_t len) +{ + struct inode *inode = folio->mapping->host; + unsigned int first_blk = (off >> inode->i_blkbits); + unsigned int last_blk = (off + len - 1) >> inode->i_blkbits; + unsigned int nr_blks = last_blk - first_blk + 1; + unsigned long flags; + + spin_lock_irqsave(&fifs->state_lock, flags); + bitmap_clear(fifs->state, first_blk, nr_blks); + spin_unlock_irqrestore(&fifs->state_lock, flags); +} + +void f2fs_iomap_set_range_dirty(struct folio *folio, size_t off, size_t len) +{ + struct f2fs_iomap_folio_state *fifs = folio_get_f2fs_ifs(folio); + + if (fifs) { + struct inode *inode = folio->mapping->host; + unsigned int blks_per_folio = i_blocks_per_folio(inode, folio); + unsigned int first_blk = (off >> inode->i_blkbits); + unsigned int last_blk = (off + len - 1) >> inode->i_blkbits; + unsigned int nr_blks = last_blk - first_blk + 1; + unsigned long flags; + + spin_lock_irqsave(&fifs->state_lock, flags); + bitmap_set(fifs->state, first_blk + blks_per_folio, nr_blks); + spin_unlock_irqrestore(&fifs->state_lock, flags); + } +} diff --git a/fs/f2fs/f2fs_ifs.h b/fs/f2fs/f2fs_ifs.h new file mode 100644 index 000000000000..3b16deda8a1e --- /dev/null +++ b/fs/f2fs/f2fs_ifs.h @@ -0,0 +1,79 @@ +// SPDX-License-Identifier: GPL-2.0 +#ifndef F2FS_IFS_H +#define F2FS_IFS_H + +#include <linux/fs.h> +#include <linux/bug.h> +#include <linux/f2fs_fs.h> +#include <linux/mm.h> +#include <linux/iomap.h> +#include <linux/slab.h> +#include <linux/spinlock.h> +#include <linux/atomic.h> + +#include "f2fs.h" + +#define F2FS_IFS_MAGIC 0xf2f5 +#define F2FS_IFS_PRIVATE_LONGS 1 + +/* + * F2FS structure for folio private data, mimicking iomap_folio_state layout. + * F2FS private flags/data are stored in extra space allocated at the end + */ +struct f2fs_iomap_folio_state { + spinlock_t state_lock; + unsigned int read_bytes_pending; + atomic_t write_bytes_pending; + /* + * Flexible array member. + * Holds [0...iomap_longs-1] for iomap uptodate/dirty bits. + * Holds [iomap_longs] for F2FS private flags/data (unsigned long). + */ + unsigned long state[]; +}; + +static inline bool +f2fs_ifs_block_is_uptodate(struct f2fs_iomap_folio_state *ifs, + unsigned int block) +{ + return test_bit(block, ifs->state); +} + +static inline size_t f2fs_ifs_iomap_longs(const struct folio *folio) +{ + struct inode *inode = folio->mapping->host; + + WARN_ON_ONCE(!inode); + unsigned int nr_blocks = + i_blocks_per_folio(inode, (struct folio *)folio); + return BITS_TO_LONGS(2 * nr_blocks); +} + +static inline size_t f2fs_ifs_total_longs(struct folio *folio) +{ + return f2fs_ifs_iomap_longs(folio) + F2FS_IFS_PRIVATE_LONGS; +} + +static inline unsigned long * +f2fs_ifs_private_flags_ptr(struct f2fs_iomap_folio_state *fifs, + const struct folio *folio) +{ + return &fifs->state[f2fs_ifs_iomap_longs(folio)]; +} + +struct f2fs_iomap_folio_state *f2fs_ifs_alloc(struct folio *folio, gfp_t gfp, + bool force_alloc); +void folio_detach_f2fs_private(struct folio *folio); +struct f2fs_iomap_folio_state *folio_get_f2fs_ifs(struct folio *folio); + +/* + * 0-order and fully dirty folio has no fifs + * they store private flag directly in their folio->private field + * as original f2fs page private behaviour + */ +void f2fs_ifs_clear_range_uptodate(struct folio *folio, + struct f2fs_iomap_folio_state *fifs, + size_t off, size_t len); +void f2fs_iomap_set_range_dirty(struct folio *folio, size_t off, size_t len); + +#endif /* F2FS_IFS_H */ -- 2.34.1 _______________________________________________ Linux-f2fs-devel mailing list Linux-f2fs-devel@lists.sourceforge.net https://lists.sourceforge.net/lists/listinfo/linux-f2fs-devel