From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-alma10-1.taild15c8.ts.net [100.103.45.18]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 0BC033FFD for ; Wed, 26 Aug 2026 02:03:11 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=100.103.45.18 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787709792; cv=none; b=QH9ZjG/d+6b/u9r/o/hqS6D5zvtJsiQLsdeyonuIcHuPYL2JPHjoJUYsUEHq1W3E2/aSM7H46DK9ANWG43FVx2KtVpijuQQ+FYGAhbEhasgoI03ObYyfCbuxhBbvtT7fvmSnXDXng2xKTOOdD2ZdayvIFZ1PAuYksXN81KtT3vA= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787709792; c=relaxed/simple; bh=Kn80mhmX9qF5qyDN/Ul2nTSvNpMMptm8ZrtUvEqvzJA=; h=Message-ID:Date:MIME-Version:Cc:Subject:To:References:From: In-Reply-To:Content-Type; b=JGWKwq1sLOANWv3jvgcbJpYwPcNiMSknkx3eXQfSM8G1pkSREs9TpEoq+9/RIZdCGOEOVPE54Yy49ZR+MBGAeR3mH5yI1Db4zxcIGk5zpA5TlceX4rIOiKy+9woczCD+bwtpjKeKHCaWtH2B1qg7UaI4iwhmJJNNqrrNanqlJAg= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=dRGOmqFV; arc=none smtp.client-ip=100.103.45.18 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="dRGOmqFV" Received: by smtp.kernel.org (Postfix) with ESMTPSA id DA0261F000E9; Wed, 26 Aug 2026 02:03:09 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=kernel.org; s=k20260515; t=1787709790; bh=tBzUdJeSdTzGaRI5If1BMgwks/AwJh7GQ1o3WyB/F7k=; h=Date:Cc:Subject:To:References:From:In-Reply-To; b=dRGOmqFV/IspwmnGZ+mvilwH5kwyM9+XzOMHEv7wbIfveOCCXU1TYKKM/8mIdWqh0 R51YREqFQ79gNeVej1ecyFCg1Ilhw6bL19kr4owc8FzU2MnPLi1+YBQ0PUrQZ3iLCa T8YBUZWShlF83CmGXMFcO+X71V+rZ9h2OE+VN4FIT39D/CdxNM7idoHUVBshJMxiMR 1Fw/9MstmH9OVK8mj+7R3p/iDXnykNdvSR7wYsPsZ1vX4itDb0yT9Zq5YuVrVIDr9/ EoGSphFkzcdpLt1xE0ci+Xirpb3vQp1gScaE/ZO/luM3DEV7Ikny23StOvRHm+qith FioCs+qN5a6dw== Message-ID: <7b1ca156-316d-428c-9a8b-4fe55eb9cbfd@kernel.org> Date: Wed, 26 Aug 2026 10:03:08 +0800 Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 User-Agent: Mozilla Thunderbird Cc: chao@kernel.org, linux-kernel@vger.kernel.org, linux-f2fs-devel@lists.sourceforge.net, kernel-team@android.com, Daeho Jeong Subject: Re: [f2fs-dev] [PATCH] f2fs: support resizable tail section and unify pinned allocation To: Daeho Jeong References: <20260824191404.2558269-1-daeho43@gmail.com> <093eae1e-f533-404c-b8da-f5025d4e3db2@kernel.org> Content-Language: en-US From: Chao Yu In-Reply-To: Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On 8/26/26 00:14, Daeho Jeong wrote: > On Mon, Aug 24, 2026 at 8:16 PM Chao Yu wrote: >> >> On 8/25/26 03:14, Daeho Jeong wrote: >>> From: Daeho Jeong >>> >>> Currently, zoned block devices restrict pinned file allocations to >>> conventional zones at the beginning of the storage (before >>> first_seq_zone_segno), triggering range GC when conventional space is >>> exhausted. >>> >>> On regular block devices, when preparing for future online filesystem >>> resizing (e.g. partition shrinking), pinned files must not be allocated >>> in the tail area that will be truncated, as pinned files cannot be >>> relocated by GC. Specifying the resizable tail area size (in sections) >>> allows uniform mount configuration across devices of different storage >>> capacities. >>> >>> To support this, introduce a unified `pinned_area_max_secno` boundary >>> abstraction in `f2fs_sb_info`: >>> 1. Add `-o resizable_tail_secno=%u` mount option to specify the number >>> of sections at the tail of the filesystem reserved for resizing. >>> 2. In `f2fs_fill_super()`, initialize `sbi->pinned_area_max_secno` as: >>> min(MAIN_SECS(sbi) - resizable_tail_sec, zoned_max_sec). >>> 3. In `get_new_segment()`, restrict segment allocation for pinned files >>> (`pinning == true`) to `0 .. sbi->pinned_area_max_secno - 1`. If no >>> free section is available in the pinned area, return -EAGAIN. >>> 4. In `f2fs_allocate_pinning_section()`, unify the range GC trigger to >>> run `f2fs_gc_range()` up to `sbi->pinned_area_max_secno` whenever >>> `sbi->pinned_area_max_secno < MAIN_SECS(sbi)` and allocation >>> returns -EAGAIN. >>> 5. Expose `/sys/fs/f2fs//pinned_area_max_secno` as a read-only >>> sysfs node. >>> >>> Signed-off-by: Daeho Jeong >>> Signed-off-by: Sunmin Jeong >>> --- >>> Documentation/ABI/testing/sysfs-fs-f2fs | 7 +++++ >>> Documentation/filesystems/f2fs.rst | 8 +++++- >>> fs/f2fs/f2fs.h | 2 ++ >>> fs/f2fs/segment.c | 33 ++++++++++++----------- >>> fs/f2fs/segment.h | 1 + >>> fs/f2fs/super.c | 36 +++++++++++++++++++++++++ >>> fs/f2fs/sysfs.c | 2 ++ >>> 7 files changed, 72 insertions(+), 17 deletions(-) >>> >>> diff --git a/Documentation/ABI/testing/sysfs-fs-f2fs b/Documentation/ABI/testing/sysfs-fs-f2fs >>> index 85194e4c7f01..0cebc89799dd 100644 >>> --- a/Documentation/ABI/testing/sysfs-fs-f2fs >>> +++ b/Documentation/ABI/testing/sysfs-fs-f2fs >>> @@ -1013,3 +1013,10 @@ Description: Every time a write operation completes f2fs_write_end_io() is >>> 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//pinned_area_max_secno >>> +Date: August 2026 >>> +Contact: "Daeho Jeong" >>> +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 1a5fd4afe609..a3c3b6948734 100644 >>> --- a/Documentation/filesystems/f2fs.rst >>> +++ b/Documentation/filesystems/f2fs.rst >>> @@ -417,7 +417,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 >>> diff --git a/fs/f2fs/f2fs.h b/fs/f2fs/f2fs.h >>> index b0a9c14de595..16720f1f0a9c 100644 >>> --- a/fs/f2fs/f2fs.h >>> +++ b/fs/f2fs/f2fs.h >>> @@ -255,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 */ >>> @@ -2005,6 +2006,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 */ >>> diff --git a/fs/f2fs/segment.c b/fs/f2fs/segment.c >>> index 56decf9c691c..ed6f2947210b 100644 >>> --- a/fs/f2fs/segment.c >>> +++ b/fs/f2fs/segment.c >>> @@ -2877,6 +2877,7 @@ static int get_new_segment(struct f2fs_sb_info *sbi, >>> unsigned int old_zoneno = GET_ZONE_FROM_SEG(sbi, *newseg); >>> unsigned int alloc_policy = sbi->allocate_section_policy; >>> unsigned int alloc_hint = sbi->allocate_section_hint; >>> + unsigned int max_secno = MAIN_SECS(sbi); >>> bool init = true; >>> bool looped = false; >>> int i, devi; >>> @@ -2908,7 +2909,7 @@ static int get_new_segment(struct f2fs_sb_info *sbi, >>> */ >>> if (f2fs_sb_has_blkzoned(sbi)) { >>> /* Prioritize writing to conventional zones */ >>> - if (sbi->blkzone_alloc_policy == BLKZONE_ALLOC_PRIOR_CONV || pinning) >>> + if (sbi->blkzone_alloc_policy == BLKZONE_ALLOC_PRIOR_CONV) >>> segno = 0; >>> else >>> segno = max(sbi->first_seq_zone_segno, *newseg); >>> @@ -2924,19 +2925,24 @@ static int get_new_segment(struct f2fs_sb_info *sbi, >>> alloc_hint > MAIN_SECS(sbi)) >>> alloc_hint = MAIN_SECS(sbi); >>> >>> - if (alloc_policy == ALLOCATE_FORWARD_FROM_HINT && >>> - hint < alloc_hint) >>> + if (pinning) { >>> + max_secno = sbi->pinned_area_max_secno; >>> + hint = 0; >>> + } else if (alloc_policy == ALLOCATE_FORWARD_FROM_HINT && >>> + hint < alloc_hint) { >>> hint = alloc_hint; >>> - else if (alloc_policy == ALLOCATE_FORWARD_WITHIN_HINT && >>> - hint >= alloc_hint) >>> + } else if (alloc_policy == ALLOCATE_FORWARD_WITHIN_HINT && >>> + hint >= alloc_hint) { >>> hint = 0; >>> + } >>> >>> find_other_zone: >>> - secno = find_next_zero_bit(free_i->free_secmap, MAIN_SECS(sbi), hint); >>> + secno = find_next_zero_bit(free_i->free_secmap, max_secno, hint); >>> >>> - if (secno >= MAIN_SECS(sbi)) { >>> + if (secno >= max_secno) { >>> if (looped) { >>> - ret = -ENOSPC; >>> + ret = (pinning && has_pinned_area(sbi)) ? >>> + -EAGAIN : -ENOSPC; >>> f2fs_bug_on(sbi, !pinning); >>> goto out_unlock; >>> } >>> @@ -3001,12 +3007,6 @@ static int get_new_segment(struct f2fs_sb_info *sbi, >>> goto out_unlock; >>> } >>> >>> - /* no free section in conventional device or conventional zone */ >>> - if (new_sec && pinning && >>> - f2fs_is_sequential_zone_area(sbi, START_BLOCK(sbi, segno))) { >>> - ret = -EAGAIN; >>> - goto out_unlock; >>> - } >>> __set_inuse(sbi, segno); >>> *newseg = segno; >>> out_unlock: >>> @@ -3472,8 +3472,9 @@ int f2fs_allocate_pinning_section(struct f2fs_sb_info *sbi) >>> err = f2fs_allocate_new_section(sbi, CURSEG_COLD_DATA_PINNED, false); >>> f2fs_unlock_op(sbi, &lc); >>> >>> - if (f2fs_sb_has_blkzoned(sbi) && err == -EAGAIN && gc_required) { >>> - err = f2fs_gc_range(sbi, 0, sbi->first_seq_zone_segno - 1, >>> + if (has_pinned_area(sbi) && err == -EAGAIN && gc_required) { >>> + err = f2fs_gc_range(sbi, 0, >>> + sbi->pinned_area_max_secno * SEGS_PER_SEC(sbi) - 1, >>> true, ZONED_PIN_SEC_REQUIRED_COUNT, true); >>> if (err) >>> return err; >>> diff --git a/fs/f2fs/segment.h b/fs/f2fs/segment.h >>> index db1079169a23..1dd8a2fa929d 100644 >>> --- a/fs/f2fs/segment.h >>> +++ b/fs/f2fs/segment.h >>> @@ -43,6 +43,7 @@ static inline void sanity_check_seg_type(struct f2fs_sb_info *sbi, >>> >>> #define MAIN_SEGS(sbi) (SM_I(sbi)->main_segments) >>> #define MAIN_SECS(sbi) ((sbi)->total_sections) >>> +#define has_pinned_area(sbi) ((sbi)->pinned_area_max_secno < MAIN_SECS(sbi)) >> >> Looks more like has_unpinned_area(sbi), since we assign pinned_area_max_secno >> w/ MAIN_SECS(sbi) by default, and will decrease it only if there is conv sections >> or valid resizable_tail_secno. Daeho, Any comments about this macro naming? >> >>> >>> #define TOTAL_SEGS(sbi) \ >>> (SM_I(sbi) ? SM_I(sbi)->segment_count : \ >>> diff --git a/fs/f2fs/super.c b/fs/f2fs/super.c >>> index 3bdb0f891c35..c1e315282ec6 100644 >>> --- a/fs/f2fs/super.c >>> +++ b/fs/f2fs/super.c >>> @@ -235,6 +235,7 @@ enum { >>> Opt_jqfmt, >>> Opt_checkpoint, >>> Opt_lookup_mode, >>> + Opt_resizable_tail_secno, >>> Opt_err, >>> }; >>> >>> @@ -366,6 +367,7 @@ static const struct fs_parameter_spec f2fs_param_specs[] = { >>> fsparam_flag("age_extent_cache", Opt_age_extent_cache), >>> fsparam_enum("errors", Opt_errors, f2fs_param_errors), >>> fsparam_enum("lookup_mode", Opt_lookup_mode, f2fs_param_lookup_mode), >>> + fsparam_u32("resizable_tail_secno", Opt_resizable_tail_secno), >>> {} >>> }; >>> >>> @@ -551,6 +553,17 @@ static inline void adjust_unusable_cap_perc(struct f2fs_sb_info *sbi) >>> F2FS_OPTION(sbi).unusable_cap_perc); >>> } >>> >>> +static inline void adjust_pinned_area_boundary(struct f2fs_sb_info *sbi) >>> +{ >>> + sbi->pinned_area_max_secno = MAIN_SECS(sbi); >>> + if (f2fs_sb_has_blkzoned(sbi) && sbi->first_seq_zone_segno != NULL_SEGNO) >>> + sbi->pinned_area_max_secno = min(sbi->pinned_area_max_secno, >>> + GET_SEC_FROM_SEG(sbi, sbi->first_seq_zone_segno)); >>> + if (F2FS_OPTION(sbi).resizable_tail_secno) >>> + sbi->pinned_area_max_secno = min(sbi->pinned_area_max_secno, >>> + MAIN_SECS(sbi) - F2FS_OPTION(sbi).resizable_tail_secno); >>> +} >>> + >>> static void init_once(void *foo) >>> { >>> struct f2fs_inode_info *fi = (struct f2fs_inode_info *) foo; >>> @@ -1235,6 +1248,9 @@ static int f2fs_parse_param(struct fs_context *fc, struct fs_parameter *param) >>> F2FS_CTX_INFO(ctx).lookup_mode = result.uint_32; >>> ctx->spec_mask |= F2FS_SPEC_lookup_mode; >>> break; >>> + case Opt_resizable_tail_secno: >>> + F2FS_CTX_INFO(ctx).resizable_tail_secno = result.uint_32; >>> + break; >>> } >>> return 0; >>> } >>> @@ -1771,6 +1787,12 @@ static void f2fs_apply_options(struct fs_context *fc, struct super_block *sb) >>> >>> static int f2fs_sanity_check_options(struct f2fs_sb_info *sbi, bool remount) >>> { >>> + if (remount && >>> + F2FS_OPTION(sbi).resizable_tail_secno >= MAIN_SECS(sbi)) { >>> + f2fs_err(sbi, "Option resizable_tail_secno is larger than or equal to total sections (%u >= %u)", >>> + F2FS_OPTION(sbi).resizable_tail_secno, MAIN_SECS(sbi)); >>> + return -EINVAL; >>> + } >>> if (f2fs_sb_has_device_alias(sbi) && >>> !test_opt(sbi, READ_EXTENT_CACHE)) { >>> f2fs_err(sbi, "device aliasing requires extent cache"); >>> @@ -2544,6 +2566,10 @@ static int f2fs_show_options(struct seq_file *seq, struct dentry *root) >>> else if (F2FS_OPTION(sbi).lookup_mode == LOOKUP_AUTO) >>> seq_show_option(seq, "lookup_mode", "auto"); >>> >>> + if (F2FS_OPTION(sbi).resizable_tail_secno) >>> + seq_printf(seq, ",resizable_tail_secno=%u", >>> + F2FS_OPTION(sbi).resizable_tail_secno); >>> + >>> return 0; >>> } >>> >>> @@ -2586,6 +2612,7 @@ static void default_options(struct f2fs_sb_info *sbi, bool remount) >>> F2FS_OPTION(sbi).bggc_mode = BGGC_MODE_ON; >>> F2FS_OPTION(sbi).memory_mode = MEMORY_MODE_NORMAL; >>> F2FS_OPTION(sbi).errors = MOUNT_ERRORS_CONTINUE; >>> + F2FS_OPTION(sbi).resizable_tail_secno = 0; >>> >>> set_opt(sbi, INLINE_XATTR); >>> set_opt(sbi, INLINE_DATA); >>> @@ -3045,6 +3072,7 @@ static int __f2fs_remount(struct fs_context *fc, struct super_block *sb) >>> sb->s_flags = (sb->s_flags & ~SB_POSIXACL) | >>> (test_opt(sbi, POSIX_ACL) ? SB_POSIXACL : 0); >>> >>> + adjust_pinned_area_boundary(sbi); >>> limit_reserve_root(sbi); >>> fc->sb_flags = (flags & ~SB_LAZYTIME) | (sb->s_flags & SB_LAZYTIME); >>> >>> @@ -5287,6 +5315,14 @@ static int f2fs_fill_super(struct super_block *sb, struct fs_context *fc) >>> /* get segno of first zoned block device */ >>> sbi->first_seq_zone_segno = get_first_seq_zone_segno(sbi); >>> >>> + if (F2FS_OPTION(sbi).resizable_tail_secno >= MAIN_SECS(sbi)) { >>> + f2fs_err(sbi, "Option resizable_tail_secno is larger than or equal to total sections (%u >= %u)", >>> + F2FS_OPTION(sbi).resizable_tail_secno, MAIN_SECS(sbi)); >>> + err = -EINVAL; >>> + goto free_nm; >>> + } >> >> How about merging this check into the one in f2fs_sanity_check_options()? > > The reason it was kept separate is that during the initial mount, > f2fs_sanity_check_options(sbi, false) is called before init_sb_info(sbi). > At that point, sbi->total_sections has not been initialized yet, so > MAIN_SECS(sbi) is still 0. Ah, I see, thanks for the explanation. > > If we simply check `F2FS_OPTION(sbi).resizable_tail_secno >= MAIN_SECS(sbi)` > in f2fs_sanity_check_options(), it evaluates against 0 and causes the initial > mount to unconditionally fail whenever resizable_tail_secno is specified. > > This is similar to how `max_open_zones` is checked only on remount in > f2fs_sanity_check_options() and checked separately during the initial mount. > > However, if you'd prefer to merge them into f2fs_sanity_check_options(), we > can use le32_to_cpu(sbi->raw_super->section_count), > since sbi->raw_super is already loaded in memory by then. I think latter one is better, more clean and better to maintain, please merge into f2fs_sanity_check_options(). Thanks, > > Which one do you prefer? > > Thanks, > >> >> Thanks, >> >>> + adjust_pinned_area_boundary(sbi); >>> + >>> sbi->reserved_pin_section = f2fs_sb_has_blkzoned(sbi) ? >>> ZONED_PIN_SEC_REQUIRED_COUNT : >>> GET_SEC_FROM_SEG(sbi, overprovision_segments(sbi)); >>> diff --git a/fs/f2fs/sysfs.c b/fs/f2fs/sysfs.c >>> index 3201e2185fea..811e350a1430 100644 >>> --- a/fs/f2fs/sysfs.c >>> +++ b/fs/f2fs/sysfs.c >>> @@ -1313,6 +1313,7 @@ F2FS_SBI_GENERAL_RW_ATTR(blkzone_alloc_policy); >>> #endif >>> F2FS_SBI_GENERAL_RW_ATTR(carve_out); >>> F2FS_SBI_GENERAL_RW_ATTR(reserved_pin_section); >>> +F2FS_SBI_GENERAL_RO_ATTR(pinned_area_max_secno); >>> F2FS_SBI_GENERAL_RW_ATTR(bggc_io_aware); >>> F2FS_SBI_GENERAL_RW_ATTR(max_lock_elapsed_time); >>> F2FS_SBI_GENERAL_RW_ATTR(lock_duration_priority); >>> @@ -1525,6 +1526,7 @@ static struct attribute *f2fs_attrs[] = { >>> ATTR_LIST(max_read_extent_count), >>> ATTR_LIST(carve_out), >>> ATTR_LIST(reserved_pin_section), >>> + ATTR_LIST(pinned_area_max_secno), >>> ATTR_LIST(allocate_section_hint), >>> ATTR_LIST(allocate_section_policy), >>> ATTR_LIST(max_lock_elapsed_time), >>