diff options
| author | Linus Torvalds <torvalds@linux-foundation.org> | 2026-08-28 10:48:48 -0700 |
|---|---|---|
| committer | Linus Torvalds <torvalds@linux-foundation.org> | 2026-08-28 10:48:48 -0700 |
| commit | 115bd364ab20b2dbef22824ec80d15901847dbe2 (patch) | |
| tree | dc4e57a6e9047e8efc745b9b737363015ae49099 | |
| parent | c20313e98b04ce543936431b6122dd639d3a8346 (diff) | |
| parent | c966d29e01bbf829f8bb4a39a49811c56cdb49c3 (diff) | |
Merge tag 'f2fs-for-7.3-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/jaegeuk/f2fs
Pull f2fs updates from Jaegeuk Kim:
"In this round, key enhancements focus on reducing inode management
memory overhead, introducing resizable tail sections with unified
pinned allocation, and boosting I/O throughput via parallel
multi-device flushes and asynchronous f2fs_write_end_io() execution.
We also add dynamic device alias reservations to allow on-the-fly
space donation from user partitions.
Alongside these features, critical bug fixes resolve folio race
conditions, lingering dirty flags, dentry and block counter leaks, and
potential deadloops in f2fs_fsync_node_pages(). Additional stability
patches address error-path handling across symlink, sync, and
rename/unlink operations, prevent pinned file fragmentation, and
correct segment migration and free section accounting in
free_segment_range.
Enhancements:
- reduce memory footprint of ino management
- support dynamic reserve/release for device aliasing
- issue multi-device flushes in parallel
- add a way to run f2fs_write_end_io() asynchronously
- support resizable tail section and unify pinned allocation
Bug fixes:
- fix to pass folio->index to f2fs_sanity_check_node_footer()
- fix folio_nr_pages() race after put in large folio invalidate
- fix to clear dirty flag on folio in error path
- accurately adjust free_sections during free_segment_range
- fix to avoid potential deadloop in f2fs_fsync_node_pages()
- fix the error path in symlink, device alias in rename/unlink,
f2fs_sync_fs
- fix to migrate all curseg types during free_segment_range
- fix to avoid pinfile fragment on fragment:{block, segment} mode
- fix valid block count leak on data block allocation failure
- fix dentry folio leak in find_in_level
- reject overlapping move range after len expansion
- fix some bugs related to file pinning, GC functions, i_size
And, the series includes a number of minor bug fixes"
* tag 'f2fs-for-7.3-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/jaegeuk/f2fs: (51 commits)
f2fs: support resizable tail section and unify pinned allocation
f2fs: don't leave the hashed inode while it's unlinked
f2fs: accurately adjust free_sections during free_segment_range
f2fs: fix to avoid potential deadloop in f2fs_fsync_node_pages()
f2fs: use adjusted write range after f2fs_write_checks()
f2fs: fix to propagate error from f2fs_sync_fs()
f2fs: return symlink writeback errors
f2fs: fix error handling on device alias check in rename and unlink
f2fs: fix to reset all pinned status during fggc
f2fs: use f2fs_{down, up}_(read, write}_trace() for nat_tree_lock
f2fs: reduce memory footprint of ino management
f2fs: fix i_size when pinned fallocate partially fails
f2fs: fix to migrate all curseg types during free_segment_range
f2fs: avoid setting SBI_NEED_FSCK on transient resize failure
f2fs: fix to avoid pinfile fragment on fragment:{block, segment} mode
f2fs: cleanup w/ f2fs_need_rand_{blk, seg, seg_blk}
f2fs: fix to shrink gc_lock coverage in f2fs_gc_range()
f2fs: fix to reclaim space in f2fs_allocate_pinning_section()
f2fs: unify add/remove ino entry API for all ino types
f2fs: fix to zero post-EOF data when extending file size
...
| -rw-r--r-- | Documentation/ABI/testing/sysfs-fs-f2fs | 18 | ||||
| -rw-r--r-- | Documentation/filesystems/f2fs.rst | 43 | ||||
| -rw-r--r-- | fs/f2fs/checkpoint.c | 123 | ||||
| -rw-r--r-- | fs/f2fs/data.c | 48 | ||||
| -rw-r--r-- | fs/f2fs/debug.c | 4 | ||||
| -rw-r--r-- | fs/f2fs/dir.c | 7 | ||||
| -rw-r--r-- | fs/f2fs/extent_cache.c | 9 | ||||
| -rw-r--r-- | fs/f2fs/f2fs.h | 90 | ||||
| -rw-r--r-- | fs/f2fs/file.c | 460 | ||||
| -rw-r--r-- | fs/f2fs/gc.c | 129 | ||||
| -rw-r--r-- | fs/f2fs/gc.h | 27 | ||||
| -rw-r--r-- | fs/f2fs/inline.c | 2 | ||||
| -rw-r--r-- | fs/f2fs/inode.c | 234 | ||||
| -rw-r--r-- | fs/f2fs/iostat.c | 6 | ||||
| -rw-r--r-- | fs/f2fs/namei.c | 168 | ||||
| -rw-r--r-- | fs/f2fs/node.c | 81 | ||||
| -rw-r--r-- | fs/f2fs/recovery.c | 55 | ||||
| -rw-r--r-- | fs/f2fs/segment.c | 279 | ||||
| -rw-r--r-- | fs/f2fs/segment.h | 37 | ||||
| -rw-r--r-- | fs/f2fs/shrinker.c | 3 | ||||
| -rw-r--r-- | fs/f2fs/super.c | 99 | ||||
| -rw-r--r-- | fs/f2fs/sysfs.c | 34 | ||||
| -rw-r--r-- | fs/f2fs/xattr.c | 2 | ||||
| -rw-r--r-- | include/trace/events/f2fs.h | 23 | ||||
| -rw-r--r-- | include/uapi/linux/f2fs.h | 7 |
25 files changed, 1532 insertions, 456 deletions
diff --git a/Documentation/ABI/testing/sysfs-fs-f2fs b/Documentation/ABI/testing/sysfs-fs-f2fs index 1b58c029abd0..0cebc89799dd 100644 --- a/Documentation/ABI/testing/sysfs-fs-f2fs +++ b/Documentation/ABI/testing/sysfs-fs-f2fs @@ -986,6 +986,7 @@ Description: This sysfs entry can be used to enable/disable to adjust priority f 0x00000008 gc_lock 0x00000010 cp_global 0x00000020 io_rwsem + 0x00000040 nat_tree_lock ========== ================== What: /sys/fs/f2fs/<disk>/lock_duration_priority @@ -1002,3 +1003,20 @@ Description: It can be used to tune priority of f2fs critical task, e.g. f2fs_ck threads, limitation as below: - it requires user has CAP_SYS_NICE capability. - the range is [100, 139], by default the value is 120. + +What: /sys/fs/f2fs/<disk>/max_atc_write_bio_size +Date: June 2026 +Contact: Bart Van Assche <bvanassche@acm.org> +Description: Every time a write operation completes f2fs_write_end_io() is + called. This function may be called from an atomic context, + e.g. from inside an interrupt handler. This attribute controls + the maximum size of a write bio that is completed in atomic + (atc) context. The default value for this attribute is UINT_MAX + which means that this functionality is disabled by default. + +What: /sys/fs/f2fs/<disk>/pinned_area_max_secno +Date: August 2026 +Contact: "Daeho Jeong" <daehojeong@google.com> +Description: This is a read-only entry to show the upper bound section number + for pinned files. Pinned files will only be allocated within + sections 0 to pinned_area_max_secno - 1. diff --git a/Documentation/filesystems/f2fs.rst b/Documentation/filesystems/f2fs.rst index b45d7a687625..771216f45207 100644 --- a/Documentation/filesystems/f2fs.rst +++ b/Documentation/filesystems/f2fs.rst @@ -415,7 +415,13 @@ lookup_mode=%s Control the directory lookup behavior for casefolded auto F2FS determines the mode based on the on-disk `SB_ENC_NO_COMPAT_FALLBACK_FL` flag. - ================== ======================================== +resizable_tail_secno=%u Control the number of sections at the tail of the + filesystem reserved for online resizing. Pinned files + will only be allocated within sections 0 to + (MAIN_SECS - resizable_tail_secno) - 1. If set to 0 + (default), there is no tail restriction unless running + on a zoned block device where conventional zones are + used. ======================== ============================================================ Debugfs Entries @@ -1043,6 +1049,41 @@ So, the key idea is, user can do any file operations on /dev/vdc, and reclaim the space after the use, while the space is counted as /data. That doesn't require modifying partition size and filesystem format. +Dynamic Device Aliasing Management +---------------------------------- + +In addition to static device aliasing by deleting the aliasing file, F2FS +supports dynamic management of device aliasing. This mechanism allows the system +to dynamically transition partition ownership between F2FS userdata and external +entities (e.g., zRAM, raw partition) based on system requirements without +deleting the master aliasing file or requiring unmount/remount. + +The master aliasing file is created during the initial format of the file system +and remains as a persistent control entity (ioctl gateway) in the root directory. + +- Partition Reservation (In-service to Aliased) + When a specific partition needs to be dedicated to external services (e.g., zRAM), + a user can reserve the device alias range via ioctl. The kernel resets GC victim + information for the target range, marks segments as in-use to prevent new + allocations, and triggers forced GC to migrate existing valid data out of the + range. Finally, it reserves these blocks in the SIT to effectively exclude the + device from the usable capacity. + +- Partition Release (Aliased to In-service) + When external usage concludes, the space is reclaimed not by deleting the file, + but through the release ioctl. The kernel truncates blocks associated with + the file, releasing them back to general filesystem allocation. + +.. code-block:: + + # f2fs_io dev_alias release /mnt/f2fs/vdc.file + # df -h + /dev/vdb 64G 753M 64G 2% /mnt/f2fs + + # f2fs_io dev_alias reserve /mnt/f2fs/vdc.file + # df -h + /dev/vdb 64G 33G 32G 52% /mnt/f2fs + Per-file Read-Only Large Folio Support -------------------------------------- diff --git a/fs/f2fs/checkpoint.c b/fs/f2fs/checkpoint.c index 064f5b537423..4b59f30ef45d 100644 --- a/fs/f2fs/checkpoint.c +++ b/fs/f2fs/checkpoint.c @@ -107,6 +107,7 @@ static bool need_uplift_priority(struct f2fs_rwsem *sem, bool is_write) case LOCK_NAME_GC_LOCK: case LOCK_NAME_CP_GLOBAL: case LOCK_NAME_IO_RWSEM: + case LOCK_NAME_NAT_TREE_LOCK: return true; default: f2fs_bug_on(sem->sbi, 1); @@ -766,28 +767,116 @@ static void __remove_ino_entry(struct f2fs_sb_info *sbi, nid_t ino, int type) spin_unlock(&im->ino_lock); } +static void __set_ino_bitmap(struct f2fs_sb_info *sbi, nid_t ino, int type) +{ + struct inode_management *im = &sbi->im[type]; + unsigned long index = INO_SLOT_INDEX(ino); + unsigned int ofs = INO_BIT_OFFSET(ino); + void **slot, *entry; + unsigned long bitmap = 0; + int ret; + + ret = radix_tree_preload(GFP_NOFS | __GFP_NOFAIL); + f2fs_bug_on(sbi, ret); + + spin_lock(&im->ino_lock); + slot = radix_tree_lookup_slot(&im->ino_root, index); + if (slot) { + entry = radix_tree_deref_slot_protected(slot, &im->ino_lock); + bitmap = xa_to_value(entry); + if (!(bitmap & (1UL << ofs))) { + bitmap |= (1UL << ofs); + entry = xa_mk_value(bitmap); + radix_tree_replace_slot(&im->ino_root, slot, entry); + } + } else { + bitmap |= (1UL << ofs); + entry = xa_mk_value(bitmap); + if (unlikely(radix_tree_insert(&im->ino_root, index, entry))) + f2fs_bug_on(sbi, 1); + } + spin_unlock(&im->ino_lock); + radix_tree_preload_end(); +} + +static void __clear_ino_bitmap(struct f2fs_sb_info *sbi, nid_t ino, int type) +{ + struct inode_management *im = &sbi->im[type]; + unsigned long index = INO_SLOT_INDEX(ino); + unsigned int ofs = INO_BIT_OFFSET(ino); + void **slot, *entry; + unsigned long bitmap; + + spin_lock(&im->ino_lock); + slot = radix_tree_lookup_slot(&im->ino_root, index); + if (slot) { + entry = radix_tree_deref_slot_protected(slot, &im->ino_lock); + bitmap = xa_to_value(entry); + if (bitmap & (1UL << ofs)) + bitmap &= ~(1UL << ofs); + + if (bitmap) { + entry = xa_mk_value(bitmap); + radix_tree_replace_slot(&im->ino_root, slot, entry); + } else { + radix_tree_delete(&im->ino_root, index); + } + } + spin_unlock(&im->ino_lock); +} + +static void f2fs_wait_for_inode_record(struct f2fs_sb_info *sbi, int mode) +{ + if (mode != APPEND_INO && mode != UPDATE_INO) + return; + + /* Let's wait for some pending updates for APPEND_INO and UPDATE_INO. */ + flush_workqueue(sbi->evict_wq); +} + +static void __f2fs_add_ino_entry(struct f2fs_sb_info *sbi, nid_t ino, + unsigned int devidx, int type) +{ + if (type <= FLUSH_INO) + __add_ino_entry(sbi, ino, devidx, type); + else + __set_ino_bitmap(sbi, ino, type); +} + void f2fs_add_ino_entry(struct f2fs_sb_info *sbi, nid_t ino, int type) { - /* add new dirty ino entry into list */ - __add_ino_entry(sbi, ino, 0, type); + __f2fs_add_ino_entry(sbi, ino, 0, type); } void f2fs_remove_ino_entry(struct f2fs_sb_info *sbi, nid_t ino, int type) { - /* remove dirty ino entry from list */ - __remove_ino_entry(sbi, ino, type); + if (type <= FLUSH_INO) + __remove_ino_entry(sbi, ino, type); + else + __clear_ino_bitmap(sbi, ino, type); } -/* mode should be APPEND_INO, UPDATE_INO or TRANS_DIR_INO */ +/* mode should be APPEND_INO, UPDATE_INO, TRANS_DIR_INO and XATTR_DIR_INO */ bool f2fs_exist_written_data(struct f2fs_sb_info *sbi, nid_t ino, int mode) { struct inode_management *im = &sbi->im[mode]; - struct ino_entry *e; + unsigned long index = INO_SLOT_INDEX(ino); + unsigned int ofs = INO_BIT_OFFSET(ino); + void *entry; + unsigned long bitmap; + + f2fs_bug_on(sbi, mode <= FLUSH_INO); spin_lock(&im->ino_lock); - e = radix_tree_lookup(&im->ino_root, ino); + entry = radix_tree_lookup(&im->ino_root, index); + if (!entry) { + spin_unlock(&im->ino_lock); + return false; + } + bitmap = xa_to_value(entry); spin_unlock(&im->ino_lock); - return e ? true : false; + + return bitmap & (1UL << ofs); } void f2fs_release_ino_entry(struct f2fs_sb_info *sbi, bool all) @@ -795,9 +884,11 @@ void f2fs_release_ino_entry(struct f2fs_sb_info *sbi, bool all) struct ino_entry *e, *tmp; int i; - for (i = all ? ORPHAN_INO : APPEND_INO; i < MAX_INO_ENTRY; i++) { + for (i = all ? ORPHAN_INO : FLUSH_INO; i <= FLUSH_INO; i++) { struct inode_management *im = &sbi->im[i]; + f2fs_wait_for_inode_record(sbi, i); + spin_lock(&im->ino_lock); list_for_each_entry_safe(e, tmp, &im->ino_list, list) { list_del(&e->list); @@ -807,12 +898,20 @@ void f2fs_release_ino_entry(struct f2fs_sb_info *sbi, bool all) } spin_unlock(&im->ino_lock); } + + for (i = APPEND_INO; i < MAX_INO_ENTRY; i++) { + struct inode_management *im = &sbi->im[i]; + + spin_lock(&im->ino_lock); + xa_destroy(&im->ino_root); + spin_unlock(&im->ino_lock); + } } void f2fs_set_dirty_device(struct f2fs_sb_info *sbi, nid_t ino, unsigned int devidx, int type) { - __add_ino_entry(sbi, ino, devidx, type); + __f2fs_add_ino_entry(sbi, ino, devidx, type); } bool f2fs_is_dirty_device(struct f2fs_sb_info *sbi, nid_t ino, @@ -864,14 +963,14 @@ void f2fs_release_orphan_inode(struct f2fs_sb_info *sbi) void f2fs_add_orphan_inode(struct inode *inode) { /* add new orphan ino entry into list */ - __add_ino_entry(F2FS_I_SB(inode), inode->i_ino, 0, ORPHAN_INO); + f2fs_add_ino_entry(F2FS_I_SB(inode), inode->i_ino, ORPHAN_INO); f2fs_update_inode_page(inode); } void f2fs_remove_orphan_inode(struct f2fs_sb_info *sbi, nid_t ino) { /* remove orphan entry from orphan list */ - __remove_ino_entry(sbi, ino, ORPHAN_INO); + f2fs_remove_ino_entry(sbi, ino, ORPHAN_INO); } static int recover_orphan_inode(struct f2fs_sb_info *sbi, nid_t ino) diff --git a/fs/f2fs/data.c b/fs/f2fs/data.c index dad5c2bd5888..21f396ebe22c 100644 --- a/fs/f2fs/data.c +++ b/fs/f2fs/data.c @@ -384,11 +384,30 @@ static void f2fs_write_end_bio(struct bio *bio) bio_put(bio); } +static void f2fs_write_end_io_work(struct work_struct *work) +{ + struct bio *bio = &container_of(work, struct f2fs_bio, work)->bio; + + f2fs_write_end_bio(bio); +} + static void f2fs_write_end_io(struct bio *bio) { + struct f2fs_sb_info *sbi; + iostat_update_and_unbind_ctx(bio); - f2fs_write_end_bio(bio); + sbi = bio->bi_private; + + if (in_atomic() && bio->bi_iter.bi_size > sbi->max_atc_write_bio_size) { + struct work_struct *w; + + w = &container_of(bio, struct f2fs_bio, bio)->work; + INIT_WORK(w, f2fs_write_end_io_work); + queue_work(sbi->wq, w); + } else { + f2fs_write_end_bio(bio); + } } #ifdef CONFIG_BLK_DEV_ZONED @@ -1216,7 +1235,7 @@ int f2fs_reserve_new_blocks(struct dnode_of_data *dn, blkcnt_t count) if (unlikely(is_inode_flag_set(dn->inode, FI_NO_ALLOC))) return -EPERM; - err = inc_valid_block_count(sbi, dn->inode, &count, true); + err = inc_valid_block_count(sbi, dn->inode, &count, true, false); if (unlikely(err)) return err; @@ -1288,10 +1307,11 @@ retry: if (folio_test_large(folio)) { pgoff_t folio_index = mapping_align_index(mapping, index); + unsigned long nr_pages = folio_nr_pages(folio); f2fs_folio_put(folio, true); invalidate_inode_pages2_range(mapping, folio_index, - folio_index + folio_nr_pages(folio) - 1); + folio_index + nr_pages - 1); f2fs_schedule_timeout(DEFAULT_SCHEDULE_TIMEOUT); goto retry; } @@ -1488,7 +1508,7 @@ static int __allocate_data_block(struct dnode_of_data *dn, int seg_type) dn->data_blkaddr = f2fs_data_blkaddr(dn); if (dn->data_blkaddr == NULL_ADDR) { - err = inc_valid_block_count(sbi, dn->inode, &count, true); + err = inc_valid_block_count(sbi, dn->inode, &count, true, false); if (unlikely(err)) return err; } @@ -1497,8 +1517,11 @@ static int __allocate_data_block(struct dnode_of_data *dn, int seg_type) old_blkaddr = dn->data_blkaddr; err = f2fs_allocate_data_block(sbi, NULL, old_blkaddr, &dn->data_blkaddr, &sum, seg_type, NULL); - if (err) + if (err) { + if (old_blkaddr == NULL_ADDR) + dec_valid_block_count(sbi, dn->inode, count); return err; + } if (GET_SEGNO(sbi, old_blkaddr) != NULL_SEGNO) f2fs_invalidate_internal_cache(sbi, old_blkaddr, 1); @@ -3900,7 +3923,7 @@ repeat: * Will wait that below with our IO control. */ folio = f2fs_filemap_get_folio(mapping, index, - FGP_LOCK | FGP_WRITE | FGP_CREAT | FGP_NOFS, + FGP_LOCK | FGP_WRITE | FGP_CREAT, mapping_gfp_mask(mapping)); if (IS_ERR(folio)) { err = PTR_ERR(folio); @@ -4460,13 +4483,24 @@ int f2fs_init_wq(struct f2fs_sb_info *sbi) { sbi->wq = alloc_workqueue("f2fs_wq", WQ_UNBOUND | WQ_HIGHPRI, num_online_cpus()); - return sbi->wq ? 0 : -ENOMEM; + if (!sbi->wq) + return -ENOMEM; + + sbi->evict_wq = alloc_workqueue("f2fs_evict_wq", + WQ_UNBOUND | WQ_HIGHPRI, num_online_cpus()); + if (!sbi->evict_wq) { + destroy_workqueue(sbi->wq); + return -ENOMEM; + } + return 0; } void f2fs_destroy_wq(struct f2fs_sb_info *sbi) { if (sbi->wq) destroy_workqueue(sbi->wq); + if (sbi->evict_wq) + destroy_workqueue(sbi->evict_wq); } int __init f2fs_init_bio_entry_cache(void) diff --git a/fs/f2fs/debug.c b/fs/f2fs/debug.c index af88db8fdb71..ff379aff4472 100644 --- a/fs/f2fs/debug.c +++ b/fs/f2fs/debug.c @@ -352,10 +352,6 @@ static void update_mem_info(struct f2fs_sb_info *sbi) get_cache: si->cache_mem = 0; - /* build gc */ - if (sbi->gc_thread) - si->cache_mem += sizeof(struct f2fs_gc_kthread); - /* build merge flush thread */ if (SM_I(sbi)->fcc_info) si->cache_mem += sizeof(struct flush_cmd_control); diff --git a/fs/f2fs/dir.c b/fs/f2fs/dir.c index a9563f7fcd88..fd0e2cd31a81 100644 --- a/fs/f2fs/dir.c +++ b/fs/f2fs/dir.c @@ -320,6 +320,7 @@ start_find_bucket: de = find_in_block(dir, dentry_folio, fname, &max_slots, use_hash); if (IS_ERR(de)) { + f2fs_folio_put(dentry_folio, false); *res_folio = ERR_CAST(de); de = NULL; break; @@ -460,7 +461,7 @@ void f2fs_set_link(struct inode *dir, struct f2fs_dir_entry *de, folio_mark_dirty(folio); inode_set_mtime_to_ts(dir, inode_set_ctime_current(dir)); - f2fs_mark_inode_dirty_sync(dir, false); + f2fs_mark_inode_dirty_sync(dir, true); f2fs_folio_put(folio, true); } @@ -615,7 +616,7 @@ void f2fs_update_parent_metadata(struct inode *dir, struct inode *inode, clear_inode_flag(inode, FI_NEW_INODE); } inode_set_mtime_to_ts(dir, inode_set_ctime_current(dir)); - f2fs_mark_inode_dirty_sync(dir, false); + f2fs_mark_inode_dirty_sync(dir, true); if (F2FS_I(dir)->i_current_depth != current_depth) f2fs_i_depth_write(dir, current_depth); @@ -927,7 +928,7 @@ void f2fs_delete_entry(struct f2fs_dir_entry *dentry, struct folio *folio, f2fs_folio_put(folio, true); inode_set_mtime_to_ts(dir, inode_set_ctime_current(dir)); - f2fs_mark_inode_dirty_sync(dir, false); + f2fs_mark_inode_dirty_sync(dir, true); if (inode) f2fs_drop_nlink(dir, inode); diff --git a/fs/f2fs/extent_cache.c b/fs/f2fs/extent_cache.c index 61f6b9714366..37cf9fa8d537 100644 --- a/fs/f2fs/extent_cache.c +++ b/fs/f2fs/extent_cache.c @@ -17,6 +17,7 @@ #include "f2fs.h" #include "node.h" +#include "segment.h" #include <trace/events/f2fs.h> bool sanity_check_extent_cache(struct inode *inode, struct folio *ifolio) @@ -62,6 +63,14 @@ bool sanity_check_extent_cache(struct inode *inode, struct folio *ifolio) __func__, inode->i_ino, ei.blk, ei.fofs, ei.len); return false; } + + if ((GET_SEGOFF_FROM_SEG0(sbi, ei.blk) % BLKS_PER_SEC(sbi)) || + (ei.len % BLKS_PER_SEC(sbi))) { + f2fs_warn(sbi, "%s: device alias inode (ino=%llx)'s extent info [%u, %u, %u] is not aligned to section size %u", + __func__, inode->i_ino, ei.blk, ei.fofs, ei.len, + BLKS_PER_SEC(sbi)); + return false; + } return true; } diff --git a/fs/f2fs/f2fs.h b/fs/f2fs/f2fs.h index 8376bbe58ee3..9940a6cecf1a 100644 --- a/fs/f2fs/f2fs.h +++ b/fs/f2fs/f2fs.h @@ -193,6 +193,7 @@ enum f2fs_lock_name { LOCK_NAME_GC_LOCK, LOCK_NAME_CP_GLOBAL, LOCK_NAME_IO_RWSEM, + LOCK_NAME_NAT_TREE_LOCK, LOCK_NAME_MAX, }; @@ -254,6 +255,7 @@ struct f2fs_mount_info { block_t unusable_cap; /* Amount of space allowed to be * unusable when disabling checkpoint */ + unsigned int resizable_tail_secno; /* number of resizable tail sections */ /* For compression */ unsigned char compress_algorithm; /* algorithm type */ @@ -388,14 +390,18 @@ enum { /* for the list of ino */ enum { ORPHAN_INO, /* for orphan ino list */ + FLUSH_INO, /* for multiple device flushing */ APPEND_INO, /* for append ino list */ UPDATE_INO, /* for update ino list */ TRANS_DIR_INO, /* for transactions dir ino list */ XATTR_DIR_INO, /* for xattr updated dir ino list */ - FLUSH_INO, /* for multiple device flushing */ MAX_INO_ENTRY, /* max. list */ }; +#define INO_BITS_PER_SLOT BITS_PER_XA_VALUE +#define INO_SLOT_INDEX(ino) ((ino) / INO_BITS_PER_SLOT) +#define INO_BIT_OFFSET(ino) ((ino) % INO_BITS_PER_SLOT) + struct ino_entry { struct list_head list; /* list head */ nid_t ino; /* inode number */ @@ -1403,6 +1409,8 @@ struct f2fs_dev_info { unsigned int total_segments; block_t start_blk; block_t end_blk; + bool has_alias; + bool is_reserving; #ifdef CONFIG_BLK_DEV_ZONED unsigned int nr_blkz; /* Total number of zones */ unsigned long *blkz_seq; /* Bitmap indicating sequential zones */ @@ -1747,6 +1755,33 @@ struct decompress_io_ctx { #define MAX_COMPRESS_LOG_SIZE 8 #define MAX_COMPRESS_WINDOW_SIZE(log_size) ((PAGE_SIZE) << (log_size)) +struct f2fs_gc_kthread { + struct task_struct *f2fs_gc_task; + wait_queue_head_t gc_wait_queue_head; + + /* for gc sleep time */ + unsigned int urgent_sleep_time; + unsigned int min_sleep_time; + unsigned int max_sleep_time; + unsigned int no_gc_sleep_time; + + /* for changing gc mode */ + bool gc_wake; + + /* for GC_MERGE mount option */ + wait_queue_head_t fggc_wq; /* + * caller of f2fs_balance_fs() + * will wait on this wait queue. + */ + + /* for gc control for zoned devices */ + unsigned int no_zoned_gc_percent; + unsigned int boost_zoned_gc_percent; + unsigned int valid_thresh_ratio; + unsigned int boost_gc_multiple; + unsigned int boost_gc_greedy; +}; + struct f2fs_sb_info { struct super_block *sb; /* pointer to VFS super block */ struct proc_dir_entry *s_proc; /* proc entry */ @@ -1772,6 +1807,8 @@ struct f2fs_sb_info { struct f2fs_sm_info *sm_info; /* segment manager */ /* for bio operations */ + /* Largest write bio size completed in atomic context (atc). */ + u32 max_atc_write_bio_size; struct f2fs_bio_info *write_io[NR_PAGE_TYPE]; /* for write bios */ /* keep migration IO order for LFS mode */ struct f2fs_rwsem io_order_lock; @@ -1854,6 +1891,7 @@ struct f2fs_sb_info { block_t last_valid_block_count; /* for recovery */ block_t reserved_blocks; /* configurable reserved blocks */ block_t current_reserved_blocks; /* current reserved blocks */ + block_t alias_reserved_blocks; /* reserved blocks for device alias */ /* Additional tracking for no checkpoint mode */ block_t unusable_block_count; /* # of blocks saved by last cp */ @@ -1882,7 +1920,7 @@ struct f2fs_sb_info { * semaphore for GC, avoid * race between GC and GC or CP */ - struct f2fs_gc_kthread *gc_thread; /* GC thread */ + struct f2fs_gc_kthread gc_thread; /* GC thread */ struct atgc_management am; /* atgc management */ unsigned int cur_victim_sec; /* current victim section num */ unsigned int gc_mode; /* current GC state */ @@ -1967,6 +2005,7 @@ struct f2fs_sb_info { spinlock_t dev_lock; /* protect dirty_device */ bool aligned_blksize; /* all devices has the same logical blksize */ unsigned int first_seq_zone_segno; /* first segno in sequential zone */ + unsigned int pinned_area_max_secno; /* upper bound section for pinned files */ unsigned int bggc_io_aware; /* For adjust the BG_GC priority when pending IO */ unsigned int allocate_section_hint; /* the boundary position between devices */ unsigned int allocate_section_policy; /* determine the section writing priority */ @@ -1980,6 +2019,8 @@ struct f2fs_sb_info { struct workqueue_struct *wq; /* bio completion workqueue */ + struct workqueue_struct *evict_wq; /* inode eviction workqueue */ + /* * If we are in irq context, let's update error information into * on-disk superblock in the work. @@ -2556,7 +2597,8 @@ static inline unsigned int get_available_block_count(struct f2fs_sb_info *sbi, block_t avail_user_block_count; avail_user_block_count = sbi->user_block_count - - sbi->current_reserved_blocks; + sbi->current_reserved_blocks - + sbi->alias_reserved_blocks; if (test_opt(sbi, RESERVE_ROOT) && !__allow_reserved_root(sbi, inode, cap)) avail_user_block_count -= F2FS_OPTION(sbi).root_reserved_blocks; @@ -2573,7 +2615,8 @@ static inline unsigned int get_available_block_count(struct f2fs_sb_info *sbi, static inline void f2fs_i_blocks_write(struct inode *, block_t, bool, bool); static inline int inc_valid_block_count(struct f2fs_sb_info *sbi, - struct inode *inode, blkcnt_t *count, bool partial) + struct inode *inode, blkcnt_t *count, + bool partial, bool alias_reserved) { long long diff = 0, release = 0; block_t avail_user_block_count; @@ -2596,10 +2639,16 @@ static inline int inc_valid_block_count(struct f2fs_sb_info *sbi, spin_lock(&sbi->stat_lock); + if (alias_reserved) + sbi->alias_reserved_blocks -= *count; + avail_user_block_count = get_available_block_count(sbi, inode, true); diff = (long long)sbi->total_valid_block_count + *count - avail_user_block_count; if (unlikely(diff > 0)) { + if (alias_reserved) + sbi->alias_reserved_blocks += *count; + if (!partial) { spin_unlock(&sbi->stat_lock); release = *count; @@ -3830,7 +3879,10 @@ void f2fs_update_inode_page(struct inode *inode); int f2fs_write_inode(struct inode *inode, struct writeback_control *wbc); void f2fs_remove_donate_inode(struct inode *inode); void f2fs_evict_inode(struct inode *inode); -void f2fs_handle_failed_inode(struct inode *inode, struct f2fs_lock_context *lc); +void f2fs_handle_failed_inode(struct inode *inode, + struct f2fs_lock_context *lc, bool add_orphan); +int f2fs_init_evict_inode_work(void); +void f2fs_destroy_evict_inode_work(void); /* * namei.c @@ -4007,6 +4059,8 @@ int f2fs_flush_device_cache(struct f2fs_sb_info *sbi); void f2fs_destroy_flush_cmd_control(struct f2fs_sb_info *sbi, bool free); void f2fs_invalidate_blocks(struct f2fs_sb_info *sbi, block_t addr, unsigned int len); +void f2fs_reserve_device_alias(struct f2fs_sb_info *sbi, block_t addr, + unsigned int len); bool f2fs_is_checkpointed_data(struct f2fs_sb_info *sbi, block_t blkaddr); int f2fs_start_discard_thread(struct f2fs_sb_info *sbi); void f2fs_drop_discard_cmd(struct f2fs_sb_info *sbi); @@ -4095,10 +4149,26 @@ static inline struct inode *fio_inode(struct f2fs_io_info *fio) #define MIN_FRAGMENT_SIZE 1 #define MAX_FRAGMENT_SIZE 512 -static inline bool f2fs_need_rand_seg(struct f2fs_sb_info *sbi) +static inline bool f2fs_need_rand_blk(struct f2fs_sb_info *sbi, + enum log_type type) +{ + if (type == CURSEG_COLD_DATA_PINNED) + return false; + return F2FS_OPTION(sbi).fs_mode == FS_MODE_FRAGMENT_BLK; +} + +static inline bool f2fs_need_rand_seg(struct f2fs_sb_info *sbi, + enum log_type type) +{ + if (type == CURSEG_COLD_DATA_PINNED) + return false; + return F2FS_OPTION(sbi).fs_mode == FS_MODE_FRAGMENT_SEG; +} + +static inline bool f2fs_need_rand_seg_blk(struct f2fs_sb_info *sbi, + enum log_type type) { - return F2FS_OPTION(sbi).fs_mode == FS_MODE_FRAGMENT_SEG || - F2FS_OPTION(sbi).fs_mode == FS_MODE_FRAGMENT_BLK; + return f2fs_need_rand_blk(sbi, type) || f2fs_need_rand_seg(sbi, type); } /* @@ -4226,7 +4296,9 @@ int f2fs_gc(struct f2fs_sb_info *sbi, struct f2fs_gc_control *gc_control); void f2fs_build_gc_manager(struct f2fs_sb_info *sbi); int f2fs_gc_range(struct f2fs_sb_info *sbi, unsigned int start_seg, unsigned int end_seg, - bool dry_run, unsigned int dry_run_sections); + bool dry_run, unsigned int dry_run_sections, bool lock); +void f2fs_reset_gc_victim_resource(struct f2fs_sb_info *sbi, + unsigned int start, unsigned int end); int f2fs_resize_fs(struct file *filp, __u64 block_count); int __init f2fs_create_garbage_collection_cache(void); void f2fs_destroy_garbage_collection_cache(void); diff --git a/fs/f2fs/file.c b/fs/f2fs/file.c index 089759366cdc..edc352569e87 100644 --- a/fs/f2fs/file.c +++ b/fs/f2fs/file.c @@ -36,23 +36,69 @@ #include <trace/events/f2fs.h> #include <uapi/linux/f2fs.h> -static void f2fs_zero_post_eof_page(struct inode *inode, - loff_t new_size, bool lock) +static int fill_zero(struct inode *inode, pgoff_t index, + loff_t start, loff_t len); + +static int do_zero_post_eof_page(struct inode *inode, loff_t new_size) { loff_t old_size = i_size_read(inode); + unsigned int offset, len; + pgoff_t index; + int err; + + offset = old_size & (PAGE_SIZE - 1); + + if (!offset) + return 0; + + len = min_t(loff_t, PAGE_SIZE - offset, new_size - old_size); + index = old_size >> PAGE_SHIFT; + + if (f2fs_has_inline_data(inode)) { + /* data post eof should be always zero */ + if (new_size <= MAX_INLINE_DATA(inode)) + return 0; + err = f2fs_convert_inline_inode(inode); + if (err) + return err; + } + + err = fill_zero(inode, index, offset, len); + if (err) + return err; + return filemap_write_and_wait_range(inode->i_mapping, + old_size, old_size + len - 1); +} + +static int f2fs_zero_post_eof_page(struct inode *inode, + loff_t new_size, bool lock, bool writeback) +{ + loff_t old_size = i_size_read(inode); + bool strict = + F2FS_OPTION(F2FS_I_SB(inode)).fsync_mode == FSYNC_MODE_STRICT; if (old_size >= new_size) - return; + return 0; - if (mapping_empty(inode->i_mapping)) - return; + if (!strict && mapping_empty(inode->i_mapping)) + return 0; |
