Commit 69ce2cbd authored by Andrew Morton's avatar Andrew Morton Committed by Jeff Garzik

[PATCH] Fix race between umount and inode pruning

Patch from Hugh Dickins <hugh@veritas.com>

When prune_icache coincides with unmounting, invalidate_inodes notices
the inode it's working on as busy but doesn't wait: Self-destruct in 5
seconds message, and later iput oopses on freed super_block.

Neither end is a fast path, so the patch just adds iprune_sem for exclusion.

The semaphore is held across dispose_list so that
dispose_list->clear_inode->destroy_inode cannot reference a destroyed
superblock.
parent 1c9bd3e4
...@@ -80,6 +80,16 @@ static LIST_HEAD(anon_hash_chain); /* for inodes with NULL i_sb */ ...@@ -80,6 +80,16 @@ static LIST_HEAD(anon_hash_chain); /* for inodes with NULL i_sb */
*/ */
spinlock_t inode_lock = SPIN_LOCK_UNLOCKED; spinlock_t inode_lock = SPIN_LOCK_UNLOCKED;
/*
* iprune_sem provides exclusion between the kswapd or try_to_free_pages
* icache shrinking path, and the umount path. Without this exclusion,
* by the time prune_icache calls iput for the inode whose pages it has
* been invalidating, or by the time it calls clear_inode & destroy_inode
* from its final dispose_list, the struct super_block they refer to
* (for inode->i_sb->s_op) may already have been freed and reused.
*/
static DECLARE_MUTEX(iprune_sem);
/* /*
* Statistics gathering.. * Statistics gathering..
*/ */
...@@ -320,6 +330,7 @@ int invalidate_inodes(struct super_block * sb) ...@@ -320,6 +330,7 @@ int invalidate_inodes(struct super_block * sb)
int busy; int busy;
LIST_HEAD(throw_away); LIST_HEAD(throw_away);
down(&iprune_sem);
spin_lock(&inode_lock); spin_lock(&inode_lock);
busy = invalidate_list(&inode_in_use, sb, &throw_away); busy = invalidate_list(&inode_in_use, sb, &throw_away);
busy |= invalidate_list(&inode_unused, sb, &throw_away); busy |= invalidate_list(&inode_unused, sb, &throw_away);
...@@ -328,6 +339,7 @@ int invalidate_inodes(struct super_block * sb) ...@@ -328,6 +339,7 @@ int invalidate_inodes(struct super_block * sb)
spin_unlock(&inode_lock); spin_unlock(&inode_lock);
dispose_list(&throw_away); dispose_list(&throw_away);
up(&iprune_sem);
return busy; return busy;
} }
...@@ -395,6 +407,7 @@ static void prune_icache(int nr_to_scan) ...@@ -395,6 +407,7 @@ static void prune_icache(int nr_to_scan)
int nr_scanned; int nr_scanned;
unsigned long reap = 0; unsigned long reap = 0;
down(&iprune_sem);
spin_lock(&inode_lock); spin_lock(&inode_lock);
for (nr_scanned = 0; nr_scanned < nr_to_scan; nr_scanned++) { for (nr_scanned = 0; nr_scanned < nr_to_scan; nr_scanned++) {
struct inode *inode; struct inode *inode;
...@@ -429,7 +442,10 @@ static void prune_icache(int nr_to_scan) ...@@ -429,7 +442,10 @@ static void prune_icache(int nr_to_scan)
} }
inodes_stat.nr_unused -= nr_pruned; inodes_stat.nr_unused -= nr_pruned;
spin_unlock(&inode_lock); spin_unlock(&inode_lock);
dispose_list(&freeable); dispose_list(&freeable);
up(&iprune_sem);
if (current_is_kswapd) if (current_is_kswapd)
mod_page_state(kswapd_inodesteal, reap); mod_page_state(kswapd_inodesteal, reap);
else else
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment