Commit dea3667b authored by Linus Torvalds's avatar Linus Torvalds

vfs: get rid of insane dentry hashing rules

The dentry hashing rules have been really quite complicated for a long
while, in odd ways.  That made functions like __d_drop() very fragile
and non-obvious.

In particular, whether a dentry was hashed or not was indicated with an
explicit DCACHE_UNHASHED bit.  That's despite the fact that the hash
abstraction that the dentries use actually have a 'is this entry hashed
or not' model (which is a simple test of the 'pprev' pointer).

The reason that was done is because we used the normal 'is this entry
unhashed' model to mark whether the dentry had _ever_ been hashed in the
dentry hash tables, and that logic goes back many years (commit
b3423415: "dcache: avoid RCU for never-hashed dentries").

That, in turn, meant that __d_drop had totally different unhashing logic
for the dentry hash table case and for the anonymous dcache case,
because in order to use the "is this dentry hashed" logic as a flag for
whether it had ever been on the RCU hash table, we had to unhash such a
dentry differently so that we'd never think that it wasn't 'unhashed'
and wouldn't be free'd correctly.

That's just insane.  It made the logic really hard to follow, when there
were two different kinds of "unhashed" states, and one of them (the one
that used "list_bl_unhashed()") really had nothing at all to do with
being unhashed per se, but with a very subtle lifetime rule instead.

So turn all of it around, and make it logical.

Instead of having a DENTRY_UNHASHED bit in d_flags to indicate whether
the dentry is on the hash chains or not, use the hash chain unhashed
logic for that.  Suddenly "d_unhashed()" just uses "list_bl_unhashed()",
and everything makes sense.

And for the lifetime rule, just use an explicit DENTRY_RCUACCEES bit.
If we ever insert the dentry into the dentry hash table so that it is
visible to RCU lookup, we mark it DENTRY_RCUACCESS to show that it now
needs the RCU lifetime rules.  Now suddently that test at dentry free
time makes sense too.

And because unhashing now is sane and doesn't depend on where the dentry
got unhashed from (because the dentry hash chain details doesn't have
some subtle side effects), we can re-unify the __d_drop() logic and use
common code for the unhashing.

Also fix one more open-coded hash chain bit_spin_lock() that I missed in
the previous chain locking cleanup commit.
Signed-off-by: default avatarLinus Torvalds <torvalds@linux-foundation.org>
parent b07ad996
...@@ -164,8 +164,8 @@ static void d_free(struct dentry *dentry) ...@@ -164,8 +164,8 @@ static void d_free(struct dentry *dentry)
if (dentry->d_op && dentry->d_op->d_release) if (dentry->d_op && dentry->d_op->d_release)
dentry->d_op->d_release(dentry); dentry->d_op->d_release(dentry);
/* if dentry was never inserted into hash, immediate free is OK */ /* if dentry was never visible to RCU, immediate free is OK */
if (hlist_bl_unhashed(&dentry->d_hash)) if (!(dentry->d_flags & DCACHE_RCUACCESS))
__d_free(&dentry->d_u.d_rcu); __d_free(&dentry->d_u.d_rcu);
else else
call_rcu(&dentry->d_u.d_rcu, __d_free); call_rcu(&dentry->d_u.d_rcu, __d_free);
...@@ -327,29 +327,20 @@ static struct dentry *d_kill(struct dentry *dentry, struct dentry *parent) ...@@ -327,29 +327,20 @@ static struct dentry *d_kill(struct dentry *dentry, struct dentry *parent)
*/ */
void __d_drop(struct dentry *dentry) void __d_drop(struct dentry *dentry)
{ {
if (!(dentry->d_flags & DCACHE_UNHASHED)) { if (!d_unhashed(dentry)) {
struct hlist_bl_head *b; struct hlist_bl_head *b;
if (unlikely(dentry->d_flags & DCACHE_DISCONNECTED)) { if (unlikely(dentry->d_flags & DCACHE_DISCONNECTED))
b = &dentry->d_sb->s_anon; b = &dentry->d_sb->s_anon;
spin_lock_bucket(b); else
dentry->d_flags |= DCACHE_UNHASHED;
hlist_bl_del_init(&dentry->d_hash);
spin_unlock_bucket(b);
} else {
struct hlist_bl_head *b;
b = d_hash(dentry->d_parent, dentry->d_name.hash); b = d_hash(dentry->d_parent, dentry->d_name.hash);
spin_lock_bucket(b); spin_lock_bucket(b);
/* __hlist_bl_del(&dentry->d_hash);
* We may not actually need to put DCACHE_UNHASHED dentry->d_hash.pprev = NULL;
* manipulations under the hash lock, but follow
* the principle of least surprise.
*/
dentry->d_flags |= DCACHE_UNHASHED;
hlist_bl_del_rcu(&dentry->d_hash);
spin_unlock_bucket(b); spin_unlock_bucket(b);
dentry_rcuwalk_barrier(dentry); dentry_rcuwalk_barrier(dentry);
} }
}
} }
EXPORT_SYMBOL(__d_drop); EXPORT_SYMBOL(__d_drop);
...@@ -1301,7 +1292,7 @@ struct dentry *d_alloc(struct dentry * parent, const struct qstr *name) ...@@ -1301,7 +1292,7 @@ struct dentry *d_alloc(struct dentry * parent, const struct qstr *name)
dname[name->len] = 0; dname[name->len] = 0;
dentry->d_count = 1; dentry->d_count = 1;
dentry->d_flags = DCACHE_UNHASHED; dentry->d_flags = 0;
spin_lock_init(&dentry->d_lock); spin_lock_init(&dentry->d_lock);
seqcount_init(&dentry->d_seq); seqcount_init(&dentry->d_seq);
dentry->d_inode = NULL; dentry->d_inode = NULL;
...@@ -1603,10 +1594,9 @@ struct dentry *d_obtain_alias(struct inode *inode) ...@@ -1603,10 +1594,9 @@ struct dentry *d_obtain_alias(struct inode *inode)
tmp->d_inode = inode; tmp->d_inode = inode;
tmp->d_flags |= DCACHE_DISCONNECTED; tmp->d_flags |= DCACHE_DISCONNECTED;
list_add(&tmp->d_alias, &inode->i_dentry); list_add(&tmp->d_alias, &inode->i_dentry);
bit_spin_lock(0, (unsigned long *)&tmp->d_sb->s_anon.first); spin_lock_bucket(&tmp->d_sb->s_anon);
tmp->d_flags &= ~DCACHE_UNHASHED;
hlist_bl_add_head(&tmp->d_hash, &tmp->d_sb->s_anon); hlist_bl_add_head(&tmp->d_hash, &tmp->d_sb->s_anon);
__bit_spin_unlock(0, (unsigned long *)&tmp->d_sb->s_anon.first); spin_unlock_bucket(&tmp->d_sb->s_anon);
spin_unlock(&tmp->d_lock); spin_unlock(&tmp->d_lock);
spin_unlock(&inode->i_lock); spin_unlock(&inode->i_lock);
security_d_instantiate(tmp, inode); security_d_instantiate(tmp, inode);
...@@ -2087,7 +2077,7 @@ static void __d_rehash(struct dentry * entry, struct hlist_bl_head *b) ...@@ -2087,7 +2077,7 @@ static void __d_rehash(struct dentry * entry, struct hlist_bl_head *b)
{ {
BUG_ON(!d_unhashed(entry)); BUG_ON(!d_unhashed(entry));
spin_lock_bucket(b); spin_lock_bucket(b);
entry->d_flags &= ~DCACHE_UNHASHED; entry->d_flags |= DCACHE_RCUACCESS;
hlist_bl_add_head_rcu(&entry->d_hash, b); hlist_bl_add_head_rcu(&entry->d_hash, b);
spin_unlock_bucket(b); spin_unlock_bucket(b);
} }
......
...@@ -197,7 +197,7 @@ struct dentry_operations { ...@@ -197,7 +197,7 @@ struct dentry_operations {
* typically using d_splice_alias. */ * typically using d_splice_alias. */
#define DCACHE_REFERENCED 0x0008 /* Recently used, don't discard. */ #define DCACHE_REFERENCED 0x0008 /* Recently used, don't discard. */
#define DCACHE_UNHASHED 0x0010 #define DCACHE_RCUACCESS 0x0010 /* Entry has ever been RCU-visible */
#define DCACHE_INOTIFY_PARENT_WATCHED 0x0020 #define DCACHE_INOTIFY_PARENT_WATCHED 0x0020
/* Parent inode is watched by inotify */ /* Parent inode is watched by inotify */
...@@ -384,7 +384,7 @@ extern struct dentry *dget_parent(struct dentry *dentry); ...@@ -384,7 +384,7 @@ extern struct dentry *dget_parent(struct dentry *dentry);
static inline int d_unhashed(struct dentry *dentry) static inline int d_unhashed(struct dentry *dentry)
{ {
return (dentry->d_flags & DCACHE_UNHASHED); return hlist_bl_unhashed(&dentry->d_hash);
} }
static inline int d_unlinked(struct dentry *dentry) static inline int d_unlinked(struct dentry *dentry)
......
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