diff options
| author | Linus Torvalds <torvalds@linux-foundation.org> | 2026-08-17 17:03:34 -0700 |
|---|---|---|
| committer | Linus Torvalds <torvalds@linux-foundation.org> | 2026-08-17 17:03:34 -0700 |
| commit | 4bb187d6f379c5490e3034b153fe476127308519 (patch) | |
| tree | 0c3091e0f3291a69a9a8261dd00821a5f3a7a7f8 | |
| parent | cf07e82984895a06a7cbfadee1b13d83805bb41b (diff) | |
| parent | 657e5af4fe0b9696a6c7232da1727a9f37eaa04d (diff) | |
Merge tag 'gfs2-for-7.3' of git://git.kernel.org/pub/scm/linux/kernel/git/gfs2/linux-gfs2
Pull gfs2 updates from Andreas Gruenbacher:
- Don't cache unreferenced glocks: when a glock is no longer referenced
(for example, because the inode it protects is evicted), it is now
released as soon as possible instead of leaving it around until
memory pressure or an unmount forces it out.
For some workloads, this saves a lot of memory and speeds up unmounts
significantly.
- Harden gfs2_glock_hold() by making sure the caller holds a reference
and fix a related race in checking for the liveliness of glocks
between gdlm_bast() and gfs2_glock_cb().
* tag 'gfs2-for-7.3' of git://git.kernel.org/pub/scm/linux/kernel/git/gfs2/linux-gfs2:
gfs2: harden gfs2_glock_hold
gfs2: Remove the glock lru list and shrinker
gfs2: Skip dlm unlocks earlier
gfs2: Don't cache unreferenced glocks
gfs2: Enable automatic glock hash table shrinking
| -rw-r--r-- | fs/gfs2/glock.c | 231 | ||||
| -rw-r--r-- | fs/gfs2/glock.h | 2 | ||||
| -rw-r--r-- | fs/gfs2/incore.h | 7 | ||||
| -rw-r--r-- | fs/gfs2/lock_dlm.c | 5 | ||||
| -rw-r--r-- | fs/gfs2/main.c | 1 | ||||
| -rw-r--r-- | fs/gfs2/ops_fstype.c | 5 | ||||
| -rw-r--r-- | fs/gfs2/super.c | 2 | ||||
| -rw-r--r-- | fs/gfs2/sys.c | 2 | ||||
| -rw-r--r-- | fs/gfs2/trace_gfs2.h | 1 |
9 files changed, 35 insertions, 221 deletions
diff --git a/fs/gfs2/glock.c b/fs/gfs2/glock.c index eaa2980051ed..d59ea71a84db 100644 --- a/fs/gfs2/glock.c +++ b/fs/gfs2/glock.c @@ -66,9 +66,6 @@ static void request_demote(struct gfs2_glock *gl, unsigned int state, unsigned long delay, bool remote); static struct dentry *gfs2_root; -static LIST_HEAD(lru_list); -static atomic_t lru_count = ATOMIC_INIT(0); -static DEFINE_SPINLOCK(lru_lock); #define GFS2_GL_HASH_SHIFT 15 #define GFS2_GL_HASH_SIZE BIT(GFS2_GL_HASH_SHIFT) @@ -78,6 +75,7 @@ static const struct rhashtable_params ht_parms = { .key_len = offsetofend(struct lm_lockname, ln_type), .key_offset = offsetof(struct gfs2_glock, gl_name), .head_offset = offsetof(struct gfs2_glock, gl_node), + .automatic_shrinking = true, }; static struct rhashtable gl_hash_table; @@ -157,9 +155,9 @@ void gfs2_glock_free(struct gfs2_glock *gl) { void gfs2_glock_free_later(struct gfs2_glock *gl) { struct gfs2_sbd *sdp = glock_sbd(gl); - spin_lock(&lru_lock); - list_add(&gl->gl_lru, &sdp->sd_dead_glocks); - spin_unlock(&lru_lock); + spin_lock(&sdp->sd_dead_lock); + list_add(&gl->gl_dead, &sdp->sd_dead_glocks); + spin_unlock(&sdp->sd_dead_lock); if (atomic_dec_and_test(&sdp->sd_glock_disposal)) wake_up(&sdp->sd_kill_wait); } @@ -171,8 +169,8 @@ static void gfs2_free_dead_glocks(struct gfs2_sbd *sdp) while(!list_empty(list)) { struct gfs2_glock *gl; - gl = list_first_entry(list, struct gfs2_glock, gl_lru); - list_del_init(&gl->gl_lru); + gl = list_first_entry(list, struct gfs2_glock, gl_dead); + list_del(&gl->gl_dead); __gfs2_glock_free(gl); } } @@ -185,35 +183,11 @@ static void gfs2_free_dead_glocks(struct gfs2_sbd *sdp) struct gfs2_glock *gfs2_glock_hold(struct gfs2_glock *gl) { - if (!lockref_get_not_dead(&gl->gl_lockref)) + if (!lockref_get_not_zero(&gl->gl_lockref)) GLOCK_BUG_ON(gl, 1); return gl; } -static void gfs2_glock_add_to_lru(struct gfs2_glock *gl) -{ - spin_lock(&lru_lock); - list_move_tail(&gl->gl_lru, &lru_list); - - if (!test_bit(GLF_LRU, &gl->gl_flags)) { - set_bit(GLF_LRU, &gl->gl_flags); - atomic_inc(&lru_count); - } - - spin_unlock(&lru_lock); -} - -static void gfs2_glock_remove_from_lru(struct gfs2_glock *gl) -{ - spin_lock(&lru_lock); - if (test_bit(GLF_LRU, &gl->gl_flags)) { - list_del_init(&gl->gl_lru); - atomic_dec(&lru_count); - clear_bit(GLF_LRU, &gl->gl_flags); - } - spin_unlock(&lru_lock); -} - /* * Enqueue the glock on the work queue. Passes one glock reference on to the * work queue. @@ -240,7 +214,6 @@ static void __gfs2_glock_put(struct gfs2_glock *gl) lockref_mark_dead(&gl->gl_lockref); spin_unlock(&gl->gl_lockref.lock); - gfs2_glock_remove_from_lru(gl); GLOCK_BUG_ON(gl, !list_empty(&gl->gl_holders)); if (mapping) { truncate_inode_pages_final(mapping); @@ -257,8 +230,8 @@ static bool __gfs2_glock_put_or_lock(struct gfs2_glock *gl) return true; GLOCK_BUG_ON(gl, gl->gl_lockref.count != 1); if (gl->gl_state != LM_ST_UNLOCKED) { - gl->gl_lockref.count--; - gfs2_glock_add_to_lru(gl); + request_demote(gl, LM_ST_UNLOCKED, 0, false); + gfs2_glock_queue_work(gl, 0); spin_unlock(&gl->gl_lockref.lock); return true; } @@ -982,16 +955,19 @@ static void delete_work_func(struct work_struct *work) static void glock_work_func(struct work_struct *work) { - unsigned long delay = 0; struct gfs2_glock *gl = container_of(work, struct gfs2_glock, gl_work.work); - unsigned int drop_refs = 1; + unsigned int drop_refs; + unsigned long delay; spin_lock(&gl->gl_lockref.lock); +again: + drop_refs = 1; if (test_bit(GLF_HAVE_REPLY, &gl->gl_flags)) { clear_bit(GLF_HAVE_REPLY, &gl->gl_flags); finish_xmote(gl, gl->gl_reply); drop_refs++; } + delay = 0; if (test_bit(GLF_PENDING_DEMOTE, &gl->gl_flags) && gl->gl_state != LM_ST_UNLOCKED && gl->gl_demote_state != LM_ST_EXCLUSIVE) { @@ -1019,11 +995,13 @@ static void glock_work_func(struct work_struct *work) GLOCK_BUG_ON(gl, gl->gl_lockref.count < drop_refs); gl->gl_lockref.count -= drop_refs; if (!gl->gl_lockref.count) { - if (gl->gl_state == LM_ST_UNLOCKED) { - __gfs2_glock_put(gl); - return; + if (gl->gl_state != LM_ST_UNLOCKED) { + gl->gl_lockref.count++; + request_demote(gl, LM_ST_UNLOCKED, 0, false); + goto again; } - gfs2_glock_add_to_lru(gl); + __gfs2_glock_put(gl); + return; } spin_unlock(&gl->gl_lockref.lock); } @@ -1059,8 +1037,6 @@ again: out: rcu_read_unlock(); finish_wait(wq, &wait.wait); - if (gl) - gfs2_glock_remove_from_lru(gl); return gl; } @@ -1793,7 +1769,9 @@ void gfs2_glock_cb(struct gfs2_glock *gl, unsigned int state) { unsigned long delay = 0; - gfs2_glock_hold(gl); + if (!lockref_get_not_dead(&gl->gl_lockref)) + return; + spin_lock(&gl->gl_lockref.lock); if (!list_empty(&gl->gl_holders) && glock_type(gl) == LM_TYPE_INODE) { @@ -1873,125 +1851,6 @@ void gfs2_glock_complete(struct gfs2_glock *gl, int ret) spin_unlock(&gl->gl_lockref.lock); } -static int glock_cmp(void *priv, const struct list_head *a, - const struct list_head *b) -{ - struct gfs2_glock *gla, *glb; - - gla = list_entry(a, struct gfs2_glock, gl_lru); - glb = list_entry(b, struct gfs2_glock, gl_lru); - - if (glock_number(gla) > glock_number(glb)) - return 1; - if (glock_number(gla) < glock_number(glb)) - return -1; - - return 0; -} - -static bool can_free_glock(struct gfs2_glock *gl) -{ - struct gfs2_sbd *sdp = glock_sbd(gl); - - return !test_bit(GLF_LOCK, &gl->gl_flags) && - !gl->gl_lockref.count && - (!test_bit(GLF_LFLUSH, &gl->gl_flags) || - test_bit(SDF_KILL, &sdp->sd_flags)); -} - -/** - * gfs2_dispose_glock_lru - Demote a list of glocks - * @list: The list to dispose of - * - * Disposing of glocks may involve disk accesses, so that here we sort - * the glocks by number (i.e. disk location of the inodes) so that if - * there are any such accesses, they'll be sent in order (mostly). - * - * Must be called under the lru_lock, but may drop and retake this - * lock. While the lru_lock is dropped, entries may vanish from the - * list, but no new entries will appear on the list (since it is - * private) - */ - -static unsigned long gfs2_dispose_glock_lru(struct list_head *list) -__releases(&lru_lock) -__acquires(&lru_lock) -{ - struct gfs2_glock *gl; - unsigned long freed = 0; - - list_sort(NULL, list, glock_cmp); - - while(!list_empty(list)) { - gl = list_first_entry(list, struct gfs2_glock, gl_lru); - if (!spin_trylock(&gl->gl_lockref.lock)) { -add_back_to_lru: - list_move(&gl->gl_lru, &lru_list); - continue; - } - if (!can_free_glock(gl)) { - spin_unlock(&gl->gl_lockref.lock); - goto add_back_to_lru; - } - list_del_init(&gl->gl_lru); - atomic_dec(&lru_count); - clear_bit(GLF_LRU, &gl->gl_flags); - freed++; - gl->gl_lockref.count++; - if (gl->gl_state != LM_ST_UNLOCKED) - request_demote(gl, LM_ST_UNLOCKED, 0, false); - gfs2_glock_queue_work(gl, 0); - spin_unlock(&gl->gl_lockref.lock); - cond_resched_lock(&lru_lock); - } - return freed; -} - -/** - * gfs2_scan_glock_lru - Scan the LRU looking for locks to demote - * @nr: The number of entries to scan - * - * This function selects the entries on the LRU which are able to - * be demoted, and then kicks off the process by calling - * gfs2_dispose_glock_lru() above. - */ - -static unsigned long gfs2_scan_glock_lru(unsigned long nr) -{ - struct gfs2_glock *gl, *next; - LIST_HEAD(dispose); - unsigned long freed = 0; - - spin_lock(&lru_lock); - list_for_each_entry_safe(gl, next, &lru_list, gl_lru) { - if (!nr--) - break; - if (can_free_glock(gl)) - list_move(&gl->gl_lru, &dispose); - } - if (!list_empty(&dispose)) - freed = gfs2_dispose_glock_lru(&dispose); - spin_unlock(&lru_lock); - - return freed; -} - -static unsigned long gfs2_glock_shrink_scan(struct shrinker *shrink, - struct shrink_control *sc) -{ - if (!(sc->gfp_mask & __GFP_FS)) - return SHRINK_STOP; - return gfs2_scan_glock_lru(sc->nr_to_scan); -} - -static unsigned long gfs2_glock_shrink_count(struct shrinker *shrink, - struct shrink_control *sc) -{ - return vfs_pressure_ratio(atomic_read(&lru_count)); -} - -static struct shrinker *glock_shrinker; - /** * glock_hash_walk - Call a function for glock in a hash bucket * @examiner: the function @@ -2062,7 +1921,6 @@ static void thaw_glock(struct gfs2_glock *gl) if (!lockref_get_not_dead(&gl->gl_lockref)) return; - gfs2_glock_remove_from_lru(gl); spin_lock(&gl->gl_lockref.lock); set_bit(GLF_HAVE_REPLY, &gl->gl_flags); gfs2_glock_queue_work(gl, 0); @@ -2070,26 +1928,6 @@ static void thaw_glock(struct gfs2_glock *gl) } /** - * clear_glock - look at a glock and see if we can free it from glock cache - * @gl: the glock to look at - * - */ - -static void clear_glock(struct gfs2_glock *gl) -{ - gfs2_glock_remove_from_lru(gl); - - spin_lock(&gl->gl_lockref.lock); - if (!lockref_is_dead(&gl->gl_lockref)) { - gl->gl_lockref.count++; - if (gl->gl_state != LM_ST_UNLOCKED) - request_demote(gl, LM_ST_UNLOCKED, 0, false); - gfs2_glock_queue_work(gl, 0); - } - spin_unlock(&gl->gl_lockref.lock); -} - -/** * gfs2_glock_thaw - Thaw any frozen glocks * @sdp: The super block * @@ -2135,22 +1973,17 @@ void gfs2_withdraw_glocks(struct gfs2_sbd *sdp) } /** - * gfs2_gl_hash_clear - Empty out the glock hash table + * gfs2_wait_glocks - Wait for the remaining glocks to go away * @sdp: the filesystem * * Called when unmounting the filesystem. */ -void gfs2_gl_hash_clear(struct gfs2_sbd *sdp) +void gfs2_wait_glocks(struct gfs2_sbd *sdp) { unsigned long start = jiffies; bool timed_out = false; - set_bit(SDF_SKIP_DLM_UNLOCK, &sdp->sd_flags); - flush_workqueue(sdp->sd_glock_wq); - glock_hash_walk(clear_glock, sdp); - flush_workqueue(sdp->sd_glock_wq); - while (!timed_out) { wait_event_timeout(sdp->sd_kill_wait, !atomic_read(&sdp->sd_glock_disposal), @@ -2273,8 +2106,6 @@ static const char *gflags2str(char *buf, const struct gfs2_glock *gl) *p++ = 'F'; if (!list_empty(&gl->gl_holders)) *p++ = 'q'; - if (test_bit(GLF_LRU, gflags)) - *p++ = 'L'; if (gl->gl_object) *p++ = 'o'; if (test_bit(GLF_BLOCKING, gflags)) @@ -2434,17 +2265,6 @@ int __init gfs2_glock_init(void) if (ret < 0) return ret; - glock_shrinker = shrinker_alloc(0, "gfs2-glock"); - if (!glock_shrinker) { - rhashtable_destroy(&gl_hash_table); - return -ENOMEM; - } - - glock_shrinker->count_objects = gfs2_glock_shrink_count; - glock_shrinker->scan_objects = gfs2_glock_shrink_scan; - - shrinker_register(glock_shrinker); - for (i = 0; i < GLOCK_WAIT_TABLE_SIZE; i++) init_waitqueue_head(glock_wait_table + i); @@ -2453,7 +2273,6 @@ int __init gfs2_glock_init(void) void gfs2_glock_exit(void) { - shrinker_free(glock_shrinker); rhashtable_destroy(&gl_hash_table); } diff --git a/fs/gfs2/glock.h b/fs/gfs2/glock.h index 6341ac9b863f..8b9e22befefb 100644 --- a/fs/gfs2/glock.h +++ b/fs/gfs2/glock.h @@ -263,7 +263,7 @@ bool gfs2_queue_try_to_evict(struct gfs2_glock *gl); bool gfs2_queue_verify_delete(struct gfs2_glock *gl, bool later); void gfs2_cancel_delete_work(struct gfs2_glock *gl); void gfs2_flush_delete_work(struct gfs2_sbd *sdp); -void gfs2_gl_hash_clear(struct gfs2_sbd *sdp); +void gfs2_wait_glocks(struct gfs2_sbd *sdp); void gfs2_withdraw_glocks(struct gfs2_sbd *sdp); void gfs2_glock_thaw(struct gfs2_sbd *sdp); void gfs2_glock_free(struct gfs2_glock *gl); diff --git a/fs/gfs2/incore.h b/fs/gfs2/incore.h index 61465777826a..dadb4d3c9d3d 100644 --- a/fs/gfs2/incore.h +++ b/fs/gfs2/incore.h @@ -321,7 +321,6 @@ enum { GLF_INITIAL = 10, GLF_HAVE_FROZEN_REPLY = 11, GLF_INSTANTIATE_IN_PROG = 12, /* instantiate happening now */ - GLF_LRU = 13, GLF_OBJECT = 14, /* Used only for tracing */ GLF_BLOCKING = 15, GLF_TRY_TO_EVICT = 17, /* iopen glocks only */ @@ -355,7 +354,7 @@ struct gfs2_glock { unsigned long gl_tchange; void *gl_object; - struct list_head gl_lru; + struct list_head gl_dead; struct list_head gl_ail_list; atomic_t gl_ail_count; atomic_t gl_revokes; @@ -601,7 +600,6 @@ enum { SDF_DEMOTE = 5, SDF_NOJOURNALID = 6, SDF_RORECOVERY = 7, /* read only recovery */ - SDF_SKIP_DLM_UNLOCK = 8, SDF_FORCE_AIL_FLUSH = 9, SDF_FREEZE_INITIATOR = 10, SDF_KILL = 15, @@ -834,6 +832,9 @@ struct gfs2_sbd { struct list_head sd_ail1_list; struct list_head sd_ail2_list; + /* glocks */ + spinlock_t sd_dead_lock; + /* For quiescing the filesystem */ struct gfs2_holder sd_freeze_gh; struct mutex sd_freeze_mutex; diff --git a/fs/gfs2/lock_dlm.c b/fs/gfs2/lock_dlm.c index cc901fb97da0..cef4bdee92a8 100644 --- a/fs/gfs2/lock_dlm.c +++ b/fs/gfs2/lock_dlm.c @@ -182,9 +182,6 @@ static void gdlm_bast(void *arg, int mode) { struct gfs2_glock *gl = arg; - if (lockref_is_dead(&gl->gl_lockref)) - return; - switch (mode) { case DLM_LOCK_EX: gfs2_glock_cb(gl, LM_ST_UNLOCKED); @@ -347,7 +344,7 @@ static void gdlm_put_lock(struct gfs2_glock *gl) * DLM_LOCK_PW mode, the lock value block (LVB) would be lost. */ - if (test_bit(SDF_SKIP_DLM_UNLOCK, &sdp->sd_flags) && + if (test_bit(SDF_KILL, &sdp->sd_flags) && (!gl->gl_lksb.sb_lvbptr || gl->gl_state != LM_ST_EXCLUSIVE)) { gfs2_glock_free_later(gl); return; diff --git a/fs/gfs2/main.c b/fs/gfs2/main.c index 9d65719353fa..36c9c06e91ac 100644 --- a/fs/gfs2/main.c +++ b/fs/gfs2/main.c @@ -52,7 +52,6 @@ static void gfs2_init_glock_once(void *foo) struct gfs2_glock *gl = foo; INIT_LIST_HEAD(&gl->gl_holders); - INIT_LIST_HEAD(&gl->gl_lru); INIT_LIST_HEAD(&gl->gl_ail_list); atomic_set(&gl->gl_ail_count, 0); atomic_set(&gl->gl_revokes, 0); diff --git a/fs/gfs2/ops_fstype.c b/fs/gfs2/ops_fstype.c index 9b9e70f14d25..718e0da7dfce 100644 --- a/fs/gfs2/ops_fstype.c +++ b/fs/gfs2/ops_fstype.c @@ -123,6 +123,8 @@ static struct gfs2_sbd *init_sbd(struct super_block *sb) INIT_LIST_HEAD(&sdp->sd_ail1_list); INIT_LIST_HEAD(&sdp->sd_ail2_list); + spin_lock_init(&sdp->sd_dead_lock); + init_rwsem(&sdp->sd_log_flush_lock); atomic_set(&sdp->sd_log_in_flight, 0); init_waitqueue_head(&sdp->sd_log_flush_wait); @@ -1300,7 +1302,6 @@ fail_locking: init_locking(sdp, &mount_gh, UNDO); fail_lm: complete_all(&sdp->sd_journal_ready); - gfs2_gl_hash_clear(sdp); gfs2_lm_unmount(sdp); fail_debug: gfs2_delete_debugfs_file(sdp); @@ -1783,6 +1784,7 @@ static void gfs2_kill_sb(struct super_block *sb) sdp->sd_master_dir = NULL; shrink_dcache_sb(sb); + set_bit(SDF_KILL, &sdp->sd_flags); gfs2_evict_inodes(sb); /* @@ -1790,7 +1792,6 @@ static void gfs2_kill_sb(struct super_block *sb) * destroy_workqueue()) to ensure that any delete work that * may be running will also see the SDF_KILL flag. */ - set_bit(SDF_KILL, &sdp->sd_flags); gfs2_flush_delete_work(sdp); destroy_workqueue(sdp->sd_delete_wq); diff --git a/fs/gfs2/super.c b/fs/gfs2/super.c index 4d854556b529..06302c29340f 100644 --- a/fs/gfs2/super.c +++ b/fs/gfs2/super.c @@ -638,7 +638,7 @@ restart: gfs2_clear_rgrpd(sdp); gfs2_jindex_free(sdp); /* Take apart glock structures and buffer lists */ - gfs2_gl_hash_clear(sdp); + gfs2_wait_glocks(sdp); iput(sdp->sd_inode); gfs2_delete_debugfs_file(sdp); diff --git a/fs/gfs2/sys.c b/fs/gfs2/sys.c index 7051db9dbea0..ea2c7b9e4a77 100644 --- a/fs/gfs2/sys.c +++ b/fs/gfs2/sys.c @@ -80,7 +80,6 @@ static ssize_t status_show(struct gfs2_sbd *sdp, char *buf) "No Journal ID: %d\n" "Mounted RO: %d\n" "RO Recovery: %d\n" - "Skip DLM Unlock: %d\n" "Force AIL Flush: %d\n" "FS Freeze Initiator: %d\n" "FS Frozen: %d\n" @@ -109,7 +108,6 @@ static ssize_t status_show(struct gfs2_sbd *sdp, char *buf) test_bit(SDF_NOJOURNALID, &f), (sb_rdonly(sdp->sd_vfs) ? 1 : 0), test_bit(SDF_RORECOVERY, &f), - test_bit(SDF_SKIP_DLM_UNLOCK, &f), test_bit(SDF_FORCE_AIL_FLUSH, &f), test_bit(SDF_FREEZE_INITIATOR, &f), test_bit(SDF_FROZEN, &f), diff --git a/fs/gfs2/trace_gfs2.h b/fs/gfs2/trace_gfs2.h index 6fd39fcdd00e..bc40320ef239 100644 --- a/fs/gfs2/trace_gfs2.h +++ b/fs/gfs2/trace_gfs2.h @@ -56,7 +56,6 @@ {(1UL << GLF_HAVE_REPLY), "r" }, \ {(1UL << GLF_INITIAL), "a" }, \ {(1UL << GLF_HAVE_FROZEN_REPLY), "F" }, \ - {(1UL << GLF_LRU), "L" }, \ {(1UL << GLF_OBJECT), "o" }, \ {(1UL << GLF_BLOCKING), "b" }, \ {(1UL << GLF_INSTANTIATE_NEEDED), "n" }, \ |
