|
@@ -382,6 +382,8 @@ static inline void prune_one_dentry(struct dentry * dentry)
|
|
|
/**
|
|
|
* prune_dcache - shrink the dcache
|
|
|
* @count: number of entries to try and free
|
|
|
+ * @sb: if given, ignore dentries for other superblocks
|
|
|
+ * which are being unmounted.
|
|
|
*
|
|
|
* Shrink the dcache. This is done when we need
|
|
|
* more memory, or simply when we need to unmount
|
|
@@ -392,16 +394,29 @@ static inline void prune_one_dentry(struct dentry * dentry)
|
|
|
* all the dentries are in use.
|
|
|
*/
|
|
|
|
|
|
-static void prune_dcache(int count)
|
|
|
+static void prune_dcache(int count, struct super_block *sb)
|
|
|
{
|
|
|
spin_lock(&dcache_lock);
|
|
|
for (; count ; count--) {
|
|
|
struct dentry *dentry;
|
|
|
struct list_head *tmp;
|
|
|
+ struct rw_semaphore *s_umount;
|
|
|
|
|
|
cond_resched_lock(&dcache_lock);
|
|
|
|
|
|
tmp = dentry_unused.prev;
|
|
|
+ if (unlikely(sb)) {
|
|
|
+ /* Try to find a dentry for this sb, but don't try
|
|
|
+ * too hard, if they aren't near the tail they will
|
|
|
+ * be moved down again soon
|
|
|
+ */
|
|
|
+ int skip = count;
|
|
|
+ while (skip && tmp != &dentry_unused &&
|
|
|
+ list_entry(tmp, struct dentry, d_lru)->d_sb != sb) {
|
|
|
+ skip--;
|
|
|
+ tmp = tmp->prev;
|
|
|
+ }
|
|
|
+ }
|
|
|
if (tmp == &dentry_unused)
|
|
|
break;
|
|
|
list_del_init(tmp);
|
|
@@ -427,7 +442,45 @@ static void prune_dcache(int count)
|
|
|
spin_unlock(&dentry->d_lock);
|
|
|
continue;
|
|
|
}
|
|
|
- prune_one_dentry(dentry);
|
|
|
+ /*
|
|
|
+ * If the dentry is not DCACHED_REFERENCED, it is time
|
|
|
+ * to remove it from the dcache, provided the super block is
|
|
|
+ * NULL (which means we are trying to reclaim memory)
|
|
|
+ * or this dentry belongs to the same super block that
|
|
|
+ * we want to shrink.
|
|
|
+ */
|
|
|
+ /*
|
|
|
+ * If this dentry is for "my" filesystem, then I can prune it
|
|
|
+ * without taking the s_umount lock (I already hold it).
|
|
|
+ */
|
|
|
+ if (sb && dentry->d_sb == sb) {
|
|
|
+ prune_one_dentry(dentry);
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ /*
|
|
|
+ * ...otherwise we need to be sure this filesystem isn't being
|
|
|
+ * unmounted, otherwise we could race with
|
|
|
+ * generic_shutdown_super(), and end up holding a reference to
|
|
|
+ * an inode while the filesystem is unmounted.
|
|
|
+ * So we try to get s_umount, and make sure s_root isn't NULL.
|
|
|
+ * (Take a local copy of s_umount to avoid a use-after-free of
|
|
|
+ * `dentry').
|
|
|
+ */
|
|
|
+ s_umount = &dentry->d_sb->s_umount;
|
|
|
+ if (down_read_trylock(s_umount)) {
|
|
|
+ if (dentry->d_sb->s_root != NULL) {
|
|
|
+ prune_one_dentry(dentry);
|
|
|
+ up_read(s_umount);
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ up_read(s_umount);
|
|
|
+ }
|
|
|
+ spin_unlock(&dentry->d_lock);
|
|
|
+ /* Cannot remove the first dentry, and it isn't appropriate
|
|
|
+ * to move it to the head of the list, so give up, and try
|
|
|
+ * later
|
|
|
+ */
|
|
|
+ break;
|
|
|
}
|
|
|
spin_unlock(&dcache_lock);
|
|
|
}
|
|
@@ -630,7 +683,7 @@ void shrink_dcache_parent(struct dentry * parent)
|
|
|
int found;
|
|
|
|
|
|
while ((found = select_parent(parent)) != 0)
|
|
|
- prune_dcache(found);
|
|
|
+ prune_dcache(found, parent->d_sb);
|
|
|
}
|
|
|
|
|
|
/**
|
|
@@ -643,9 +696,10 @@ void shrink_dcache_parent(struct dentry * parent)
|
|
|
* done under dcache_lock.
|
|
|
*
|
|
|
*/
|
|
|
-void shrink_dcache_anon(struct hlist_head *head)
|
|
|
+void shrink_dcache_anon(struct super_block *sb)
|
|
|
{
|
|
|
struct hlist_node *lp;
|
|
|
+ struct hlist_head *head = &sb->s_anon;
|
|
|
int found;
|
|
|
do {
|
|
|
found = 0;
|
|
@@ -668,7 +722,7 @@ void shrink_dcache_anon(struct hlist_head *head)
|
|
|
}
|
|
|
}
|
|
|
spin_unlock(&dcache_lock);
|
|
|
- prune_dcache(found);
|
|
|
+ prune_dcache(found, sb);
|
|
|
} while(found);
|
|
|
}
|
|
|
|
|
@@ -689,7 +743,7 @@ static int shrink_dcache_memory(int nr, gfp_t gfp_mask)
|
|
|
if (nr) {
|
|
|
if (!(gfp_mask & __GFP_FS))
|
|
|
return -1;
|
|
|
- prune_dcache(nr);
|
|
|
+ prune_dcache(nr, NULL);
|
|
|
}
|
|
|
return (dentry_stat.nr_unused / 100) * sysctl_vfs_cache_pressure;
|
|
|
}
|