* [PATCH 1/3] ext4: make fast commit support optional
2026-11-11 12:52 [PATCH 0/3] ext4: reduce per-inode memory overhead Max Kellermann
@ 2026-11-11 12:52 ` Max Kellermann
2026-11-11 12:52 ` [PATCH 2/3] ext4: make bigalloc " Max Kellermann
2026-11-11 12:52 ` [PATCH 3/3] ext4: store creation time in separate seconds and nanoseconds Max Kellermann
2 siblings, 0 replies; 4+ messages in thread
From: Max Kellermann @ 2026-11-11 12:52 UTC (permalink / raw)
To: tytso, adilger.kernel, libaokun, jack, ojaswin, ritesh.list,
yi.zhang, linux-ext4, linux-kernel
Cc: Max Kellermann
Add CONFIG_EXT4_FS_FAST_COMMIT, enabled by default, to allow builds
without fast commit support. Disabling it saves 48 bytes per inode.
Fall back to ordinary journal commits and reject journals requiring
fast commit recovery.
Signed-off-by: Max Kellermann <max.kellermann@ionos.com>
---
Documentation/filesystems/ext4/journal.rst | 7 +++
fs/ext4/Kconfig | 16 ++++++
fs/ext4/Makefile | 3 +-
fs/ext4/ext4.h | 63 ++++++++++++++++++++++
fs/ext4/inode.c | 4 +-
fs/ext4/super.c | 30 ++++++++---
fs/ext4/sysfs.c | 6 +++
include/trace/events/ext4.h | 3 ++
8 files changed, 121 insertions(+), 11 deletions(-)
diff --git a/Documentation/filesystems/ext4/journal.rst b/Documentation/filesystems/ext4/journal.rst
index 6e8fb2d4b46f..2eae34487322 100644
--- a/Documentation/filesystems/ext4/journal.rst
+++ b/Documentation/filesystems/ext4/journal.rst
@@ -39,6 +39,13 @@ A full commit invalidates all the fast commits that happened before
it and thus it makes the fast commit area empty for further fast
commits. This feature needs to be enabled at mkfs time.
+Fast commit support can be omitted with ``CONFIG_EXT4_FS_FAST_COMMIT=n``
+to reduce the memory used by each in-memory inode. Such kernels use
+ordinary journal commits and can mount clean filesystems with the
+``fast_commit`` feature. If the journal advertises fast commit records,
+mounting with journal loading is refused; recover the filesystem first
+using a kernel or e2fsck with fast commit support.
+
The journal inode is typically inode 8. The first 68 bytes of the
journal inode are replicated in the ext4 superblock. The journal itself
is normal (but hidden) file within the filesystem. The file usually
diff --git a/fs/ext4/Kconfig b/fs/ext4/Kconfig
index 01873c2a34ad..0f3e702cd5e1 100644
--- a/fs/ext4/Kconfig
+++ b/fs/ext4/Kconfig
@@ -43,6 +43,22 @@ config EXT4_USE_FOR_EXT2
compiled kernel size by using one file system driver for
ext2, ext3, and ext4 file systems.
+config EXT4_FS_FAST_COMMIT
+ bool "Ext4 fast commit support"
+ depends on EXT4_FS
+ default y
+ help
+ Fast commits reduce journal commit latency by logging metadata
+ changes instead of complete metadata blocks. They are used on
+ filesystems with the fast_commit feature enabled.
+
+ Say N to reduce memory usage per inode and omit fast commit code.
+ Clean filesystems with the fast_commit feature can still be mounted
+ using ordinary journaling, but journals containing fast commits must
+ first be recovered by a kernel or e2fsck with fast commit support.
+
+ If unsure, say Y.
+
config EXT4_FS_POSIX_ACL
bool "Ext4 POSIX Access Control Lists"
depends on EXT4_FS
diff --git a/fs/ext4/Makefile b/fs/ext4/Makefile
index 3f9fc0eb8eca..e6a2d84211ec 100644
--- a/fs/ext4/Makefile
+++ b/fs/ext4/Makefile
@@ -10,8 +10,9 @@ ext4-y := balloc.o bitmap.o block_validity.o dir.o ext4_jbd2.o extents.o \
indirect.o inline.o inode.o ioctl.o mballoc.o migrate.o \
mmp.o move_extent.o namei.o page-io.o readpage.o resize.o \
super.o symlink.o sysfs.o xattr.o xattr_hurd.o xattr_trusted.o \
- xattr_user.o fast_commit.o orphan.o
+ xattr_user.o orphan.o
+ext4-$(CONFIG_EXT4_FS_FAST_COMMIT) += fast_commit.o
ext4-$(CONFIG_EXT4_FS_POSIX_ACL) += acl.o
ext4-$(CONFIG_EXT4_FS_SECURITY) += xattr_security.o
ext4-test-objs += inode-test.o mballoc-test.o \
diff --git a/fs/ext4/ext4.h b/fs/ext4/ext4.h
index bf0325ecbec7..1aedbfbcb101 100644
--- a/fs/ext4/ext4.h
+++ b/fs/ext4/ext4.h
@@ -1089,6 +1089,7 @@ struct ext4_inode_info {
unsigned int i_orphan_idx; /* Index in orphan file */
};
+#ifdef CONFIG_EXT4_FS_FAST_COMMIT
/* Fast commit related info */
/* For tracking dentry create updates */
@@ -1119,14 +1120,17 @@ struct ext4_inode_info {
* - data range records for EXT4_FC_TAG_{ADD,DEL}_RANGE
*/
struct ext4_fc_inode_snap *i_fc_snap;
+#endif
spinlock_t i_raw_lock; /* protects updates to the raw inode */
+#ifdef CONFIG_EXT4_FS_FAST_COMMIT
/*
* Protect concurrent accesses on i_fc_lblk_start, i_fc_lblk_len
* and inode's EXT4_FC_STATE_COMMITTING state bit.
*/
spinlock_t i_fc_lock;
+#endif
/*
* i_disksize keeps track of what the inode size is ON DISK, not
@@ -1845,6 +1849,7 @@ struct ext4_sb_info {
unsigned int s_awu_min;
unsigned int s_awu_max;
+#ifdef CONFIG_EXT4_FS_FAST_COMMIT
/* Ext4 fast commit sub transaction ID */
atomic_t s_fc_subtid;
@@ -1877,6 +1882,7 @@ struct ext4_sb_info {
int s_fc_debug_max_replay;
#endif
struct ext4_fc_replay_state s_fc_replay_state;
+#endif
};
static inline struct ext4_sb_info *EXT4_SB(struct super_block *sb)
@@ -1912,6 +1918,7 @@ static inline void ext4_writepages_up_write(struct super_block *sb, int ctx)
percpu_up_write(&EXT4_SB(sb)->s_writepages_rwsem);
}
+#ifdef CONFIG_EXT4_FS_FAST_COMMIT
static inline int ext4_fc_lock(struct super_block *sb)
{
mutex_lock(&EXT4_SB(sb)->s_fc_lock);
@@ -1923,6 +1930,7 @@ static inline void ext4_fc_unlock(struct super_block *sb, int ctx)
memalloc_nofs_restore(ctx);
mutex_unlock(&EXT4_SB(sb)->s_fc_lock);
}
+#endif
static inline int ext4_valid_inum(struct super_block *sb, unsigned long ino)
{
@@ -3086,6 +3094,12 @@ extern int ext4_init_inode_table(struct super_block *sb,
void ext4_end_bitmap_read(struct bio *bio);
/* fast_commit.c */
+#ifdef CONFIG_EXT4_FS_FAST_COMMIT
+static inline bool ext4_fc_inode_tracked(struct inode *inode)
+{
+ return !list_empty(&EXT4_I(inode)->i_fc_list);
+}
+
int ext4_fc_info_show(struct seq_file *seq, void *v);
void ext4_fc_init(struct super_block *sb, journal_t *journal);
void ext4_fc_init_inode(struct inode *inode);
@@ -3112,6 +3126,55 @@ void ext4_fc_destroy_dentry_cache(void);
int ext4_fc_record_regions(struct super_block *sb, int ino,
ext4_lblk_t lblk, ext4_fsblk_t pblk,
int len, int replay);
+#else
+static inline bool ext4_fc_inode_tracked(struct inode *inode)
+{
+ return false;
+}
+
+static inline void ext4_fc_init(struct super_block *sb, journal_t *journal) {}
+static inline void ext4_fc_init_inode(struct inode *inode) {}
+static inline void ext4_fc_track_range(handle_t *handle, struct inode *inode,
+ ext4_lblk_t start, ext4_lblk_t end) {}
+static inline void __ext4_fc_track_unlink(handle_t *handle, struct inode *inode,
+ struct dentry *dentry) {}
+static inline void __ext4_fc_track_link(handle_t *handle, struct inode *inode,
+ struct dentry *dentry) {}
+static inline void ext4_fc_track_unlink(handle_t *handle, struct dentry *dentry) {}
+static inline void ext4_fc_track_link(handle_t *handle, struct inode *inode,
+ struct dentry *dentry) {}
+static inline void __ext4_fc_track_create(handle_t *handle, struct inode *inode,
+ struct dentry *dentry) {}
+static inline void ext4_fc_track_create(handle_t *handle, struct dentry *dentry) {}
+static inline void ext4_fc_track_inode(handle_t *handle, struct inode *inode) {}
+static inline void ext4_fc_mark_ineligible(struct super_block *sb, int reason,
+ handle_t *handle) {}
+static inline void ext4_fc_del(struct inode *inode) {}
+static inline bool ext4_fc_replay_check_excluded(struct super_block *sb,
+ ext4_fsblk_t block)
+{
+ return false;
+}
+
+static inline void ext4_fc_replay_cleanup(struct super_block *sb) {}
+static inline int ext4_fc_commit(journal_t *journal, tid_t commit_tid)
+{
+ return jbd2_complete_transaction(journal, commit_tid);
+}
+
+static inline int ext4_fc_init_dentry_cache(void)
+{
+ return 0;
+}
+
+static inline void ext4_fc_destroy_dentry_cache(void) {}
+static inline int ext4_fc_record_regions(struct super_block *sb, int ino,
+ ext4_lblk_t lblk, ext4_fsblk_t pblk,
+ int len, int replay)
+{
+ return 0;
+}
+#endif
/* mballoc.c */
extern const struct seq_operations ext4_mb_seq_groups_ops;
diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c
index 134e6d8538dd..829095aedbe6 100644
--- a/fs/ext4/inode.c
+++ b/fs/ext4/inode.c
@@ -395,7 +395,7 @@ void ext4_evict_inode(struct inode *inode)
*/
WARN_ON_ONCE(!list_empty_careful(&inode->i_io_list));
- if (!list_empty(&EXT4_I(inode)->i_fc_list))
+ if (ext4_fc_inode_tracked(inode))
ext4_fc_mark_ineligible(inode->i_sb, EXT4_FC_REASON_NOMEM, NULL);
ext4_clear_inode(inode); /* We must guarantee clearing of inode... */
}
@@ -3587,7 +3587,7 @@ static bool ext4_inode_datasync_dirty(struct inode *inode)
EXT4_I(inode)->i_datasync_tid))
return false;
if (test_opt2(inode->i_sb, JOURNAL_FAST_COMMIT))
- return !list_empty(&EXT4_I(inode)->i_fc_list);
+ return ext4_fc_inode_tracked(inode);
return true;
}
diff --git a/fs/ext4/super.c b/fs/ext4/super.c
index 9cae52471dee..aced1a67a554 100644
--- a/fs/ext4/super.c
+++ b/fs/ext4/super.c
@@ -1457,7 +1457,9 @@ static struct inode *ext4_alloc_inode(struct super_block *sb)
ei->i_datasync_tid = 0;
INIT_WORK(&ei->i_rsv_conversion_work, ext4_end_io_rsv_work);
ext4_fc_init_inode(&ei->vfs_inode);
+#ifdef CONFIG_EXT4_FS_FAST_COMMIT
spin_lock_init(&ei->i_fc_lock);
+#endif
ei->i_metadata_bhs = NULL;
#ifdef CONFIG_LOCKDEP
lockdep_set_subclass(&ei->i_data_sem, I_DATA_SEM_NORMAL);
@@ -1480,7 +1482,7 @@ static void ext4_free_in_core_inode(struct inode *inode)
{
fscrypt_free_inode(inode);
kfree(ext4_i_metadata_bhs(inode));
- if (!list_empty(&(EXT4_I(inode)->i_fc_list))) {
+ if (ext4_fc_inode_tracked(inode)) {
pr_warn("%s: inode %llu still in fc list",
__func__, inode->i_ino);
}
@@ -1757,7 +1759,7 @@ enum {
Opt_no_prefetch_block_bitmaps, Opt_mb_optimize_scan,
Opt_buffered_iomap, Opt_nobuffered_iomap,
Opt_errors, Opt_data, Opt_data_err, Opt_jqfmt, Opt_dax_type,
-#ifdef CONFIG_EXT4_DEBUG
+#if defined(CONFIG_EXT4_DEBUG) && defined(CONFIG_EXT4_FS_FAST_COMMIT)
Opt_fc_debug_max_replay, Opt_fc_debug_force
#endif
};
@@ -1877,7 +1879,7 @@ static const struct fs_parameter_spec ext4_param_specs[] = {
fsparam_u32 ("init_itable", Opt_init_itable),
fsparam_flag ("init_itable", Opt_init_itable),
fsparam_flag ("noinit_itable", Opt_noinit_itable),
-#ifdef CONFIG_EXT4_DEBUG
+#if defined(CONFIG_EXT4_DEBUG) && defined(CONFIG_EXT4_FS_FAST_COMMIT)
fsparam_flag ("fc_debug_force", Opt_fc_debug_force),
fsparam_u32 ("fc_debug_max_replay", Opt_fc_debug_max_replay),
#endif
@@ -1993,7 +1995,7 @@ static const struct mount_opts {
MOPT_SET | MOPT_2 | MOPT_EXT4_ONLY},
{Opt_nobuffered_iomap, EXT4_MOUNT2_BUFFERED_IOMAP,
MOPT_CLEAR | MOPT_2 | MOPT_EXT4_ONLY},
-#ifdef CONFIG_EXT4_DEBUG
+#if defined(CONFIG_EXT4_DEBUG) && defined(CONFIG_EXT4_FS_FAST_COMMIT)
{Opt_fc_debug_force, EXT4_MOUNT2_JOURNAL_FAST_COMMIT,
MOPT_SET | MOPT_2 | MOPT_EXT4_ONLY},
#endif
@@ -2048,7 +2050,7 @@ struct ext4_fs_context {
char *s_qf_names[EXT4_MAXQUOTAS];
struct fscrypt_dummy_policy dummy_enc_policy;
int s_jquota_fmt; /* Format of quota to use */
-#ifdef CONFIG_EXT4_DEBUG
+#if defined(CONFIG_EXT4_DEBUG) && defined(CONFIG_EXT4_FS_FAST_COMMIT)
int s_fc_debug_max_replay;
#endif
unsigned short qname_spec;
@@ -2354,7 +2356,7 @@ static int ext4_parse_param(struct fs_context *fc, struct fs_parameter *param)
ctx->s_max_dir_size_kb = result.uint_32;
ctx->spec |= EXT4_SPEC_s_max_dir_size_kb;
return 0;
-#ifdef CONFIG_EXT4_DEBUG
+#if defined(CONFIG_EXT4_DEBUG) && defined(CONFIG_EXT4_FS_FAST_COMMIT)
case Opt_fc_debug_max_replay:
ctx->s_fc_debug_max_replay = result.uint_32;
ctx->spec |= EXT4_SPEC_s_fc_debug_max_replay;
@@ -2934,7 +2936,7 @@ static void ext4_apply_options(struct fs_context *fc, struct super_block *sb)
APPLY(s_resgid);
APPLY(s_resuid);
-#ifdef CONFIG_EXT4_DEBUG
+#if defined(CONFIG_EXT4_DEBUG) && defined(CONFIG_EXT4_FS_FAST_COMMIT)
APPLY(s_fc_debug_max_replay);
#endif
@@ -4462,7 +4464,8 @@ static void ext4_set_def_opts(struct super_block *sb,
#ifdef CONFIG_EXT4_FS_POSIX_ACL
set_opt(sb, POSIX_ACL);
#endif
- if (ext4_has_feature_fast_commit(sb))
+ if (IS_ENABLED(CONFIG_EXT4_FS_FAST_COMMIT) &&
+ ext4_has_feature_fast_commit(sb))
set_opt2(sb, JOURNAL_FAST_COMMIT);
/* don't forget to enable journal_csum when metadata_csum is enabled. */
if (ext4_has_feature_metadata_csum(sb))
@@ -4590,6 +4593,7 @@ static void ext4_atomic_write_init(struct super_block *sb)
static void ext4_fast_commit_init(struct super_block *sb)
{
+#ifdef CONFIG_EXT4_FS_FAST_COMMIT
struct ext4_sb_info *sbi = EXT4_SB(sb);
/* Initialize fast commit stuff */
@@ -4611,6 +4615,7 @@ static void ext4_fast_commit_init(struct super_block *sb)
sbi->s_fc_replay_state.fc_modified_inodes = NULL;
sbi->s_fc_replay_state.fc_modified_inodes_size = 0;
sbi->s_fc_replay_state.fc_modified_inodes_used = 0;
+#endif
}
static int ext4_inode_info_init(struct super_block *sb,
@@ -6196,6 +6201,15 @@ static int ext4_load_journal(struct super_block *sb,
return PTR_ERR(journal);
}
+ /* Never let JBD2 skip fast commit recovery without a replay callback. */
+ if (!IS_ENABLED(CONFIG_EXT4_FS_FAST_COMMIT) &&
+ jbd2_has_feature_fast_commit(journal)) {
+ ext4_msg(sb, KERN_ERR,
+ "journal requires CONFIG_EXT4_FS_FAST_COMMIT recovery support");
+ err = -EOPNOTSUPP;
+ goto err_out;
+ }
+
journal_dev_ro = bdev_read_only(journal->j_dev);
really_read_only = bdev_read_only(sb->s_bdev) | journal_dev_ro;
diff --git a/fs/ext4/sysfs.c b/fs/ext4/sysfs.c
index 923b375e017f..c0231706330c 100644
--- a/fs/ext4/sysfs.c
+++ b/fs/ext4/sysfs.c
@@ -361,7 +361,9 @@ EXT4_ATTR_FEATURE(casefold);
EXT4_ATTR_FEATURE(verity);
#endif
EXT4_ATTR_FEATURE(metadata_csum_seed);
+#ifdef CONFIG_EXT4_FS_FAST_COMMIT
EXT4_ATTR_FEATURE(fast_commit);
+#endif
#if IS_ENABLED(CONFIG_UNICODE) && defined(CONFIG_FS_ENCRYPTION)
EXT4_ATTR_FEATURE(encrypted_casefold);
#endif
@@ -384,7 +386,9 @@ static struct attribute *ext4_feat_attrs[] = {
ATTR_LIST(verity),
#endif
ATTR_LIST(metadata_csum_seed),
+#ifdef CONFIG_EXT4_FS_FAST_COMMIT
ATTR_LIST(fast_commit),
+#endif
#if IS_ENABLED(CONFIG_UNICODE) && defined(CONFIG_FS_ENCRYPTION)
ATTR_LIST(encrypted_casefold),
#endif
@@ -631,8 +635,10 @@ int ext4_register_sysfs(struct super_block *sb)
proc_create_single_data("es_shrinker_info", S_IRUGO,
sbi->s_proc, ext4_seq_es_shrinker_info_show,
sb);
+#ifdef CONFIG_EXT4_FS_FAST_COMMIT
proc_create_single_data("fc_info", 0444, sbi->s_proc,
ext4_fc_info_show, sb);
+#endif
proc_create_seq_data("mb_groups", S_IRUGO, sbi->s_proc,
&ext4_mb_seq_groups_ops, sb);
proc_create_single_data("mb_stats", 0444, sbi->s_proc,
diff --git a/include/trace/events/ext4.h b/include/trace/events/ext4.h
index 92048da9dae4..4049567cc4a0 100644
--- a/include/trace/events/ext4.h
+++ b/include/trace/events/ext4.h
@@ -2738,6 +2738,7 @@ TRACE_EVENT(ext4_lazy_itable_init,
MAJOR(__entry->dev), MINOR(__entry->dev), __entry->group)
);
+#ifdef CONFIG_EXT4_FS_FAST_COMMIT
TRACE_EVENT(ext4_fc_replay_scan,
TP_PROTO(struct super_block *sb, int error, int off),
@@ -3060,6 +3061,8 @@ TRACE_EVENT(ext4_fc_cleanup,
__entry->j_fc_off, __entry->full, __entry->tid)
);
+#endif /* CONFIG_EXT4_FS_FAST_COMMIT */
+
TRACE_EVENT(ext4_update_sb,
TP_PROTO(struct super_block *sb, ext4_fsblk_t fsblk,
unsigned int flags),
--
2.53.0
^ permalink raw reply [flat|nested] 4+ messages in thread* [PATCH 2/3] ext4: make bigalloc support optional
2026-11-11 12:52 [PATCH 0/3] ext4: reduce per-inode memory overhead Max Kellermann
2026-11-11 12:52 ` [PATCH 1/3] ext4: make fast commit support optional Max Kellermann
@ 2026-11-11 12:52 ` Max Kellermann
2026-11-11 12:52 ` [PATCH 3/3] ext4: store creation time in separate seconds and nanoseconds Max Kellermann
2 siblings, 0 replies; 4+ messages in thread
From: Max Kellermann @ 2026-11-11 12:52 UTC (permalink / raw)
To: tytso, adilger.kernel, libaokun, jack, ojaswin, ritesh.list,
yi.zhang, linux-ext4, linux-kernel
Cc: Max Kellermann
Add CONFIG_EXT4_FS_BIGALLOC, enabled by default. Disabling it omits
pending cluster reservation tracking and saves 8 bytes per inode.
Reject bigalloc filesystems before journal recovery, including on
read-only mounts.
Signed-off-by: Max Kellermann <max.kellermann@ionos.com>
---
Documentation/filesystems/ext4/bigalloc.rst | 6 +++
fs/ext4/Kconfig | 15 +++++++
fs/ext4/ext4.h | 2 +
fs/ext4/extents_status.c | 44 +++++++++++++++++++--
fs/ext4/extents_status.h | 15 +++++++
fs/ext4/super.c | 10 +++++
6 files changed, 89 insertions(+), 3 deletions(-)
diff --git a/Documentation/filesystems/ext4/bigalloc.rst b/Documentation/filesystems/ext4/bigalloc.rst
index 976a180b209c..51aee018ec6d 100644
--- a/Documentation/filesystems/ext4/bigalloc.rst
+++ b/Documentation/filesystems/ext4/bigalloc.rst
@@ -3,6 +3,12 @@
Bigalloc
--------
+Bigalloc support is enabled by default with ``CONFIG_EXT4_FS_BIGALLOC``.
+Disabling this option removes the per-inode pending cluster reservation
+tree and its tracking code. Such kernels reject filesystems with the
+``bigalloc`` feature, including read-only mounts, before journal recovery.
+Filesystems without this feature remain supported.
+
At the moment, the default size of a block is 4KiB, which is a commonly
supported page size on most MMU-capable hardware. This is fortunate, as
ext4 code is not prepared to handle the case where the block size
diff --git a/fs/ext4/Kconfig b/fs/ext4/Kconfig
index 0f3e702cd5e1..518bd68ed167 100644
--- a/fs/ext4/Kconfig
+++ b/fs/ext4/Kconfig
@@ -43,6 +43,21 @@ config EXT4_USE_FOR_EXT2
compiled kernel size by using one file system driver for
ext2, ext3, and ext4 file systems.
+config EXT4_FS_BIGALLOC
+ bool "Ext4 bigalloc support"
+ depends on EXT4_FS
+ default y
+ help
+ Support filesystems with the bigalloc feature, which allocate space
+ in clusters of multiple blocks to reduce allocation overhead for
+ large files.
+
+ Say N to omit pending cluster reservation tracking and reduce memory
+ usage per inode. Filesystems with the bigalloc feature cannot be
+ mounted, including read-only mounts, when this option is disabled.
+
+ If unsure, say Y.
+
config EXT4_FS_FAST_COMMIT
bool "Ext4 fast commit support"
depends on EXT4_FS
diff --git a/fs/ext4/ext4.h b/fs/ext4/ext4.h
index 1aedbfbcb101..c2c242f60700 100644
--- a/fs/ext4/ext4.h
+++ b/fs/ext4/ext4.h
@@ -1194,8 +1194,10 @@ struct ext4_inode_info {
/* ialloc */
ext4_group_t i_last_alloc_group;
+#ifdef CONFIG_EXT4_FS_BIGALLOC
/* pending cluster reservations for bigalloc file systems */
struct ext4_pending_tree i_pending_tree;
+#endif
/* on-disk additional length */
__u16 i_extra_isize;
diff --git a/fs/ext4/extents_status.c b/fs/ext4/extents_status.c
index 018b6146b9d8..68630b32720c 100644
--- a/fs/ext4/extents_status.c
+++ b/fs/ext4/extents_status.c
@@ -174,7 +174,9 @@
*/
static struct kmem_cache *ext4_es_cachep;
+#ifdef CONFIG_EXT4_FS_BIGALLOC
static struct kmem_cache *ext4_pending_cachep;
+#endif
static int __es_insert_extent(struct inode *inode, struct extent_status *newes,
struct extent_status *prealloc);
@@ -185,9 +187,18 @@ static int __es_remove_extent(struct inode *inode, ext4_lblk_t lblk,
static int es_reclaim_extents(struct ext4_inode_info *ei, int *nr_to_scan);
static int __es_shrink(struct ext4_sb_info *sbi, int nr_to_scan,
struct ext4_inode_info *locked_ei);
+#ifdef CONFIG_EXT4_FS_BIGALLOC
static int __revise_pending(struct inode *inode, ext4_lblk_t lblk,
ext4_lblk_t len,
struct pending_reservation **prealloc);
+#else
+static inline int __revise_pending(struct inode *inode, ext4_lblk_t lblk,
+ ext4_lblk_t len,
+ struct pending_reservation **prealloc)
+{
+ return 0;
+}
+#endif
int __init ext4_init_es(void)
{
@@ -506,6 +517,7 @@ static void ext4_es_list_del(struct inode *inode)
spin_unlock(&sbi->s_es_lock);
}
+#ifdef CONFIG_EXT4_FS_BIGALLOC
static inline struct pending_reservation *__alloc_pending(bool nofail)
{
if (!nofail)
@@ -518,6 +530,14 @@ static inline void __free_pending(struct pending_reservation *pr)
{
kmem_cache_free(ext4_pending_cachep, pr);
}
+#else
+static inline struct pending_reservation *__alloc_pending(bool nofail)
+{
+ return NULL;
+}
+
+static inline void __free_pending(struct pending_reservation *pr) {}
+#endif
/*
* Returns true if we cannot fail to allocate memory for this extent_status
@@ -934,7 +954,8 @@ void ext4_es_insert_extent(struct inode *inode, ext4_lblk_t lblk,
ext4_es_insert_extent_check(inode, &newes);
- revise_pending = sbi->s_cluster_ratio > 1 &&
+ revise_pending = IS_ENABLED(CONFIG_EXT4_FS_BIGALLOC) &&
+ sbi->s_cluster_ratio > 1 &&
test_opt(inode->i_sb, DELALLOC) &&
(status & (EXTENT_STATUS_WRITTEN |
EXTENT_STATUS_UNWRITTEN));
@@ -1197,7 +1218,7 @@ static void init_rsvd(struct inode *inode, ext4_lblk_t lblk,
* the region to be removed, if any, and note that there's no partial
* cluster to track
*/
- if (sbi->s_cluster_ratio > 1) {
+ if (IS_ENABLED(CONFIG_EXT4_FS_BIGALLOC) && sbi->s_cluster_ratio > 1) {
rc->first_do_lblk_found = false;
if (lblk > es->es_lblk) {
rc->left_es = es;
@@ -1235,7 +1256,7 @@ static void count_rsvd(struct inode *inode, ext4_lblk_t lblk, long len,
WARN_ON(len <= 0);
- if (sbi->s_cluster_ratio == 1) {
+ if (!IS_ENABLED(CONFIG_EXT4_FS_BIGALLOC) || sbi->s_cluster_ratio == 1) {
rc->ndelayed += (int) len;
return;
}
@@ -1296,6 +1317,7 @@ static void count_rsvd(struct inode *inode, ext4_lblk_t lblk, long len,
}
}
+#ifdef CONFIG_EXT4_FS_BIGALLOC
/*
* __pr_tree_search - search for a pending cluster reservation
*
@@ -1330,6 +1352,7 @@ static struct pending_reservation *__pr_tree_search(struct rb_root *root,
}
return NULL;
}
+#endif
/*
* get_rsvd - calculates and returns the number of cluster reservations to be
@@ -1350,6 +1373,7 @@ static unsigned int get_rsvd(struct inode *inode, ext4_lblk_t end,
struct extent_status *right_es,
struct rsvd_count *rc)
{
+#ifdef CONFIG_EXT4_FS_BIGALLOC
struct ext4_sb_info *sbi = EXT4_SB(inode->i_sb);
struct pending_reservation *pr;
struct ext4_pending_tree *tree = &EXT4_I(inode)->i_pending_tree;
@@ -1457,6 +1481,7 @@ static unsigned int get_rsvd(struct inode *inode, ext4_lblk_t end,
}
}
}
+#endif
return rc->ndelayed;
}
@@ -1996,6 +2021,7 @@ void ext4_clear_inode_es(struct inode *inode)
write_unlock(&ei->i_es_lock);
}
+#ifdef CONFIG_EXT4_FS_BIGALLOC
#ifdef ES_DEBUG__
static void ext4_print_pending_tree(struct inode *inode)
{
@@ -2188,6 +2214,16 @@ bool ext4_is_pending(struct inode *inode, ext4_lblk_t lblk)
return ret;
}
+#else
+#define ext4_print_pending_tree(inode)
+
+static inline int __insert_pending(struct inode *inode, ext4_lblk_t lblk,
+ struct pending_reservation **prealloc)
+{
+ return 0;
+}
+#endif
+
/*
* ext4_es_insert_delayed_extent - adds some delayed blocks to the extents
* status tree, adding a pending reservation
@@ -2297,6 +2333,7 @@ void ext4_es_insert_delayed_extent(struct inode *inode, ext4_lblk_t lblk,
return;
}
+#ifdef CONFIG_EXT4_FS_BIGALLOC
/*
* __revise_pending - makes, cancels, or leaves unchanged pending cluster
* reservations for a specified block range depending
@@ -2395,3 +2432,4 @@ static int __revise_pending(struct inode *inode, ext4_lblk_t lblk,
out:
return (ret < 0) ? ret : pendings;
}
+#endif /* CONFIG_EXT4_FS_BIGALLOC */
diff --git a/fs/ext4/extents_status.h b/fs/ext4/extents_status.h
index 01cddcaaa68e..67d5059061a4 100644
--- a/fs/ext4/extents_status.h
+++ b/fs/ext4/extents_status.h
@@ -242,11 +242,26 @@ extern void ext4_es_destroy_stats(struct ext4_sb_info *sbi);
extern int ext4_seq_es_shrinker_info_show(struct seq_file *seq, void *v);
+#ifdef CONFIG_EXT4_FS_BIGALLOC
extern int __init ext4_init_pending(void);
extern void ext4_exit_pending(void);
extern void ext4_init_pending_tree(struct ext4_pending_tree *tree);
extern void ext4_remove_pending(struct inode *inode, ext4_lblk_t lblk);
extern bool ext4_is_pending(struct inode *inode, ext4_lblk_t lblk);
+#else
+static inline int ext4_init_pending(void)
+{
+ return 0;
+}
+
+static inline void ext4_exit_pending(void) {}
+static inline void ext4_remove_pending(struct inode *inode, ext4_lblk_t lblk) {}
+static inline bool ext4_is_pending(struct inode *inode, ext4_lblk_t lblk)
+{
+ return false;
+}
+#endif
+
extern void ext4_es_insert_delayed_extent(struct inode *inode, ext4_lblk_t lblk,
ext4_lblk_t len, bool lclu_allocated,
bool end_allocated);
diff --git a/fs/ext4/super.c b/fs/ext4/super.c
index aced1a67a554..a319077fb3ec 100644
--- a/fs/ext4/super.c
+++ b/fs/ext4/super.c
@@ -1438,7 +1438,9 @@ static struct inode *ext4_alloc_inode(struct super_block *sb)
ei->i_es_seq = 0;
ei->i_reserved_data_blocks = 0;
spin_lock_init(&(ei->i_block_reservation_lock));
+#ifdef CONFIG_EXT4_FS_BIGALLOC
ext4_init_pending_tree(&ei->i_pending_tree);
+#endif
#ifdef CONFIG_QUOTA
ei->i_reserved_quota = 0;
memset(&ei->i_dquot, 0, sizeof(ei->i_dquot));
@@ -3698,6 +3700,14 @@ int ext4_feature_set_ok(struct super_block *sb, int readonly)
return 0;
}
+ /* Read-only mounts can still replay the journal. */
+ if (!IS_ENABLED(CONFIG_EXT4_FS_BIGALLOC) &&
+ ext4_has_feature_bigalloc(sb)) {
+ ext4_msg(sb, KERN_ERR,
+ "Filesystem with bigalloc feature cannot be mounted without CONFIG_EXT4_FS_BIGALLOC");
+ return 0;
+ }
+
if (readonly)
return 1;
--
2.53.0
^ permalink raw reply [flat|nested] 4+ messages in thread* [PATCH 3/3] ext4: store creation time in separate seconds and nanoseconds
2026-11-11 12:52 [PATCH 0/3] ext4: reduce per-inode memory overhead Max Kellermann
2026-11-11 12:52 ` [PATCH 1/3] ext4: make fast commit support optional Max Kellermann
2026-11-11 12:52 ` [PATCH 2/3] ext4: make bigalloc " Max Kellermann
@ 2026-11-11 12:52 ` Max Kellermann
2 siblings, 0 replies; 4+ messages in thread
From: Max Kellermann @ 2026-11-11 12:52 UTC (permalink / raw)
To: tytso, adilger.kernel, libaokun, jack, ojaswin, ritesh.list,
yi.zhang, linux-ext4, linux-kernel
Cc: Max Kellermann
Keep 64-bit seconds and 32-bit nanoseconds and place the latter next
to i_raw_lock. Preserve the existing on-disk encoding and timestamp
range while saving 8 bytes per inode without fast commit.
Signed-off-by: Max Kellermann <max.kellermann@ionos.com>
---
fs/ext4/ext4.h | 18 +++++++++++++-----
fs/ext4/ialloc.c | 3 ++-
fs/ext4/inode.c | 4 ++--
3 files changed, 17 insertions(+), 8 deletions(-)
diff --git a/fs/ext4/ext4.h b/fs/ext4/ext4.h
index c2c242f60700..9b6df2632fdd 100644
--- a/fs/ext4/ext4.h
+++ b/fs/ext4/ext4.h
@@ -930,9 +930,15 @@ do { \
EXT4_INODE_SET_XTIME_VAL(i_ctime, inode, raw_inode, inode_get_ctime(inode))
#define EXT4_EINODE_SET_XTIME(xtime, einode, raw_inode) \
+do { \
+ struct timespec64 ts = { \
+ .tv_sec = (einode)->xtime ## _sec, \
+ .tv_nsec = (einode)->xtime ## _nsec, \
+ }; \
if (EXT4_FITS_IN_INODE(raw_inode, einode, xtime)) \
EXT4_INODE_SET_XTIME_VAL(xtime, &((einode)->vfs_inode), \
- raw_inode, (einode)->xtime)
+ raw_inode, ts); \
+} while (0)
#define EXT4_INODE_GET_XTIME_VAL(xtime, inode, raw_inode) \
(EXT4_FITS_IN_INODE(raw_inode, EXT4_I(inode), xtime ## _extra) ? \
@@ -962,12 +968,13 @@ do { \
#define EXT4_EINODE_GET_XTIME(xtime, einode, raw_inode) \
do { \
+ struct timespec64 ts = { 0, 0 }; \
if (EXT4_FITS_IN_INODE(raw_inode, einode, xtime)) \
- (einode)->xtime = \
+ ts = \
EXT4_INODE_GET_XTIME_VAL(xtime, &(einode->vfs_inode), \
raw_inode); \
- else \
- (einode)->xtime = (struct timespec64){0, 0}; \
+ (einode)->xtime ## _sec = ts.tv_sec; \
+ (einode)->xtime ## _nsec = ts.tv_nsec; \
} while (0)
#define i_disk_version osd1.linux1.l_i_version
@@ -1123,6 +1130,7 @@ struct ext4_inode_info {
#endif
spinlock_t i_raw_lock; /* protects updates to the raw inode */
+ u32 i_crtime_nsec;
#ifdef CONFIG_EXT4_FS_FAST_COMMIT
/*
@@ -1168,7 +1176,7 @@ struct ext4_inode_info {
* File creation time. Its function is same as that of
* struct timespec64 i_{a,c,m}time in the generic inode.
*/
- struct timespec64 i_crtime;
+ time64_t i_crtime_sec;
/* mballoc */
atomic_t i_prealloc_active;
diff --git a/fs/ext4/ialloc.c b/fs/ext4/ialloc.c
index 527f1f703c3e..1d73dd35e000 100644
--- a/fs/ext4/ialloc.c
+++ b/fs/ext4/ialloc.c
@@ -1261,7 +1261,8 @@ struct inode *__ext4_new_inode(const struct mnt_idmap *idmap,
/* This is the optimal IO size (for stat), not the fs block size */
inode->i_blocks = 0;
simple_inode_init_ts(inode);
- ei->i_crtime = inode_get_mtime(inode);
+ ei->i_crtime_sec = inode_get_mtime_sec(inode);
+ ei->i_crtime_nsec = inode_get_mtime_nsec(inode);
memset(ei->i_data, 0, sizeof(ei->i_data));
ei->i_dir_start_lookup = 0;
diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c
index 829095aedbe6..5dc22f8747ff 100644
--- a/fs/ext4/inode.c
+++ b/fs/ext4/inode.c
@@ -7190,8 +7190,8 @@ int ext4_getattr(const struct mnt_idmap *idmap, const struct path *path,
if ((request_mask & STATX_BTIME) &&
EXT4_FITS_IN_INODE(raw_inode, ei, i_crtime)) {
stat->result_mask |= STATX_BTIME;
- stat->btime.tv_sec = ei->i_crtime.tv_sec;
- stat->btime.tv_nsec = ei->i_crtime.tv_nsec;
+ stat->btime.tv_sec = ei->i_crtime_sec;
+ stat->btime.tv_nsec = ei->i_crtime_nsec;
}
/* Return the DIO alignment restrictions if requested. */
--
2.53.0
^ permalink raw reply [flat|nested] 4+ messages in thread