dentry lookup now uses RCU based locking model, i.e adding/removing
dentry to/from dentry cache can happen cocurrently with lookup code.
Before, dcache_lock was used to protect the hashtable (dentry cache)
for lookup, adding and removing. Because lookup is the most common
operation, contention for the dcache_lock was very high. So optimizing
this common path is desired even the code is much harder to understand
after optimization.
dput
1. If dentry->d_count == 1, the caller is the last user. The
might_sleep() indicates that dput can't be called in interrupt context
when the caller is the last user.
This dentry may be the last user of the inode, kernel needs to flush out
the dirty pages in page cache of this inode, clean_inode needs to wait
before continuing on.
dput->d_kill->dentry_iput->iput->iput_final->generic_drop_inode->
generic_forget_inode->clean_inode
2. If this is the last user, we need to acquire the dcache_lock first
and then decrease the dentry->d_count to 0.
3. Then we need to acquire the per-dentry d_lock, we still need to test
the d_count of this dentry. Since the __d_lookup is not dcache_lock
protected now, it can find the dentry and increase the d_count before
dput gets the d_lock. If this is the case, dput just releases the lock
and returns, since the dentry is in use now.
4. d_delete is called to determine whether to directly remove the dentry
from the hashtable and free the dentry. Usually if a dentry becomes
unused (d_count == 0), it will still be kept in the dcache hashtable (it
will be moved to the per-sb lru list, if kernel decides to shrink the
dentry cache, it will try to free the unused dentries).
5. If this dentry is not in the hashtable (d_unhashed), just kill it
now. i.e for some network fs, it will cache some info in local cache,
these info need to be revalidated periodically. During lookup, if fs
provides d_revalidate operation, we need to call d_revalidate to
whether the dentry is valid. If the dentry is not valid at that time,
d_revalidate needs call d_drop to remove the dentry from dcache
hashtable.
6. If the dentry is still in the hashtable, just move it to the per-sb
LRU list for unused dentries. These unused dentries are still in the
dcache, later lookup can still find them. Only when the available memory
is low and kernel starts reclaiming memory from all kinds of caches,
including dcache, unused dentries will be freed.
If the dentry is not on the LRU list, move it to the per-sb LRU list and
set DCACHE_REFERENCED in d_flags. When freeing dentries, we will start
from the end of the LRU list, trying to free the ones which are not used
for a long time.
The correct way to maintain the time order in LRU list is
(which was implemented in 2.4):
When we find a dentry in __d_lookup and increase it d_count, we should
remove the dentry from the LRU list if it is still on it, and put the
dentry back to the LRU list if its d_count == 0 in dput.
But since the __d_lookup is not dcache_lock protected now, we can't
modify the LRU list in the lookup code.
So the time order seems not correct on the LRU list now, f.e if the last
user dput the dentry, the dentry will be put on the LRU list. Later if
it is touched by other users in __d_lookup, it will not be removed from
the LRU list (is it a big problem?)
To really free a dentry, you must:
1. spin_lock(&dcache_lock), spin_lock(&dentry->d_lock);
2. ensure dentry->d_count == 0
3. __d_drop(dentry)
set DCACHE_UNHASHED flag in dentry->d_flags and remove dentry from
dcache hashtable
4. d_kill -> d_free -> call_rcu -> d_callback
Since in __d_lookup, hash chain walking is not protected by
dcache_lock, __d_lookup can still find the dentry you are trying to
free. So you need to set the DCACHE_UNHASHED flag, __d_lookup will try
to acquire the dentry->d_lock and test this bit. It will not return
unhashed dentry to the caller.
And we can't free the dentry to the kmem_cache immediately, __d_lookup
may still point to the dentry. We must wait for a grace period past,
till there is no reference to the dentry, can we free the dentry to
the kmem_cache.
d_kill:
called with dcache_lock, dentry->d_lock held and will release these two
locks after finished. The dentry has already removed from the dcache
hashtable and the lru list (d_hash, d_lrun, done in __d_drop).
1. remove the dentry from its parent list (d_child)
2. dentry_iput
release the dentry's inode
a. remove from the inode.i_dentry list (d_alias), this needs the
dcache_lock
b. release d_lock, dcache_lock, since the iput() may sleep, also at
this point, no one can find the dentry. The dentry is unhashed, only
__d_lookup can hold a reference to this dentry just before this entry
is removed from the hashtable. But since DCACHE_UNHASHED is set,
__d_lookup will not return this dentry to the caller.
3. d_free: to free the dentry after a grace period
references:
1. Linux-2.6.32 fs/dcache.c, Documentation/filesystems/dentry-locking.txt
2.
阅读(2124) | 评论(0) | 转发(0) |