vfs scalability patches updated
Subsystems (70)
cover vfs scalability patches updated
http://www.kernel.org/pub/linux/kernel/people/npiggin/patches/fs-scale/
Update to vfs scalability patches:
- Lots of fixes, particularly RCU inode stuff
- Lots of cleanups and aesthetic to the code, ifdef reduction etc
- Use bit locks for inode and dentry hashes
- Small improvements to single-threaded performance
- Split inode LRU and writeback list locking
- Per-bdi inode writeback list locking
- Per-zone mm shrinker
- Per-zone dentry and inode LRU lists
- Several fixes brought in from -rt tree testing
- No global locks remain in any fastpaths (arguably, rename)
I have not included the store-free path walk patches in this posting. They require a bit more work and they will need to be reworked after ->d_revalidate/->follow_mount changes that Al wants to do. I prefer to concentrate on these locking patches first.
Autofs4 is sadly missing. It's a bit tricky, patches have to be reworked.
Performance:
Last time I was testing on a 32-node Altix which could be considered as not a sweet-spot for Linux performance target (ie. improvements there may not justify complexity). So recently I've been testing with a tightly interconnected 4-socket Nehalem (4s/32c/64t). Linux needs to perform well on this size of system.
*** Single-thread microbenchmark (simple syscall loops, lower is better):
Test Difference at 95.0% confidence (50 runs) open/close -6.07% +/- 1.075% creat/unlink 27.83% +/- 0.522%
Open/close is a little faster, which should be due to one less atomic in the dput common case. Creat/unlink is significantly slower, which is due to RCU freeing inodes. We have have made the same magnitude of performance regression tradeoff when going to RCU freed dentries and files as well. Inode RCU is required for reducing inode hash lookup locking and improve lock ordering, also for store-free path-walk.
*** Let's take a look at this creat/unlink regression more closely. If we call rdtsc around the creat/unlink loop, and just run it once (so as to avoid much of the RCU induced problems):
vanilla: 5328 cycles
vfs: 5960 cycles (+11.8%)
Not so bad when RCU is not being stressed.
*** 64 parallel git diff on 64 kernel trees fully cached (avg of 5 runs):
vanilla vfs real 0m4.911s 0m0.183s user 0m1.920s 0m1.610s sys 4m58.670s 0m5.770s
After vfs patches, 26x increase in throughput, however parallelism is limited by test spawning and exit phases. sys time improvement shows closer to 50x improvement. vanilla is bottlenecked on dcache_lock.
*** Google sockets (http://marc.info/?l=linux-kernel&m=123215942507568&w=2):
vanilla vfs real 1m 7.774s 0m 3.245s user 0m19.230s 0m36.750s sys 71m41.310s 2m47.320s
do_exit path for
the run took 24.755s 1.219s
After vfs patches, 20x increase in throughput for both the total duration and the do_exit (teardown) time.
*** file-ops test (people.redhat.com/mingo/file-ops-test/file-ops-test.c) Parallel open/close or creat/unlink in same or different cwds within the same ramfs mount. Relative throughput percentages are given at each parallelism point (higher is better):
open/close vanilla vfs
same cwd
1 100.0 119.1
2 74.2 187.4 4 38.4 40.9 8 18.7 27.0 16 9.0 24.9 32 5.9 24.2 64 6.0 27.7 different cwd 1 100.0 119.1 2 133.0 238.6 4 21.2 488.6 8 19.7 932.6 16 18.8 1784.1 32 18.7 3469.5 64 19.0 2858.0
creat/unlink vanilla vfs
same cwd
1 100.0 75.0 2 44.1 41.8 4 28.7 24.6 8 16.5 14.2 16 8.7 8.9 32 5.5 7.8 64 5.9 7.4 different cwd 1 100.0 75.0 2 89.8 137.2 4 20.1 267.4 8 17.2 513.0 16 16.2 901.9 32 15.8 1724.0 64 17.3 1161.8
Note that at 64, we start using sibling threads on the CPU, making results jump around a bit. The drop at 64 in different-cwd cases seems to be hitting an RCU or slab allocator issue (or maybe it's just the SMT).
The scalability regression I was seeing in same-cwd tests is no longer there (is even improved now). It may still be present in some workloads doing common-element path lookups. This can be solved by making d_count atomic again, at the cost of more atomic ops in some cases, but scalability is still limited. So I prefer to do store-free path walking which is much more scalable.
In the different cwd open/close case, cost to bounce cachelines over the interconnect is putting absolute upper limit of 162K open/closes per second over the entire machine in vanilla kernel. After vfs patches, it is around 30M. On larger and less well connected machines, the lower limit will only get lower while the vfs case should continue to keep going up (assuming mm subsystem can keep up).
*** Reclaim
I have not done much reclaim testing yet. It should be more scalable and lower latency due to significant reduction in lru locks interfering with other critical sections in inode/dentry code, and because we have per-zone locks. Per-zone LRUs mean that reclaim is targetted to the correct zone, and that kswapd will operate on lists of node-local memory objects.
PATCH 1/52 kernel: add bl_list
Introduce a type of hlist that can support the use of the lowest bit in the hlist_head. This will be subsequently used to implement per-bucket bit spinlock for inode and dentry hashes.
--- include/linux/list_bl.h | 99 +++++++++++++++++++++++++++++++++++++ include/linux/rculist_bl.h | 120 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 219 insertions(+)
include/linux/list_bl.h new file +99 -0
@@ -0,0 +1,99 @@ +#ifndef _LINUX_LIST_BL_H +#define _LINUX_LIST_BL_H + +#include <linux/list.h> + +/* +* Special version of lists, where head of the list has a bit spinlock +* in the lowest bit. This is useful for scalable hash tables without +* increasing memory footprint overhead. +*/ + +struct hlist_bl_head { +struct hlist_bl_node *first; +}; + +struct hlist_bl_node { +struct hlist_bl_node *next, **pprev; +}; +#define INIT_HLIST_BL_HEAD (ptr) \ + ((ptr)->first = NULL) + +static inline void INIT_HLIST_BL_NODE (struct hlist_bl_node *h) +{ + h->next =NULL ; + h->pprev =NULL ; +} + +#define hlist_bl_entry (ptr, type, member) container_of(ptr,type,member) + +static inline int hlist_bl_unhashed (const struct hlist_bl_node *h) +{ +return !h->pprev; +} + +static inline struct hlist_bl_node *hlist_bl_first (struct hlist_bl_head *h) +{ +return (struct hlist_bl_node *)((unsigned long )h->first & ~1UL ); +} + +static inline void hlist_bl_set_first (struct hlist_bl_head *h,struct hlist_bl_node *n) +{ + h->first = (struct hlist_bl_node *)((unsigned long )n | ((unsigned long )h->first &1UL )); +} + +static inline int hlist_bl_empty (const struct hlist_bl_head *h) +{ +return !((unsigned long )h->first & ~1UL ); +} + +static inline void hlist_bl_add_head (struct hlist_bl_node *n, +struct hlist_bl_head *h) +{ +struct hlist_bl_node *first =hlist_bl_first (h); + + n->next = first; +if (first) + first->pprev = &n->next; + n->pprev = &h->first; +hlist_bl_set_first (h, n); +} + +static inline void __hlist_bl_del (struct hlist_bl_node *n) +{ +struct hlist_bl_node *next = n->next; +struct hlist_bl_node **pprev = n->pprev; + *pprev = (struct hlist_bl_node *)((unsigned long )next | ((unsigned long )*pprev &1UL )); +if (next) + next->pprev = pprev; +} + +static inline void hlist_bl_del (struct hlist_bl_node *n) +{ +__hlist_bl_del (n); + n->pprev =LIST_POISON2 ; +} + +static inline void hlist_bl_del_init (struct hlist_bl_node *n) +{ +if (!hlist_bl_unhashed (n)) { +__hlist_bl_del (n); +INIT_HLIST_BL_NODE (n); + } +} + +/** +* hlist_bl_for_each_entry - iterate over list of given type +* @tpos: the type * to use as a loop cursor. +* @pos: the &struct hlist_node to use as a loop cursor. +* @head: the head for your list. +* @member: the name of the hlist_node within the struct. +* +*/ +#define hlist_bl_for_each_entry (tpos, pos, head, member) \ + for (pos = hlist_bl_first(head); \ + pos && \ + ({ tpos = hlist_bl_entry(pos, typeof(*tpos), member); 1;}); \ + pos = pos->next) + +#endif
include/linux/rculist_bl.h new file +120 -0
120 lines not on this page · raw message
Re: [patch 01/52] kernel: add bl_list
Quoted 49 lines · Le jeudi 24 juin 2010 à 13:02 +1000, [email protected] a écrit :
pièce jointe document texte brut (list-bitlock.patch)
Introduce a type of hlist that can support the use of the lowest bit in thehlist_head. This will be subsequently used to implement per-bucket bit spinlock for inode and dentry hashes.Signed-off-by Nick Piggin <[email protected]>--- include/linux/list_bl.h | 99 +++++++++++++++++++++++++++++++++++++ include/linux/rculist_bl.h | 120 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 219 insertions(+)Index:
linux-2.6/include/linux/list_bl.h
===================================================================include/linux/list_bl.h new file +32
-0@@ -0,0 +1,99 @@ +#ifndef _LINUX_LIST_BL_H +#define _LINUX_LIST_BL_H + +#include <linux/list.h> + +/* +* Special version of lists, where head of the list has a bit spinlock +* in the lowest bit. This is useful for scalable hash tables without +* increasing memory footprint overhead. +*/ + +struct hlist_bl_head { +struct hlist_bl_node *first; +}; + +struct hlist_bl_node { +struct hlist_bl_node *next, **pprev; +}; +#define INIT_HLIST_BL_HEAD (ptr) \ + ((ptr)->first = NULL) + +static inline void INIT_HLIST_BL_NODE (struct hlist_bl_node *h) +{ + h->next =NULL ; + h->pprev =NULL ; +} + +#define hlist_bl_entry (ptr, type, member) container_of(ptr,type,member) + +static inline int hlist_bl_unhashed (const struct hlist_bl_node *h) +{ +return !h->pprev;
include/linux/list_bl.h line 42
Quoted 10 lines
include/linux/list_bl.h new file +10
-0+} + +static inline struct hlist_bl_node *hlist_bl_first (struct hlist_bl_head *h) +{ +return (struct hlist_bl_node *)((unsigned long )h->first & ~1UL ); +} + +static inline void hlist_bl_set_first (struct hlist_bl_head *h,struct hlist_bl_node *n) +{ + h->first = (struct hlist_bl_node *)((unsigned long )n | ((unsigned long )h->first &1UL ));
Hmm, shouldnt hlist_bl_set_first() be used only with bit lock held ?
h->first = (struct hlist_bl_node *)((unsigned long)n | 1UL);
include/linux/list_bl.h
Quoted 7 lines
include/linux/list_bl.h new file +6
-0+} + + +static inline void hlist_bl_set_first_rcu (struct hlist_bl_head *h,struct hlist_bl_node *n) +{ +rcu_assign_pointer (h->first, (struct hlist_bl_node *)((unsigned long )n | ((unsigned long )h->first &1UL )));
Same question here.
include/linux/list_bl.h
Quoted 6 lines
include/linux/list_bl.h new file +6
-0+} + +static inline struct hlist_bl_node *hlist_bl_first_rcu (struct hlist_bl_head *h) +{ +return (struct hlist_bl_node *)((unsigned long )rcu_dereference (h->first) & ~1UL ); +}
Looks really nice Nick, maybe we should push this so that other subsystem can start using it.
Thanks
Re: [patch 01/52] kernel: add bl_list
Quoted 9 lines · Eric Dumazet
include/linux/list_bl.h line 42
Quoted 3 lines · Le jeudi 24 juin 2010 à 13:02 +1000, [email protected] a écrit :
include/linux/list_bl.h new file +3
-0+static inline void hlist_bl_set_first (struct hlist_bl_head *h,struct hlist_bl_node *n) +{ + h->first = (struct hlist_bl_node *)((unsigned long )n | ((unsigned long )h->first &1UL ));Hmm, shouldnt
hlist_bl_set_first()be used only with bit lock held ?h->first = (struct hlist_bl_node *)((unsigned long)n | 1UL);Quoted 1 line
+}
I had it that way but changed it for some reason. Thinking about it again though, you're right I'm sure (it could have been some other bug in my code making me think I needed it).
Thanks.
Quoted 6 lines
include/linux/rculist_bl.h line 14
Quoted 3 lines
include/linux/rculist_bl.h new file +3
-0+static inline void hlist_bl_set_first_rcu (struct hlist_bl_head *h,struct hlist_bl_node *n) +{ +rcu_assign_pointer (h->first, (struct hlist_bl_node *)((unsigned long )n | ((unsigned long )h->first &1UL )));Same question here.
Quoted 10 lines
include/linux/rculist_bl.h line 20
Quoted 6 lines
include/linux/rculist_bl.h new file +6
-0+} + +static inline struct hlist_bl_node *hlist_bl_first_rcu (struct hlist_bl_head *h) +{ +return (struct hlist_bl_node *)((unsigned long )rcu_dereference (h->first) & ~1UL ); +}Looks really nice Nick, maybe we should push this so that other subsystem can start using it.
Sure, if you have an interest in using it, it will be trivial to send upstream. Should we merge it before any users appear? I don't know...
--
To unsubscribe from this list: send the line "unsubscribe linux-fsdevel" in the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html
Re: [patch 01/52] kernel: add bl_list
Quoted 3 lines · Nick Piggin
Sure, if you have an interest in using it, it will be trivial to send upstream. Should we merge it before any users appear? I don't know...
I'll work on using this for ip route cache, as you suggested.
BTW, I think we could care about this 0 bit only if it could possibly set, ie :
#if defined(CONFIG_SMP) || defined(CONFIG_DEBUG_SPINLOCK)
since bit_spin_lock() wont set it anyway if CONFIG_SMP=n and CONFIG_DEBUG_SPINLOCK=n
--
To unsubscribe from this list: send the line "unsubscribe linux-fsdevel" in the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html
Re: [patch 01/52] kernel: add bl_list
In reply to [email protected]Quoted 5 lines · On Thu, Jun 24, 2010 at 01:02:13PM +1000, [email protected] wrote:
Introduce a type of hlist that can support the use of the lowest bit in the
hlist_head. This will be subsequently used to implement per-bucket bit spinlock for inode and dentry hashes.Signed-off-by Nick Piggin <[email protected]>
Looks good! One question on non-RCU pointer poisoning and a typo. When these are addressed (perhaps by showing me the error of my ways):
Quoted 74 lines
--- include/linux/list_bl.h | 99 +++++++++++++++++++++++++++++++++++++ include/linux/rculist_bl.h | 120 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 219 insertions(+)Index:
linux-2.6/include/linux/list_bl.h
===================================================================include/linux/list_bl.h new file +64
-0@@ -0,0 +1,99 @@ +#ifndef _LINUX_LIST_BL_H +#define _LINUX_LIST_BL_H + +#include <linux/list.h> + +/* +* Special version of lists, where head of the list has a bit spinlock +* in the lowest bit. This is useful for scalable hash tables without +* increasing memory footprint overhead. +*/ + +struct hlist_bl_head { +struct hlist_bl_node *first; +}; + +struct hlist_bl_node { +struct hlist_bl_node *next, **pprev; +}; +#define INIT_HLIST_BL_HEAD (ptr) \ + ((ptr)->first = NULL) + +static inline void INIT_HLIST_BL_NODE (struct hlist_bl_node *h) +{ + h->next =NULL ; + h->pprev =NULL ; +} + +#define hlist_bl_entry (ptr, type, member) container_of(ptr,type,member) + +static inline int hlist_bl_unhashed (const struct hlist_bl_node *h) +{ +return !h->pprev; +} + +static inline struct hlist_bl_node *hlist_bl_first (struct hlist_bl_head *h) +{ +return (struct hlist_bl_node *)((unsigned long )h->first & ~1UL ); +} + +static inline void hlist_bl_set_first (struct hlist_bl_head *h,struct hlist_bl_node *n) +{ + h->first = (struct hlist_bl_node *)((unsigned long )n | ((unsigned long )h->first &1UL )); +} + +static inline int hlist_bl_empty (const struct hlist_bl_head *h) +{ +return !((unsigned long )h->first & ~1UL ); +} + +static inline void hlist_bl_add_head (struct hlist_bl_node *n, +struct hlist_bl_head *h) +{ +struct hlist_bl_node *first =hlist_bl_first (h); + + n->next = first; +if (first) + first->pprev = &n->next; + n->pprev = &h->first; +hlist_bl_set_first (h, n); +} + +static inline void __hlist_bl_del (struct hlist_bl_node *n) +{ +struct hlist_bl_node *next = n->next;
include/linux/list_bl.h line 74
Quoted 10 lines
include/linux/list_bl.h new file +10
-0+struct hlist_bl_node **pprev = n->pprev; + *pprev = (struct hlist_bl_node *)((unsigned long )next | ((unsigned long )*pprev &1UL )); +if (next) + next->pprev = pprev; +} + +static inline void hlist_bl_del (struct hlist_bl_node *n) +{ +__hlist_bl_del (n); + n->pprev =LIST_POISON2 ;
OK, I'll bite... Why don't we poison the ->next pointer?
Quoted 46 lines
include/linux/list_bl.h new file +25
-0+} + +static inline void hlist_bl_del_init (struct hlist_bl_node *n) +{ +if (!hlist_bl_unhashed (n)) { +__hlist_bl_del (n); +INIT_HLIST_BL_NODE (n); + } +} + +/** +* hlist_bl_for_each_entry - iterate over list of given type +* @tpos: the type * to use as a loop cursor. +* @pos: the &struct hlist_node to use as a loop cursor. +* @head: the head for your list. +* @member: the name of the hlist_node within the struct. +* +*/ +#define hlist_bl_for_each_entry (tpos, pos, head, member) \ + for (pos = hlist_bl_first(head); \ + pos && \ + ({ tpos = hlist_bl_entry(pos, typeof(*tpos), member); 1;}); \ + pos = pos->next) + +#endif Index:
linux-2.6/include/linux/rculist_bl.h
===================================================================include/linux/rculist_bl.h new file +16
-0@@ -0,0 +1,120 @@ +#ifndef _LINUX_RCULIST_BL_H +#define _LINUX_RCULIST_BL_H + +#ifdef __KERNEL__ + +/* +* RCU-protected list version +*/ +#include <linux/list_bl.h> +#include <linux/rcupdate.h> + +static inline void hlist_bl_set_first_rcu (struct hlist_bl_head *h,struct hlist_bl_node *n) +{ +rcu_assign_pointer (h->first, (struct hlist_bl_node *)((unsigned long )n | ((unsigned long )h->first &1UL ))); +} +
include/linux/rculist_bl.h line 26
Quoted 10 lines
include/linux/rculist_bl.h new file +10
-0+static inline struct hlist_bl_node *hlist_bl_first_rcu (struct hlist_bl_head *h) +{ +return (struct hlist_bl_node *)((unsigned long )rcu_dereference (h->first) & ~1UL ); +} + +/** +* hlist_bl_del_init_rcu - deletes entry from hash list with re-initialization +* @n: the element to delete from the hash list. +* +* Note: hlist_bl_unhashed() on the node return true after this. It is
returns
Quoted 101 lines
include/linux/rculist_bl.h new file +94
-1+* useful for RCU based read lockfree traversal if the writer side +* must know if the list entry is still hashed or already unhashed. +* +* In particular, it means that we can not poison the forward pointers +* that may still be used for walking the hash list and we can only +* zero the pprev pointer so list_unhashed() will return true after +* this. +* +* The caller must take whatever precautions are necessary (such as +* holding appropriate locks) to avoid racing with another +* list-mutation primitive, such as hlist_bl_add_head_rcu() or +* hlist_bl_del_rcu(), running on this same list. However, it is +* perfectly legal to run concurrently with the _rcu list-traversal +* primitives, such as hlist_bl_for_each_entry_rcu(). +*/ +static inline void hlist_bl_del_init_rcu (struct hlist_bl_node *n) +{ +if (!hlist_bl_unhashed (n)) { +__hlist_bl_del (n); + n->pprev =NULL ; + } +} + +/** +* hlist_bl_del_rcu - deletes entry from hash list without re-initialization +* @n: the element to delete from the hash list. +* +* Note: hlist_bl_unhashed() on entry does not return true after this, +* the entry is in an undefined state. It is useful for RCU based +* lockfree traversal. +* +* In particular, it means that we can not poison the forward +* pointers that may still be used for walking the hash list. +* +* The caller must take whatever precautions are necessary +* (such as holding appropriate locks) to avoid racing +* with another list-mutation primitive, such as hlist_bl_add_head_rcu() +* or hlist_bl_del_rcu(), running on this same list. +* However, it is perfectly legal to run concurrently with +* the _rcu list-traversal primitives, such as +* hlist_bl_for_each_entry(). +*/ +static inline void hlist_bl_del_rcu (struct hlist_bl_node *n) +{ +__hlist_bl_del (n); + n->pprev =LIST_POISON2 ; +} + +/** +* hlist_bl_add_head_rcu +* @n: the element to add to the hash list. +* @h: the list to add to. +* +* Description: +* Adds the specified element to the specified hlist_bl, +* while permitting racing traversals. +* +* The caller must take whatever precautions are necessary +* (such as holding appropriate locks) to avoid racing +* with another list-mutation primitive, such as hlist_bl_add_head_rcu() +* or hlist_bl_del_rcu(), running on this same list. +* However, it is perfectly legal to run concurrently with +* the _rcu list-traversal primitives, such as +* hlist_bl_for_each_entry_rcu(), used to prevent memory-consistency +* problems on Alpha CPUs. Regardless of the type of CPU, the +* list-traversal primitive must be guarded by rcu_read_lock(). +*/ +static inline void hlist_bl_add_head_rcu (struct hlist_bl_node *n, +struct hlist_bl_head *h) +{ +struct hlist_bl_node *first =hlist_bl_first (h); + + n->next = first; +if (first) + first->pprev = &n->next; + n->pprev = &h->first; +hlist_bl_set_first_rcu (h, n); +} +/** +* hlist_bl_for_each_entry_rcu - iterate over rcu list of given type +* @tpos: the type * to use as a loop cursor. +* @pos: the &struct hlist_bl_node to use as a loop cursor. +* @head: the head for your list. +* @member: the name of the hlist_bl_node within the struct. +* +*/ +#define hlist_bl_for_each_entry_rcu (tpos, pos, head, member) \ + for (pos = hlist_bl_first_rcu(head); \ + pos && \ + ({ tpos = hlist_bl_entry(pos, typeof(*tpos), member); 1; }); \ + pos = rcu_dereference_raw(pos->next)) + +#endif +#endif --To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/
Re: [patch 01/52] kernel: add bl_list
Quoted 12 lines · Paul E. McKenney
Quoted 5 lines · On Thu, Jun 24, 2010 at 01:02:13PM +1000, [email protected] wrote:
Introduce a type of hlist that can support the use of the lowest bit in the
hlist_head. This will be subsequently used to implement per-bucket bit spinlock for inode and dentry hashes.Signed-off-by Nick Piggin <[email protected]>Looks good! One question on non-RCU pointer poisoning and a typo. When these are addressed (perhaps by showing me the error of my ways):
Reviewed-by Paul E. McKenney <[email protected]>
..
Quoted 6 lines
include/linux/list_bl.h line 74
Quoted 4 lines
include/linux/list_bl.h new file +4
-0+static inline void hlist_bl_del (struct hlist_bl_node *n) +{ +__hlist_bl_del (n); + n->pprev =LIST_POISON2 ;OK, I'll bite... Why don't we poison the ->next pointer?
Ah, I took the code from list_nulls.h, but actually, except for the hlist anchoring, the code is much more similar to the standard hlist. This can be poisoned, and I'll go through and look for other possible differences with hlists.
Quoted 6 lines
Quoted 5 lines
+/**
+ *hlist_bl_del_init_rcu- deletes entry from hash list with re-initialization
+ * @n: the element to delete from the hash list.
+ *
+ * Note:hlist_bl_unhashed()on the node return true after this. It isreturns
Yes, thanks!
PATCH 2/52 fs: fix superblock iteration race
In reply to [email protected]list_for_each_entry_safe is not suitable to protect against concurrent modification of the list. 6754af6 introduced a race in sb walking.
list_for_each_entry can use the trick of pinning the current entry in the list before we drop and retake the lock because it subsequently follows cur->next. However list_for_each_entry_safe saves n=cur->next for following before entering the loop body, so when the lock is dropped, n may be deleted.
--- fs/dcache.c | 2 ++ fs/super.c | 6 ++++++ include/linux/list.h | 15 +++++++++++++++ 3 files changed, 23 insertions(+)
fs/dcache.c +2 -0
@@ -590,6 +590,8 @@ static void prune_dcache(int count)up_read (&sb->s_umount); }spin_lock (&sb_lock); +/* lock was dropped, must reset next */ +list_safe_reset_next (sb, n, s_list); count -= pruned;__put_super (sb);/* more work left to do? */
fs/super.c +6 -0
@@ -374,6 +374,8 @@ void sync_supers(void)up_read (&sb->s_umount);spin_lock (&sb_lock); +/* lock was dropped, must reset next */ +list_safe_reset_next (sb, n, s_list);__put_super (sb); } }
@@ -405,6 +407,8 @@ void iterate_supers(void (*f)(struct supup_read (&sb->s_umount);spin_lock (&sb_lock); +/* lock was dropped, must reset next */ +list_safe_reset_next (sb, n, s_list);__put_super (sb); }spin_unlock (&sb_lock);
@@ -585,6 +589,8 @@ static void do_emergency_remount(struct }up_write (&sb->s_umount);spin_lock (&sb_lock); +/* lock was dropped, must reset next */ +list_safe_reset_next (sb, n, s_list);__put_super (sb); }spin_unlock (&sb_lock);
include/linux/list.h +15 -0
@@ -544,6 +544,21 @@ static inline void list_splice_tail_init &pos->member != (head); \ pos = n, n = list_entry(n->member.prev, typeof(*n), member)) +/** +* list_safe_reset_next - reset a stale list_for_each_entry_safe loop +* @pos: the loop cursor used in the list_for_each_entry_safe loop +* @n: temporary storage used in list_for_each_entry_safe +* @member: the name of the list_struct within the struct. +* +* list_safe_reset_next is not safe to use in general if the list may be +* modified concurrently (eg. the lock is dropped in the loop body). An +* exception to this is if the cursor element (pos) is pinned in the list, +* and list_safe_reset_next is called after re-taking the lock and before +* completing the current iteration of the loop body. +*/ +#define list_safe_reset_next (pos, n, member) \ + n = list_entry(pos->member.next, typeof(*pos), member) +/* * Double linked lists with a single pointer list head. * Mostly useful for hash tables where the two pointer list head is
Re: [patch 02/52] fs: fix superblock iteration race
This should actually be on it's way to Linus for .35, shouldn't it?
Quoted 89 lines · On Thu, Jun 24, 2010 at 01:02:14PM +1000, [email protected] wrote:
list_for_each_entry_safeis not suitable to protect against concurrent modification of the list. 6754af6 introduced a race in sb walking.
list_for_each_entrycan use the trick of pinning the current entry in the list before we drop and retake the lock because it subsequently followscur->next. Howeverlist_for_each_entry_safesaves n=cur->next for following before entering the loop body, so when the lock is dropped, n may be deleted.Signed-off-by Nick Piggin <[email protected]>--- fs/dcache.c | 2 ++ fs/super.c | 6 ++++++ include/linux/list.h | 15 +++++++++++++++ 3 files changed, 23 insertions(+)Index:
linux-2.6/fs/dcache.c
===================================================================fs/dcache.c +2
-0@@ -590,6 +590,8 @@ static void prune_dcache(int count)up_read (&sb->s_umount); }spin_lock (&sb_lock); +/* lock was dropped, must reset next */ +list_safe_reset_next (sb, n, s_list); count -= pruned;__put_super (sb);/* more work left to do? */ Index:
linux-2.6/fs/super.c
===================================================================fs/super.c +6
-0@@ -374,6 +374,8 @@ void sync_supers(void)up_read (&sb->s_umount);spin_lock (&sb_lock); +/* lock was dropped, must reset next */ +list_safe_reset_next (sb, n, s_list);__put_super (sb); } }@@ -405,6 +407,8 @@ void iterate_supers(void (*f)(struct supup_read (&sb->s_umount);spin_lock (&sb_lock); +/* lock was dropped, must reset next */ +list_safe_reset_next (sb, n, s_list);__put_super (sb); }spin_unlock (&sb_lock);@@ -585,6 +589,8 @@ static void do_emergency_remount(struct }up_write (&sb->s_umount);spin_lock (&sb_lock); +/* lock was dropped, must reset next */ +list_safe_reset_next (sb, n, s_list);__put_super (sb); }spin_unlock (&sb_lock);Index:
linux-2.6/include/linux/list.h
===================================================================include/linux/list.h +15
-1@@ -544,6 +544,21 @@ static inline void list_splice_tail_init &pos->member != (head); \ pos = n, n = list_entry(n->member.prev, typeof(*n), member)) +/** +* list_safe_reset_next - reset a stale list_for_each_entry_safe loop +* @pos: the loop cursor used in the list_for_each_entry_safe loop +* @n: temporary storage used in list_for_each_entry_safe +* @member: the name of the list_struct within the struct. +* +* list_safe_reset_next is not safe to use in general if the list may be +* modified concurrently (eg. the lock is dropped in the loop body). An +* exception to this is if the cursor element (pos) is pinned in the list, +* and list_safe_reset_next is called after re-taking the lock and before +* completing the current iteration of the loop body. +*/ +#define list_safe_reset_next (pos, n, member) \ + n = list_entry(pos->member.next, typeof(*pos), member) +/* * Double linked lists with a single pointer list head. * Mostly useful for hash tables where the two pointer list head is --
Quoted 3 lines
To unsubscribe from this list: send the line "unsubscribe linux-fsdevel" in the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html
---end quoted text---
Re: [patch 02/52] fs: fix superblock iteration race
Quoted 1 line · Christoph Hellwig
This should actually be on it's way to Linus for .35, shouldn't it?
Yeah, I was waiting for Al to reappear, but I think this is probably the nicest way to solve the problem. Linus? --
fs: fix superblock iteration race
list_for_each_entry_safe is not suitable to protect against concurrent modification of the list. 6754af6 introduced a race in sb walking.
list_for_each_entry can use the trick of pinning the current entry in the list before we drop and retake the lock because it subsequently follows cur->next. However list_for_each_entry_safe saves n=cur->next for following before entering the loop body, so when the lock is dropped, n may be deleted.
--- fs/dcache.c | 2 ++ fs/super.c | 6 ++++++ include/linux/list.h | 15 +++++++++++++++ 3 files changed, 23 insertions(+)
fs/dcache.c +2 -0
@@ -590,6 +590,8 @@ static void prune_dcache(int count)up_read (&sb->s_umount); }spin_lock (&sb_lock); +/* lock was dropped, must reset next */ +list_safe_reset_next (sb, n, s_list); count -= pruned;__put_super (sb);/* more work left to do? */
fs/super.c +6 -0
@@ -374,6 +374,8 @@ void sync_supers(void)up_read (&sb->s_umount);spin_lock (&sb_lock); +/* lock was dropped, must reset next */ +list_safe_reset_next (sb, n, s_list);__put_super (sb); } }
@@ -405,6 +407,8 @@ void iterate_supers(void (*f)(struct supup_read (&sb->s_umount);spin_lock (&sb_lock); +/* lock was dropped, must reset next */ +list_safe_reset_next (sb, n, s_list);__put_super (sb); }spin_unlock (&sb_lock);
@@ -585,6 +589,8 @@ static void do_emergency_remount(struct }up_write (&sb->s_umount);spin_lock (&sb_lock); +/* lock was dropped, must reset next */ +list_safe_reset_next (sb, n, s_list);__put_super (sb); }spin_unlock (&sb_lock);
include/linux/list.h +15 -0
@@ -544,6 +544,21 @@ static inline void list_splice_tail_init &pos->member != (head); \ pos = n, n = list_entry(n->member.prev, typeof(*n), member)) +/** +* list_safe_reset_next - reset a stale list_for_each_entry_safe loop +* @pos: the loop cursor used in the list_for_each_entry_safe loop +* @n: temporary storage used in list_for_each_entry_safe +* @member: the name of the list_struct within the struct. +* +* list_safe_reset_next is not safe to use in general if the list may be +* modified concurrently (eg. the lock is dropped in the loop body). An +* exception to this is if the cursor element (pos) is pinned in the list, +* and list_safe_reset_next is called after re-taking the lock and before +* completing the current iteration of the loop body. +*/ +#define list_safe_reset_next (pos, n, member) \ + n = list_entry(pos->member.next, typeof(*pos), member) +/* * Double linked lists with a single pointer list head. * Mostly useful for hash tables where the two pointer list head is
Re: [patch 02/52] fs: fix superblock iteration race
Quoted 5 lines · Nick Piggin
Quoted 1 line · Christoph Hellwig
This should actually be on it's way to Linus for .35, shouldn't it?
Yeah, I was waiting for Al to reappear, but I think this is probably the nicest way to solve the problem. Linus?
I'll apply it. We have a couple of oopses listed for the superblock iterator, and I haven't heard from Al. And the patch looks obviously fine, whether it's actually the cause of some of the bugs or not.
Linus
Re: [patch 02/52] fs: fix superblock iteration race
Quoted 10 lines · Linus Torvalds
Quoted 5 lines · Nick Piggin
Quoted 1 line · Christoph Hellwig
This should actually be on it's way to Linus for .35, shouldn't it?
Yeah, I was waiting for Al to reappear, but I think this is probably the nicest way to solve the problem. Linus?
I'll apply it. We have a couple of oopses listed for the superblock iterator, and I haven't heard from Al. And the patch looks obviously fine, whether it's actually the cause of some of the bugs or not.
OK. I only have managed to get it into an infininte loop but I think it would be surely possible to oops it because the next pointer can be uninitialised memory at that point.
Re: [patch 02/52] fs: fix superblock iteration race
Quoted 9 lines · Nick Piggin
Quoted 4 lines · Linus Torvalds
I'll apply it. We have a couple of oopses listed for the superblock iterator, and I haven't heard from Al. And the patch looks obviously fine, whether it's actually the cause of some of the bugs or not.
OK. I only have managed to get it into an infininte loop but I think it would be surely possible to oops it because the next pointer can be uninitialised memory at that point.
Look for "2.6.35-rc3 oops trying to suspend" on lkml, for example. No guarantee that it's the same thing, but it's "iterate_supers()" getting an oops when it does "down_read(&sb->s_umount)". Which really looks suspiciously like "sb" just being totally bogus, most likely because of this same issue.
So I dunno, but I asked Al to look at it, and haven't heard back.
Regardless, I think your patch is the right thing to do (modulo any syntactic issues - and I think your final version was the best of the lot).
Linus
Re: [patch 02/52] fs: fix superblock iteration race
Quoted 4 lines · Linus Torvalds
Look for "2.6.35-rc3 oops trying to suspend" on lkml, for example. No guarantee that it's the same thing, but it's "
iterate_supers()" getting an oops [..]
Also, "Oops during closedown with 2.6.35-rc3-git3" is an iterate_supers oops (in the jpg) and Chris says it's repeatable for him.
Chris - you could try testing current -git now that I've merged Nick's patch. It's commit 57439f878af ("fs: fix superblock iteration race"), and I just pushed it out (so it might take a few minutes to mirror out to the public git trees, but it should be there shortly).
Linus
Re: [patch 02/52] fs: fix superblock iteration race
Quoted 7 lines · Linus Torvalds
On Tue, Jun 29, 2010 at 10:52 AM, Linus Torvalds
Quoted 3 lines · <[email protected]> wrote:
Look for "2.6.35-rc3 oops trying to suspend" on lkml, for example. No guarantee that it's the same thing, but it's "
iterate_supers()" getting an oops [..]
Quoted 9 lines
Also, "Oops during closedown with 2.6.35-rc3-git3" is an
iterate_supersoops (in the jpg) and Chris says it's repeatable for him.Chris - you could try testing current -git now that I've merged Nick's patch. It's commit
57439f878af("fs: fix superblock iteration race"), and I just pushed it out (so it might take a few minutes to mirror out to the public git trees, but it should be there shortly).
Well, it was repeatable this morning, but despite 30+ shutdowns this evening, I haven't had a single oops. Perhaps I'm just not doing enough computing stuff between startup and shutdown to create the conditions under which I got the oopses this morning. Moreover, I've been using this kernel every day with, maybe two or three shutdowns a day, for two weeks or so now (since just after Linus went on vacation) with no oopses. (The kernel is -rc3 + John Fastabend's "net: fix deliver_no_wcard regression on loopback device" patch).
I'll spend a couple of hours doing stuff and see if I can generate an oops. Trouble is, of course, that when I pull and build the latest and greatest, I won't know why I'm not getting oopses, assuming I don't.
I'll update y'all later.
Chris
Quoted 1 line
Linus
Signature · 2 lines
The more I see, the more I know. The more I know, the less I understand. Changing Man - Paul Weller
Re: [patch 02/52] fs: fix superblock iteration race
Quoted 26 lines · Chris Clayton
Quoted 7 lines · Linus Torvalds
On Tue, Jun 29, 2010 at 10:52 AM, Linus Torvalds
Quoted 3 lines · <[email protected]> wrote:
Look for "2.6.35-rc3 oops trying to suspend" on lkml, for example. No guarantee that it's the same thing, but it's "
iterate_supers()" getting an oops [..]Quoted 9 lines
Also, "Oops during closedown with 2.6.35-rc3-git3" is an
iterate_supersoops (in the jpg) and Chris says it's repeatable for him.Chris - you could try testing current -git now that I've merged Nick's patch. It's commit
57439f878af("fs: fix superblock iteration race"), and I just pushed it out (so it might take a few minutes to mirror out to the public git trees, but it should be there shortly).Well, it was repeatable this morning, but despite 30+ shutdowns this evening, I haven't had a single oops. Perhaps I'm just not doing enough computing stuff between startup and shutdown to create the conditions under which I got the oopses this morning. Moreover, I've been using this kernel every day with, maybe two or three shutdowns a day, for two weeks or so now (since just after Linus went on vacation) with no oopses. (The kernel is -rc3 + John Fastabend's "net: fix
deliver_no_wcardregression on loopback device" patch).
Quoted 3 lines
I'll spend a couple of hours doing stuff and see if I can generate an oops. Trouble is, of course, that when I pull and build the latest and greatest, I won't know why I'm not getting oopses, assuming I don't.
Don't worry too much if you can't reproduce. I'd say it is likely to be this bug, and if so, it is going to depend significantly on timing and ordering of mounts/umounts.
Re: [patch 02/52] fs: fix superblock iteration race
Quoted 27 lines · Nick Piggin
Quoted 26 lines · Chris Clayton
Quoted 7 lines · Linus Torvalds
On Tue, Jun 29, 2010 at 10:52 AM, Linus Torvalds
Quoted 3 lines · <[email protected]> wrote:
Look for "2.6.35-rc3 oops trying to suspend" on lkml, for example. No guarantee that it's the same thing, but it's "
iterate_supers()" getting an oops [..]Quoted 8 lines
Also, "Oops during closedown with 2.6.35-rc3-git3" is an
iterate_supersoops (in the jpg) and Chris says it's repeatable for him.Chris - you could try testing current -git now that I've merged Nick's patch. It's commit
57439f878af("fs: fix superblock iteration race"), and I just pushed it out (so it might take a few minutes to mirror out to the public git trees, but it should be there shortly).Well, it was repeatable this morning, but despite 30+ shutdowns this evening, I haven't had a single oops. Perhaps I'm just not doing enough computing stuff between startup and shutdown to create the conditions under which I got the oopses this morning. Moreover, I've been using this kernel every day with, maybe two or three shutdowns a day, for two weeks or so now (since just after Linus went on vacation) with no oopses. (The kernel is -rc3 + John Fastabend's "net: fix
deliver_no_wcardregression on loopback device" patch).
Quoted 7 lines
Quoted 3 lines
I'll spend a couple of hours doing stuff and see if I can generate an oops. Trouble is, of course, that when I pull and build the latest and greatest, I won't know why I'm not getting oopses, assuming I don't.
Don't worry too much if you can't reproduce. I'd say it is likely to be this bug, and if so, it is going to depend significantly on timing and ordering of mounts/umounts.
Yes, that makes sense. I did quite a bit of copying files to and from USB storage devices this morning. I've got some more to do, so I'll see what that produces.
Signature · 2 lines
The more I see, the more I know. The more I know, the less I understand. Changing Man - Paul Weller
Re: [patch 02/52] fs: fix superblock iteration race
Quoted 8 lines · Chris Clayton
Quoted 7 lines · Nick Piggin
Quoted 4 lines · Chris Clayton
I'll spend a couple of hours doing stuff and see if I can generate an oops. Trouble is, of course, that when I pull and build the latest and greatest, I won't know why I'm not getting oopses, assuming I don't.
Quoted 7 lines
Quoted 3 lines
Don't worry too much if you can't reproduce. I'd say it is likely to be this bug, and if so, it is going to depend significantly on timing and ordering of mounts/umounts.
Yes, that makes sense. I did quite a bit of copying files to and from USB storage devices this morning. I've got some more to do, so I'll see what that produces.
Well, try as I may, these oopses are like London buses - firstly you get three in quick succession when you don't need them and then when you do, none turn up. Oh well!
Chris
Signature · 2 lines
The more I see, the more I know. The more I know, the less I understand. Changing Man - Paul Weller
Al Viro
maintainerRe: [patch 02/52] fs: fix superblock iteration race
In reply to Linus TorvaldsQuoted 7 lines · Linus Torvalds
Quoted 4 lines · Linus Torvalds
Look for "2.6.35-rc3 oops trying to suspend" on lkml, for example. No guarantee that it's the same thing, but it's "
iterate_supers()" getting an oops [..]
Quoted 8 lines
Also, "Oops during closedown with 2.6.35-rc3-git3" is an
iterate_supersoops (in the jpg) and Chris says it's repeatable for him.Chris - you could try testing current -git now that I've merged Nick's patch. It's commit
57439f878af("fs: fix superblock iteration race"), and I just pushed it out (so it might take a few minutes to mirror out to the public git trees, but it should be there shortly).
ACK. I've several cleanups on top of that, but that's not a .35 fodder. I'll put them into for-next tonight. But yes, the hole caught by Nick is definitely real and very likely to be responsible for all that pile of reports. Mea culpa.
PATCH 3/52 fs: fs_struct rwlock to spinlock
In reply to [email protected]struct fs_struct.lock is an rwlock with the read-side used to protect root and pwd members while taking references to them. Taking a reference to a path typically requires just 2 atomic ops, so the critical section is very small. Parallel read-side operations would have cacheline contention on the lock, the dentry, and the vfsmount cachelines, so the rwlock is unlikely to ever give a real parallelism increase.
Replace it with a spinlock to avoid one or two atomic operations in typical path lookup fastpath.
--- drivers/staging/pohmelfs/path_entry.c | 8 +++---- fs/cachefiles/daemon.c | 8 +++---- fs/dcache.c | 8 +++---- fs/exec.c | 4 +-- fs/fs_struct.c | 36 +++++++++++++++++----------------- fs/namei.c | 8 +++---- fs/namespace.c | 4 +-- fs/proc/base.c | 4 +-- include/linux/fs_struct.h | 2 - kernel/auditsc.c | 4 +-- kernel/fork.c | 10 ++++----- 11 files changed, 48 insertions(+), 48 deletions(-)
fs/dcache.c +4 -4
@@ -2012,10 +2012,10 @@ char *d_path(const struct path *path, chif (path->dentry->d_op && path->dentry->d_op->d_dname)return path->dentry->d_op->d_dname (path->dentry, buf, buflen); -read_lock (¤t->fs->lock); +spin_lock (¤t->fs->lock); root = current->fs->root;path_get (&root); -read_unlock (¤t->fs->lock); +spin_unlock (¤t->fs->lock);spin_lock (&dcache_lock); tmp = root; res =__d_path (path, &tmp, buf, buflen);
@@ -2110,12 +2110,12 @@ SYSCALL_DEFINE2(getcwd, char __user *, bif (!page)return -ENOMEM ; -read_lock (¤t->fs->lock); +spin_lock (¤t->fs->lock); pwd = current->fs->pwd;path_get (&pwd); root = current->fs->root;path_get (&root); -read_unlock (¤t->fs->lock); +spin_unlock (¤t->fs->lock); error = -ENOENT ;spin_lock (&dcache_lock);
fs/fs_struct.c +18 -18
@@ -13,11 +13,11 @@ void set_fs_root(struct fs_struct *fs, s {struct path old_root; -write_lock (&fs->lock); +spin_lock (&fs->lock); old_root = fs->root; fs->root = *path;path_get (path); -write_unlock (&fs->lock); +spin_unlock (&fs->lock);if (old_root.dentry)path_put (&old_root); }
@@ -30,11 +30,11 @@ void set_fs_pwd(struct fs_struct *fs, st {struct path old_pwd; -write_lock (&fs->lock); +spin_lock (&fs->lock); old_pwd = fs->pwd; fs->pwd = *path;path_get (path); -write_unlock (&fs->lock); +spin_unlock (&fs->lock);if (old_pwd.dentry)path_put (&old_pwd);
@@ -51,7 +51,7 @@ void chroot_fs_refs(struct path *old_rootask_lock (p); fs = p->fs;if (fs) { -write_lock (&fs->lock); +spin_lock (&fs->lock);if (fs->root.dentry == old_root->dentry && fs->root.mnt == old_root->mnt) {path_get (new_root);
@@ -64,7 +64,7 @@ void chroot_fs_refs(struct path *old_roo fs->pwd = *new_root; count++; } -write_unlock (&fs->lock); +spin_unlock (&fs->lock); }task_unlock (p); }while_each_thread (g, p);
@@ -87,10 +87,10 @@ void exit_fs(struct task_struct *tsk)if (fs) {int kill;task_lock (tsk); -write_lock (&fs->lock); +spin_lock (&fs->lock); tsk->fs =NULL ; kill = !--fs->users; -write_unlock (&fs->lock); +spin_unlock (&fs->lock);task_unlock (tsk);if (kill)free_fs_struct (fs);
@@ -104,14 +104,14 @@ struct fs_struct *copy_fs_struct(structif (fs) { fs->users =1 ; fs->in_exec =0 ; -rwlock_init (&fs->lock); +spin_lock_init (&fs->lock); fs->umask = old->umask; -read_lock (&old->lock); +spin_lock (&old->lock); fs->root = old->root;path_get (&old->root); fs->pwd = old->pwd;path_get (&old->pwd); -read_unlock (&old->lock); +spin_unlock (&old->lock); }return fs; }
@@ -126,10 +126,10 @@ int unshare_fs_struct(void)return -ENOMEM ;task_lock (current); -write_lock (&fs->lock); +spin_lock (&fs->lock); kill = !--fs->users; current->fs = new_fs; -write_unlock (&fs->lock); +spin_unlock (&fs->lock);task_unlock (current);if (kill)
@@ -148,7 +148,7 @@ EXPORT_SYMBOL(current_umask);/* to be mentioned only in INIT_TASK */ struct fs_struct init_fs = { .users =1 , - .lock =__RW_LOCK_UNLOCKED (init_fs.lock), + .lock =__SPIN_LOCK_UNLOCKED (init_fs.lock), .umask =0022 , };
@@ -161,14 +161,14 @@ void daemonize_fs_struct(void)task_lock (current); -write_lock (&init_fs.lock); +spin_lock (&init_fs.lock); init_fs.users++; -write_unlock (&init_fs.lock); +spin_unlock (&init_fs.lock); -write_lock (&fs->lock); +spin_lock (&fs->lock); current->fs = &init_fs; kill = !--fs->users; -write_unlock (&fs->lock); +spin_unlock (&fs->lock);task_unlock (current);if (kill)
fs/namei.c +4 -4
@@ -486,10 +486,10 @@ static __always_inline void set_root(str {if (!nd->root.mnt) {struct fs_struct *fs = current->fs; -read_lock (&fs->lock); +spin_lock (&fs->lock); nd->root = fs->root;path_get (&nd->root); -read_unlock (&fs->lock); +spin_unlock (&fs->lock); } }
@@ -1017,10 +1017,10 @@ static int path_init(int dfd, const charpath_get (&nd->root); }else if (dfd ==AT_FDCWD ) {struct fs_struct *fs = current->fs; -read_lock (&fs->lock); +spin_lock (&fs->lock); nd->path = fs->pwd;path_get (&fs->pwd); -read_unlock (&fs->lock); +spin_unlock (&fs->lock); }else {struct dentry *dentry;
fs/namespace.c +2 -2
@@ -2208,10 +2208,10 @@ SYSCALL_DEFINE2(pivot_root, const char _ goto out1; } -read_lock (¤t->fs->lock); +spin_lock (¤t->fs->lock); root = current->fs->root;path_get (¤t->fs->root); -read_unlock (¤t->fs->lock); +spin_unlock (¤t->fs->lock);down_write (&namespace_sem);mutex_lock (&old.dentry->d_inode->i_mutex); error = -EINVAL ;
include/linux/fs_struct.h +1 -1
@@ -5,7 +5,7 @@struct fs_struct {int users; -rwlock_t lock; +spinlock_t lock;int umask;int in_exec;struct path root, pwd;
fs/exec.c +2 -2
@@ -1117,7 +1117,7 @@ int check_unsafe_exec(struct linux_binpr bprm->unsafe =tracehook_unsafe_exec (p); n_fs =1 ; -write_lock (&p->fs->lock); +spin_lock (&p->fs->lock);rcu_read_lock ();for (t =next_thread (p); t != p; t =next_thread (t)) {if (t->fs == p->fs)
@@ -1134,7 +1134,7 @@ int check_unsafe_exec(struct linux_binpr res =1 ; } } -write_unlock (&p->fs->lock); +spin_unlock (&p->fs->lock);return res; }
fs/proc/base.c +2 -2
@@ -156,10 +156,10 @@ static int get_fs_path(struct task_structask_lock (task); fs = task->fs;if (fs) { -read_lock (&fs->lock); +spin_lock (&fs->lock); *path = root ? fs->root : fs->pwd;path_get (path); -read_unlock (&fs->lock); +spin_unlock (&fs->lock); result =0 ; }task_unlock (task);
kernel/fork.c +5 -5
@@ -752,13 +752,13 @@ static int copy_fs(unsigned long clone_fstruct fs_struct *fs = current->fs;if (clone_flags &CLONE_FS ) {/* tsk->fs is already what we want */ -write_lock (&fs->lock); +spin_lock (&fs->lock);if (fs->in_exec) { -write_unlock (&fs->lock); +spin_unlock (&fs->lock);return -EAGAIN ; } fs->users++; -write_unlock (&fs->lock); +spin_unlock (&fs->lock);return 0 ; } tsk->fs =copy_fs_struct (fs);
@@ -1675,13 +1675,13 @@ SYSCALL_DEFINE1(unshare, unsigned long,if (new_fs) { fs = current->fs; -write_lock (&fs->lock); +spin_lock (&fs->lock); current->fs = new_fs;if (--fs->users) new_fs =NULL ;else new_fs = fs; -write_unlock (&fs->lock); +spin_unlock (&fs->lock); }if (new_mm) {
drivers/staging/pohmelfs/path_entry.c +4 -4
@@ -44,9 +44,9 @@ int pohmelfs_construct_path_string(strucreturn -ENOENT ; } -read_lock (¤t->fs->lock); +spin_lock (¤t->fs->lock); path.mnt =mntget (current->fs->root.mnt); -read_unlock (¤t->fs->lock); +spin_unlock (¤t->fs->lock); path.dentry = d;
@@ -91,9 +91,9 @@ int pohmelfs_path_length(struct pohmelfsreturn -ENOENT ; } -read_lock (¤t->fs->lock); +spin_lock (¤t->fs->lock); root =dget (current->fs->root.dentry); -read_unlock (¤t->fs->lock); +spin_unlock (¤t->fs->lock);spin_lock (&dcache_lock);
fs/cachefiles/daemon.c +4 -4
@@ -574,9 +574,9 @@ static int cachefiles_daemon_cull(struct/* extract the directory dentry from the cwd */ fs = current->fs; -read_lock (&fs->lock); +spin_lock (&fs->lock); dir =dget (fs->pwd.dentry); -read_unlock (&fs->lock); +spin_unlock (&fs->lock);if (!S_ISDIR (dir->d_inode->i_mode)) goto notdir;
@@ -650,9 +650,9 @@ static int cachefiles_daemon_inuse(struc/* extract the directory dentry from the cwd */ fs = current->fs; -read_lock (&fs->lock); +spin_lock (&fs->lock); dir =dget (fs->pwd.dentry); -read_unlock (&fs->lock); +spin_unlock (&fs->lock);if (!S_ISDIR (dir->d_inode->i_mode)) goto notdir;
kernel/auditsc.c +2 -2
@@ -1838,10 +1838,10 @@ void __audit_getname(const char *name) context->names[context->name_count].osid =0 ; ++context->name_count;if (!context->pwd.dentry) { -read_lock (¤t->fs->lock); +spin_lock (¤t->fs->lock); context->pwd = current->fs->pwd;path_get (¤t->fs->pwd); -read_unlock (¤t->fs->lock); +spin_unlock (¤t->fs->lock); } }
PATCH 4/52 fs: cleanup files_lock
In reply to [email protected]Lock tty_files with a new spinlock, tty_files_lock; provide helpers to manipulate the per-sb files list; unexport the files_lock spinlock.
--- drivers/char/pty.c | 6 +++++- drivers/char/tty_io.c | 26 ++++++++++++++++++-------- fs/file_table.c | 42 ++++++++++++++++++------------------------ fs/open.c | 4 ++-- include/linux/fs.h | 7 ++----- include/linux/tty.h | 1 + security/selinux/hooks.c | 4 ++-- 7 files changed, 48 insertions(+), 42 deletions(-)
security/selinux/hooks.c +2 -2
@@ -2219,7 +2219,7 @@ static inline void flush_unauthorized_fi tty =get_current_tty ();if (tty) { -file_list_lock (); +spin_lock (&tty_files_lock);if (!list_empty (&tty->tty_files)) {struct inode *inode;
@@ -2235,7 +2235,7 @@ static inline void flush_unauthorized_fi drop_tty =1 ; } } -file_list_unlock (); +spin_unlock (&tty_files_lock);tty_kref_put (tty); }/* Reset controlling tty. */
drivers/char/pty.c +5 -1
@@ -650,7 +650,11 @@ static int __ptmx_open(struct inode *inoset_bit (TTY_PTY_LOCK , &tty->flags);/* LOCK THE SLAVE */ filp->private_data = tty; -file_move (filp, &tty->tty_files); + +file_sb_list_del (filp);/* __dentry_open has put it on the sb list */ +spin_lock (&tty_files_lock); +list_add (&filp->f_u.fu_list, &tty->tty_files); +spin_unlock (&tty_files_lock); retval =devpts_pty_new (inode, tty->link);if (retval)
drivers/char/tty_io.c +18 -8
@@ -136,6 +136,9 @@ LIST_HEAD(tty_drivers); /* linked listDEFINE_MUTEX (tty_mutex);EXPORT_SYMBOL (tty_mutex); +/* Spinlock to protect the tty->tty_files list */ +DEFINE_SPINLOCK (tty_files_lock); +static ssize_t tty_read (struct file *,char __user *,size_t ,loff_t *);static ssize_t tty_write (struct file *,const char __user *,size_t ,loff_t *); ssize_t redirected_tty_write(struct file *, const char __user *,
@@ -234,11 +237,11 @@ static int check_tty_count(struct tty_ststruct list_head *p;int count =0 ; -file_list_lock (); +spin_lock (&tty_files_lock);list_for_each (p, &tty->tty_files) { count++; } -file_list_unlock (); +spin_unlock (&tty_files_lock);if (tty->driver->type ==TTY_DRIVER_TYPE_PTY && tty->driver->subtype ==PTY_TYPE_SLAVE && tty->link && tty->link->count)
@@ -517,7 +520,7 @@ static void do_tty_hangup(struct work_stlock_kernel ();check_tty_count (tty,"do_tty_hangup" ); -file_list_lock (); +spin_lock (&tty_files_lock);/* This breaks for file handles being sent over AF_UNIX sockets ? */ list_for_each_entry (filp, &tty->tty_files, f_u.fu_list) {if (filp->f_op->write == redirected_tty_write)
@@ -528,7 +531,7 @@ static void do_tty_hangup(struct work_sttty_fasync (-1 , filp,0 );/* can't block */ filp->f_op = &hung_up_tty_fops; } -file_list_unlock (); +spin_unlock (&tty_files_lock);tty_ldisc_hangup (tty);
@@ -1419,9 +1422,9 @@ static void release_one_tty(struct work_tty_driver_kref_put (driver);module_put (driver->owner); -file_list_lock (); +spin_lock (&tty_files_lock);list_del_init (&tty->tty_files); -file_list_unlock (); +spin_unlock (&tty_files_lock);put_pid (tty->pgrp);put_pid (tty->session);
@@ -1666,7 +1669,10 @@ int tty_release(struct inode *inode, str* - do_tty_hangup no longer sees this file descriptor as * something that needs to be handled for hangups. */ -file_kill (filp); +spin_lock (&tty_files_lock); +BUG_ON (list_empty (&filp->f_u.fu_list)); +list_del_init (&filp->f_u.fu_list); +spin_unlock (&tty_files_lock); filp->private_data =NULL ;/*
@@ -1835,7 +1841,11 @@ got_driver: } filp->private_data = tty; -file_move (filp, &tty->tty_files); +BUG_ON (list_empty (&filp->f_u.fu_list)); +file_sb_list_del (filp);/* __dentry_open has put it on the sb list */ +spin_lock (&tty_files_lock); +list_add (&filp->f_u.fu_list, &tty->tty_files); +spin_unlock (&tty_files_lock);check_tty_count (tty,"tty_open" );if (tty->driver->type ==TTY_DRIVER_TYPE_PTY && tty->driver->subtype ==PTY_TYPE_MASTER )
fs/file_table.c +18 -24
104 lines not on this page · raw message
fs/open.c +2 -2
@@ -675,7 +675,7 @@ static struct file *__dentry_open(struct f->f_path.mnt = mnt; f->f_pos =0 ; f->f_op =fops_get (inode->i_fop); -file_move (f, &inode->i_sb->s_files); +file_sb_list_add (f, inode->i_sb); error =security_dentry_open (f, cred);if (error)
@@ -721,7 +721,7 @@ cleanup_all:mnt_drop_write (mnt); } } -file_kill (f); +file_sb_list_del (f); f->f_path.dentry =NULL ; f->f_path.mnt =NULL ; cleanup_file:
include/linux/fs.h +2 -5
@@ -949,9 +949,6 @@ struct file {unsigned long f_mnt_write_state;#endif }; -extern spinlock_t files_lock; -#define file_list_lock () spin_lock(&files_lock); -#define file_list_unlock () spin_unlock(&files_lock);#define get_file (x) atomic_long_inc(&(x)->f_count)#define fput_atomic (x) atomic_long_add_unless(&(x)->f_count, -1, 1)
@@ -2182,8 +2179,8 @@ static inline void insert_inode_hash(str__insert_inode_hash (inode, inode->i_ino); } -extern void file_move (struct file *f,struct list_head *list); -extern void file_kill (struct file *f); +extern void file_sb_list_add (struct file *f,struct super_block *sb); +extern void file_sb_list_del (struct file *f);#ifdef CONFIG_BLOCK struct bio ;extern void submit_bio (int ,struct bio *);
include/linux/tty.h +1 -0
@@ -467,6 +467,7 @@ extern struct tty_struct *tty_pair_get_textern struct tty_struct *tty_pair_get_pty (struct tty_struct *tty);extern struct mutex tty_mutex; +extern spinlock_t tty_files_lock;extern void tty_write_unlock (struct tty_struct *tty);extern int tty_write_lock (struct tty_struct *tty,int ndelay);
PATCH 5/52 lglock: introduce special lglock and brlock spin locks
In reply to [email protected]This patch introduces "local-global" locks (lglocks). These can be used to:
- Provide fast exclusive access to per-CPU data, with exclusive access to another CPU's data allowed but possibly subject to contention, and to provide very slow exclusive access to all per-CPU data.
- Or to provide very fast and scalable read serialisation, and to provide very slow exclusive serialisation of data (not necessarily per-CPU data).
Brlocks are also implemented as a short-hand notation for the latter use case.
Thanks to Paul for local/global naming convention.
--- include/linux/lglock.h | 172 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 172 insertions(+)
include/linux/lglock.h new file +172 -0
@@ -0,0 +1,172 @@ +/* +* Specialised local-global spinlock. Can only be declared as global variables +* to avoid overhead and keep things simple (and we don't want to start using +* these inside dynamically allocated structures). +* +* "local/global locks" (lglocks) can be used to: +* +* - Provide fast exclusive access to per-CPU data, with exclusive access to +* another CPU's data allowed but possibly subject to contention, and to +* provide very slow exclusive access to all per-CPU data. +* - Or to provide very fast and scalable read serialisation, and to provide +* very slow exclusive serialisation of data (not necessarily per-CPU data). +* +* Brlocks are also implemented as a short-hand notation for the latter use +* case. +* +* Copyright 2009, 2010, Nick Piggin, Novell Inc. +*/ +#ifndef __LINUX_LGLOCK_H +#define __LINUX_LGLOCK_H + +#include <linux/spinlock.h> +#include <linux/lockdep.h> +#include <linux/percpu.h> + +/* can make br locks by using local lock for read side, global lock for write */ +#define br_lock_init (name) name##_lock_init() +#define br_read_lock (name) name##_local_lock() +#define br_read_unlock (name) name##_local_unlock() +#define br_write_lock (name) name##_global_lock_online() +#define br_write_unlock (name) name##_global_unlock_online() + +#define DECLARE_BRLOCK (name) DECLARE_LGLOCK(name) +#define DEFINE_BRLOCK (name) DEFINE_LGLOCK(name) + + +#define lg_lock_init (name) name##_lock_init() +#define lg_local_lock (name) name##_local_lock() +#define lg_local_unlock (name) name##_local_unlock() +#define lg_local_lock_cpu (name, cpu) name##_local_lock_cpu(cpu) +#define lg_local_unlock_cpu (name, cpu) name##_local_unlock_cpu(cpu) +#define lg_global_lock (name) name##_global_lock() +#define lg_global_unlock (name) name##_global_unlock() +#define lg_global_lock_online (name) name##_global_lock_online() +#define lg_global_unlock_online (name) name##_global_unlock_online() + +#ifdef CONFIG_DEBUG_LOCK_ALLOC +#define LOCKDEP_INIT_MAP lockdep_init_map + +#define DEFINE_LGLOCK_LOCKDEP (name) \ + struct lock_class_key name##_lock_key; \ + struct lockdep_map name##_lock_dep_map; \ + EXPORT_SYMBOL(name##_lock_dep_map) + +#else +#define LOCKDEP_INIT_MAP (a, b, c, d) + +#define DEFINE_LGLOCK_LOCKDEP (name) +#endif + + +#define DECLARE_LGLOCK (name) \ + extern void name##_lock_init(void); \ + extern void name##_local_lock(void); \ + extern void name##_local_unlock(void); \ + extern void name##_local_lock_cpu(int cpu); \ + extern void name##_local_unlock_cpu(int cpu); \ + extern void name##_global_lock(void); \ + extern void name##_global_unlock(void); \ + extern void name##_global_lock_online(void); \ + extern void name##_global_unlock_online(void); \ + +#define DEFINE_LGLOCK (name) \ + \ + DEFINE_PER_CPU(arch_spinlock_t, name##_lock); \ + DEFINE_LGLOCK_LOCKDEP(name); \ + \ + void name##_lock_init(void) { \ + int i; \ + LOCKDEP_INIT_MAP(&name##_lock_dep_map, #name, &name##_lock_key, 0); \ + for_each_possible_cpu(i) { \ + arch_spinlock_t *lock; \ + lock = &per_cpu(name##_lock, i); \ + *lock = (arch_spinlock_t)__ARCH_SPIN_LOCK_UNLOCKED; \ + } \ + } \ + EXPORT_SYMBOL(name##_lock_init); \ + \ + void name##_local_lock(void) { \ + arch_spinlock_t *lock; \ + preempt_disable(); \ + rwlock_acquire_read(&name##_lock_dep_map, 0, 0, _THIS_IP_); \ + lock = &__get_cpu_var(name##_lock); \ + arch_spin_lock(lock); \ + } \ + EXPORT_SYMBOL(name##_local_lock); \ + \ + void name##_local_unlock(void) { \ + arch_spinlock_t *lock; \ + rwlock_release(&name##_lock_dep_map, 1, _THIS_IP_); \ + lock = &__get_cpu_var(name##_lock); \ + arch_spin_unlock(lock); \ + preempt_enable(); \ + } \ + EXPORT_SYMBOL(name##_local_unlock); \ + \ + void name##_local_lock_cpu(int cpu) { \ + arch_spinlock_t *lock; \ + preempt_disable(); \ + rwlock_acquire_read(&name##_lock_dep_map, 0, 0, _THIS_IP_); \ + lock = &per_cpu(name##_lock, cpu); \ + arch_spin_lock(lock); \ + } \ + EXPORT_SYMBOL(name##_local_lock_cpu); \ + \ + void name##_local_unlock_cpu(int cpu) { \ + arch_spinlock_t *lock; \ + rwlock_release(&name##_lock_dep_map, 1, _THIS_IP_); \ + lock = &per_cpu(name##_lock, cpu); \ + arch_spin_unlock(lock); \ + preempt_enable(); \ + } \ + EXPORT_SYMBOL(name##_local_unlock_cpu); \ + \ + void name##_global_lock_online(void) { \ + int i; \ + preempt_disable(); \ + rwlock_acquire(&name##_lock_dep_map, 0, 0, _RET_IP_); \ + for_each_online_cpu(i) { \ + arch_spinlock_t *lock; \ + lock = &per_cpu(name##_lock, i); \ + arch_spin_lock(lock); \ + } \ + } \ + EXPORT_SYMBOL(name##_global_lock_online); \ + \ + void name##_global_unlock_online(void) { \ + int i; \ + rwlock_release(&name##_lock_dep_map, 1, _RET_IP_); \ + for_each_online_cpu(i) { \ + arch_spinlock_t *lock; \ + lock = &per_cpu(name##_lock, i); \ + arch_spin_unlock(lock); \ + } \ + preempt_enable(); \ + } \ + EXPORT_SYMBOL(name##_global_unlock_online); \ + \ + void name##_global_lock(void) { \ + int i; \ + preempt_disable(); \ + rwlock_acquire(&name##_lock_dep_map, 0, 0, _RET_IP_); \ + for_each_online_cpu(i) { \ + arch_spinlock_t *lock; \ + lock = &per_cpu(name##_lock, i); \ + arch_spin_lock(lock); \ + } \ + } \ + EXPORT_SYMBOL(name##_global_lock); \ + \ + void name##_global_unlock(void) { \ + int i; \ + rwlock_release(&name##_lock_dep_map, 1, _RET_IP_); \ + for_each_online_cpu(i) { \ + arch_spinlock_t *lock; \ + lock = &per_cpu(name##_lock, i); \ + arch_spin_unlock(lock); \ + } \ + preempt_enable(); \ + } \ + EXPORT_SYMBOL(name##_global_unlock); +#endif
Re: [patch 05/52] lglock: introduce special lglock and brlock spin locks
include/linux/lglock.h line 75
Quoted 3 lines · On Thu, 24 Jun 2010, [email protected] wrote:
include/linux/lglock.h new file +3
-0+#define DEFINE_LGLOCK (name) \ + \ + DEFINE_PER_CPU(arch_spinlock_t, name##_lock); \
Uuurgh. You want to make that an arch_spinlock ? Just to avoid the preempt_count overflow when you lock all cpu locks nested ?
I'm really not happy about that, it's going to be a complete nightmare for RT. If you wanted to make this a present for RT giving the scalability stuff massive testing, then you failed miserably :)
I know how to fix it, but can't we go for an approach which does not require massive RT patching again ?
struct percpu_lock {
spinlock_t lock;
unsigned global_state;
};And let the lock function do:
spin_lock(&pcp->lock);
while (pcp->global_state)
cpu_relax();So the global lock side can take each single lock, modify the percpu "global state" and release the lock. On unlock you just need to reset the global state w/o taking the percpu lock and be done.
I doubt that the extra conditional in the lock path is going to be relevant overhead, compared to the spin_lock it's noise.
Thanks,
tglx
Re: [patch 05/52] lglock: introduce special lglock and brlock spin locks
Quoted 8 lines · Thomas Gleixner
include/linux/lglock.h line 75
Quoted 3 lines · On Thu, 24 Jun 2010, [email protected] wrote:
include/linux/lglock.h new file +3
-0+#define DEFINE_LGLOCK (name) \ + \ + DEFINE_PER_CPU(arch_spinlock_t, name##_lock); \Uuurgh. You want to make that an
arch_spinlock? Just to avoid thepreempt_countoverflow when you lock all cpu locks nested ?
Yep, and the lockdep wreckage too :)
Actually it's nice to avoid the function call too (lglock/brlocks are already out of line). Calls aren't totally free, especially on small chips without RSBs. And even with RSBs it's helpful not to overflow them, although Nehalem seems to have 12-16 entries.
Quoted 3 lines
I'm really not happy about that, it's going to be a complete nightmare for RT. If you wanted to make this a present for RT giving the scalability stuff massive testing, then you failed miserably :)
Heh, it's a present for -rt because the locking is quite isolated (I did the same thing with hashtable bitlocks, added a new structure for them, in case you prefer spinlocks than bit spinlocks there).
-rt already changes locking primitives, so in the worst case you might have to tweak this. My previous patches were open coding these locks in fs/ so I can understand why that was a headache.
Quoted 14 lines
I know how to fix it, but can't we go for an approach which does not require massive RT patching again ?
struct percpu_lock { spinlock_t lock; unsigned global_state; };And let the lock function do:
spin_lock(&pcp->lock); while (pcp->global_state) cpu_relax();
Quoted 6 lines
So the global lock side can take each single lock, modify the percpu "global state" and release the lock. On unlock you just need to reset the global state w/o taking the percpu lock and be done.
I doubt that the extra conditional in the lock path is going to be relevant overhead, compared to the
spin_lockit's noise.
Hmm. We need a smp_rmb() which costs a bit (on non-x86). For -rt you would need to do priority boosting too.
reader lock:spin_lock(&pcp->rlock);
if (unlikely(pcp->global_state)) {
spin_unlock(&pcp->rlock);
spin_lock(&wlock);
spin_lock(&pcp->rlock);
spin_unlock(&wlock);
} else
smp_rmb();
I think I'll keep it as is for now, it's hard enough to keep single threaded performance up. But it should be much easier to override this in -rt and I'll be happy to try restructuring things to help rt if and when it's possible.
Re: [patch 05/52] lglock: introduce special lglock and brlock spin locks
Quoted 18 lines · Nick Piggin
Quoted 8 lines · Thomas Gleixner
include/linux/lglock.h line 75
Quoted 3 lines · On Thu, 24 Jun 2010, [email protected] wrote:
include/linux/lglock.h new file +3
-0+#define DEFINE_LGLOCK (name) \ + \ + DEFINE_PER_CPU(arch_spinlock_t, name##_lock); \Uuurgh. You want to make that an
arch_spinlock? Just to avoid thepreempt_countoverflow when you lock all cpu locks nested ?Yep, and the lockdep wreckage too :)
Actually it's nice to avoid the function call too (lglock/brlocks are already out of line). Calls aren't totally free, especially on small chips without RSBs. And even with RSBs it's helpful not to overflow them, although Nehalem seems to have 12-16 entries.
Quoted 7 lines
Quoted 3 lines
I'm really not happy about that, it's going to be a complete nightmare for RT. If you wanted to make this a present for RT giving the scalability stuff massive testing, then you failed miserably :)
Heh, it's a present for -rt because the locking is quite isolated (I did the same thing with hashtable bitlocks, added a new structure for them, in case you prefer spinlocks than bit spinlocks there).
Sure, bitlocks are equally horrible.
Quoted 3 lines
-rt already changes locking primitives, so in the worst case you might have to tweak this. My previous patches were open coding these locks in
fs/so I can understand why that was a headache.
I agree that having the code isolated makes my life easier, but I'm a bit worried about the various new locking primitives which pop up in all corners of the kernel.
Quoted 4 lines
I think I'll keep it as is for now, it's hard enough to keep single threaded performance up. But it should be much easier to override this in -rt and I'll be happy to try restructuring things to help rt if and when it's possible.
Ok, lets see how bad it gets :)
Thanks,
tglx
Re: [patch 05/52] lglock: introduce special lglock and brlock spin locks
Quoted 29 lines · Thomas Gleixner
Quoted 18 lines · Nick Piggin
Quoted 8 lines · Thomas Gleixner
include/linux/lglock.h line 75
Quoted 3 lines · On Thu, 24 Jun 2010, [email protected] wrote:
include/linux/lglock.h new file +3
-0+#define DEFINE_LGLOCK (name) \ + \ + DEFINE_PER_CPU(arch_spinlock_t, name##_lock); \Uuurgh. You want to make that an
arch_spinlock? Just to avoid thepreempt_countoverflow when you lock all cpu locks nested ?Yep, and the lockdep wreckage too :)
Actually it's nice to avoid the function call too (lglock/brlocks are already out of line). Calls aren't totally free, especially on small chips without RSBs. And even with RSBs it's helpful not to overflow them, although Nehalem seems to have 12-16 entries.
Quoted 7 lines
Quoted 3 lines
I'm really not happy about that, it's going to be a complete nightmare for RT. If you wanted to make this a present for RT giving the scalability stuff massive testing, then you failed miserably :)
Heh, it's a present for -rt because the locking is quite isolated (I did the same thing with hashtable bitlocks, added a new structure for them, in case you prefer spinlocks than bit spinlocks there).
Sure, bitlocks are equally horrible.
Quoted 7 lines
Quoted 3 lines
-rt already changes locking primitives, so in the worst case you might have to tweak this. My previous patches were open coding these locks in
fs/so I can understand why that was a headache.I agree that having the code isolated makes my life easier, but I'm a bit worried about the various new locking primitives which pop up in all corners of the kernel.
Be sure to shout at people for it :). Locking primitives really need to be reviewed, and having them in common places lets other people use them too.
Quoted 6 lines
Quoted 4 lines
I think I'll keep it as is for now, it's hard enough to keep single threaded performance up. But it should be much easier to override this in -rt and I'll be happy to try restructuring things to help rt if and when it's possible.
Ok, lets see how bad it gets :)
Ok good.
PATCH 6/52 fs: scale files_lock
In reply to [email protected]Improve scalability of files_lock by adding per-cpu, per-sb files lists, protected with an lglock. The lglock provides fast access to the per-cpu lists to add and remove files. It also provides a snapshot of all the per-cpu lists (although this is very slow).
One difficulty with this approach is that a file can be removed from the list by another CPU. We must track which per-cpu list the file is on. Scalability could suffer if files are frequently removed from different cpu's list.
However loads with frequent removal of files imply short interval between adding and removing the files, and the scheduler attempts to avoid moving processes too far away. Also, even in the case of cross-CPU removal, the hardware has much more opportunity to parallelise cacheline transfers with N cachelines than with 1.
A worst-case test of 1 CPU allocating files subsequently being freed by N CPUs degenerates to contending on a single lock, which is no worse than before. When more than one CPU are allocating files, even if they are always freed by different CPUs, there will be more parallelism than the single-lock case.
Testing results:
On a 2 socket, 8 core opteron, I measure the number of times the lock is taken to remove the file, the number of times it is removed by the same CPU that added it, and the number of times it is removed by the same node that added it.
Booting: locks= 25049 cpu-hits= 23174 (92.5%) node-hits= 23945 (95.6%)
kbuild -j16 locks=2281913 cpu-hits=2208126 (96.8%) node-hits=2252674 (98.7%)
dbench 64 locks=4306582 cpu-hits=4287247 (99.6%) node-hits=4299527 (99.8%)
So a file is removed from the same CPU it was added by over 90% of the time. It remains within the same node 95% of the time.
Tim Chen run some numbers for a 64 thread Nehalem system performing a compile.
throughput
2.6.34-rc2 24.5
+patch 24.9
us sys idle IO wait (in %) 2.6.34-rc2 51.25 28.25 17.25 3.25 +patch 53.75 18.5 19 8.75
So significantly less CPU time spent in kernel code, higher idle time and slightly higher throughput.
Single threaded performance difference was within the noise of microbenchmarks. That is not to say one does not exist, the code is larger and more memory accesses required so it will be slightly slower.
--- fs/file_table.c | 108 ++++++++++++++++++++++++++++++++++++++++++++--------- fs/super.c | 18 ++++++++ include/linux/fs.h | 7 +++ 3 files changed, 115 insertions(+), 18 deletions(-)
fs/file_table.c +90 -18
@@ -20,7 +20,9 @@#include <linux/cdev.h> #include <linux/fsnotify.h> #include <linux/sysctl.h> +#include <linux/lglock.h> #include <linux/percpu_counter.h> +#include <linux/percpu.h> #include <linux/ima.h> #include <asm/atomic.h>
@@ -32,7 +34,8 @@ struct files_stat_struct files_stat = { .max_files = NR_FILE }; -static __cacheline_aligned_in_smp DEFINE_SPINLOCK (files_lock ); +DECLARE_LGLOCK (files_lglock); +DEFINE_LGLOCK (files_lglock);/* SLAB cache for file structures */ static struct kmem_cache *filp_cachep__read_mostly ;
@@ -327,30 +330,98 @@ void put_filp(struct file *file) } } +static inline int file_list_cpu (struct file *file) +{ +#ifdef CONFIG_SMP +return file->f_sb_list_cpu; +#else +return smp_processor_id (); +#endif +} + +/* helper for file_sb_list_add to reduce ifdefs */ +static inline void __file_sb_list_add (struct file *file,struct super_block *sb) +{ +struct list_head *list; +#ifdef CONFIG_SMP +int cpu; + cpu =smp_processor_id (); + file->f_sb_list_cpu = cpu; + list =per_cpu_ptr (sb->s_files, cpu); +#else + list = &sb->s_files; +#endif +list_add (&file->f_u.fu_list, list); +} + +/** +* file_sb_list_add - add a file to the sb's file list +* @file: file to add +* @sb: sb to add it to +* +* Use this function to associate a file with the superblock of the inode it +* refers to. +*/ void file_sb_list_add (struct file *file,struct super_block *sb) { -spin_lock (&files_lock); -BUG_ON (!list_empty (&file->f_u.fu_list)); -list_add (&file->f_u.fu_list, &sb->s_files); -spin_unlock (&files_lock); +lg_local_lock (files_lglock); +__file_sb_list_add (file, sb); +lg_local_unlock (files_lglock); } +/** +* file_sb_list_del - remove a file from the sb's file list +* @file: file to remove +* @sb: sb to remove it from +* +* Use this function to remove a file from its superblock. +*/ void file_sb_list_del (struct file *file) {if (!list_empty (&file->f_u.fu_list)) { -spin_lock (&files_lock); +lg_local_lock_cpu (files_lglock,file_list_cpu (file));list_del_init (&file->f_u.fu_list); -spin_unlock (&files_lock); +lg_local_unlock_cpu (files_lglock,file_list_cpu (file)); } } +#ifdef CONFIG_SMP + +/* +* These macros iterate all files on all CPUs for a given superblock. +* files_lglock must be held globally. +*/ +#define do_file_list_for_each_entry (__sb, __file) \ +{ \ + int i; \ + for_each_possible_cpu(i) { \ + struct list_head *list; \ + list = per_cpu_ptr((__sb)->s_files, i); \ + list_for_each_entry((__file), list, f_u.fu_list) + +#define while_file_list_for_each_entry \ + } \ +} + +#else + +#define do_file_list_for_each_entry (__sb, __file) \ +{ \ + struct list_head *list; \ + list = &(sb)->s_files; \ + list_for_each_entry((__file), list, f_u.fu_list) + +#define while_file_list_for_each_entry \ +} + +#endif +int fs_may_remount_ro (struct super_block *sb) {struct file *file; -/* Check that no files are currently opened for writing. */ -spin_lock (&files_lock); -list_for_each_entry (file, &sb->s_files, f_u.fu_list) { +lg_global_lock (files_lglock); +do_file_list_for_each_entry (sb, file) {struct inode *inode = file->f_path.dentry->d_inode;/* File with pending delete? */
@@ -360,11 +431,11 @@ int fs_may_remount_ro(struct super_block/* Writeable file? */ if (S_ISREG (inode->i_mode) && (file->f_mode &FMODE_WRITE )) goto too_bad; - } -spin_unlock (&files_lock); + } while_file_list_for_each_entry; +lg_global_unlock (files_lglock);return 1 ;/* Tis' cool bro. */ too_bad: -spin_unlock (&files_lock); +lg_global_unlock (files_lglock);return 0 ; }
@@ -380,8 +451,8 @@ void mark_files_ro(struct super_block *sstruct file *f; retry: -spin_lock (&files_lock); -list_for_each_entry (f, &sb->s_files, f_u.fu_list) { +lg_global_lock (files_lglock); +do_file_list_for_each_entry (sb, f) {struct vfsmount *mnt;if (!S_ISREG (f->f_path.dentry->d_inode->i_mode))continue ;
@@ -397,12 +468,12 @@ retry:file_release_write (f); mnt =mntget (f->f_path.mnt);/* This can sleep, so we can't hold the spinlock. */ -spin_unlock (&files_lock); +lg_global_unlock (files_lglock);mnt_drop_write (mnt);mntput (mnt); goto retry; - } -spin_unlock (&files_lock); + } while_file_list_for_each_entry; +lg_global_unlock (files_lglock); }void __init files_init (unsigned long mempages)
@@ -422,5 +493,6 @@ void __init files_init(unsigned long memif (files_stat.max_files <NR_FILE ) files_stat.max_files =NR_FILE ;files_defer_init (); +lg_lock_init (files_lglock);percpu_counter_init (&nr_files,0 ); }
fs/super.c +18 -0
@@ -54,7 +54,22 @@ static struct super_block *alloc_super(s s =NULL ; goto out; } +#ifdef CONFIG_SMP + s->s_files = alloc_percpu(struct list_head); +if (!s->s_files) { +security_sb_free (s); +kfree (s); + s =NULL ; + goto out; + }else { +int i; + +for_each_possible_cpu (i) +INIT_LIST_HEAD (per_cpu_ptr (s->s_files, i)); + } +#else INIT_LIST_HEAD (&s->s_files); +#endif INIT_LIST_HEAD (&s->s_instances);INIT_HLIST_HEAD (&s->s_anon);INIT_LIST_HEAD (&s->s_inodes);
@@ -108,6 +123,9 @@ out:*/ static inline void destroy_super (struct super_block *s) { +#ifdef CONFIG_SMP +free_percpu (s->s_files); +#endif security_sb_free (s);kfree (s->s_subtype);kfree (s->s_options);
include/linux/fs.h +7 -0
@@ -925,6 +925,9 @@ struct file {#define f_vfsmnt f_path.mntconst struct file_operations *f_op;spinlock_t f_lock;/* f_ep_links, f_flags, no IRQ */ +#ifdef CONFIG_SMP +int f_sb_list_cpu; +#endif atomic_long_t f_count;unsigned int f_flags;fmode_t f_mode;
@@ -1339,7 +1342,11 @@ struct super_block {struct list_head s_inodes;/* all inodes */ struct hlist_head s_anon;/* anonymous dentries for (nfs) exporting */ +#ifdef CONFIG_SMP +struct list_head __percpu *s_files; +#else struct list_head s_files; +#endif /* s_dentry_lru and s_nr_dentry_unused are protected by dcache_lock */ struct list_head s_dentry_lru;/* unused dentry lru */ int s_nr_dentry_unused;/* # of dentry on lru */
Peter Zijlstra
maintainerRe: [patch 06/52] fs: scale files_lock
Quoted 4 lines · On Thu, 2010-06-24 at 13:02 +1000, [email protected] wrote:
One difficulty with this approach is that a file can be removed from the list by another CPU. We must track which per-cpu list the file is on. Scalability could suffer if files are frequently removed from different cpu's list.
Is this really a lot less complex than what I did with my fine-grained locked list?
Re: [patch 06/52] fs: scale files_lock
Quoted 9 lines · Peter Zijlstra
Quoted 4 lines · On Thu, 2010-06-24 at 13:02 +1000, [email protected] wrote:
One difficulty with this approach is that a file can be removed from the list by another CPU. We must track which per-cpu list the file is on. Scalability could suffer if files are frequently removed from different cpu's list.
Is this really a lot less complex than what I did with my fine-grained locked list?
http://www.mail-archive.com/[email protected]/msg115071.html
Honestly the filevec code seemed overkill to me, and yes it was a bit complex. The only reason to consider it AFAIKS would be if the space overhead of the per-cpu structures, or the slowpath cost of the brlock was unbearable.
filevecs probably dont perform as well in the fastpath. My patch doesn't add any atomics. The cost of adding or removing a file from its list are one atomic for the spinlock.
The cost of adding a file with filevecs is a spinlock to put it on the vec, a spinlock to take it off the vec, a spinlock to put it on the lock-list. 3 atomics. A heap more icache and branches.
Removing a file with filevecs is a spinlock to check the vec, and 1 or 2 spinlocks to take it off the list (common case).
Scalability will be improved, but it will hit the global list still 1/15th times (and there is even no lock batching on the list but I assume that could be fixed). Compared with never for my patch (unless there is a cross-CPU removal, in which case they both need to hit a remote-CPU cacheline).
But before we even get to scalability, I think filevecs from complexity and single threaded performance point already lose.
PATCH 7/52 fs: brlock vfsmount_lock
In reply to [email protected]Use a brlock for the vfsmount lock. It must be taken for write whenever modifying the mount hash or associated fields, and may be taken for read when performing mount hash lookups.
The number of atomics should remain the same for fastpath rlock cases, though code will be slightly slower due to per-cpu access. Scalability will probably not be much improved in common cases yet, due to other locks getting in the way. However independent path lookups over mountpoints should be one case where scalability is improved.
The slowpath will be made significantly slower due to use of brlock. On a 64 core, 64 socket, 32 node Altix system (so a decent amount of latency to remote nodes), a simple umount microbenchmark (mount --bind mnt mnt2 ; umount mnt2 loop 1000 times), before this patch it took 6.8s, afterwards took 7.1s, for about 5% increase in elapsed time.
--- fs/dcache.c | 11 +-- fs/internal.h | 5 + fs/namei.c | 7 +- fs/namespace.c | 177 +++++++++++++++++++++++++++++++++++---------------------- fs/pnode.c | 11 ++- 5 files changed, 134 insertions(+), 77 deletions(-)
fs/dcache.c +6 -5
@@ -1928,7 +1928,7 @@ char *__d_path(const struct path *path,char *end = buffer + buflen;char *retval; -spin_lock (&vfsmount_lock); +br_read_lock (vfsmount_lock);prepend (&end, &buflen,"\0" ,1 );if (d_unlinked (dentry) && (prepend (&end, &buflen," (deleted)" ,10 ) !=0 ))
@@ -1964,7 +1964,7 @@ char *__d_path(const struct path *path, } out: -spin_unlock (&vfsmount_lock); +br_read_unlock (vfsmount_lock);return retval; global_root:
@@ -2195,11 +2195,12 @@ int path_is_under(struct path *path1, ststruct vfsmount *mnt = path1->mnt;struct dentry *dentry = path1->dentry;int res; -spin_lock (&vfsmount_lock); + +br_read_lock (vfsmount_lock);if (mnt != path2->mnt) {for (;;) {if (mnt->mnt_parent == mnt) { -spin_unlock (&vfsmount_lock); +br_read_unlock (vfsmount_lock);return 0 ; }if (mnt->mnt_parent == path2->mnt)
@@ -2209,7 +2210,7 @@ int path_is_under(struct path *path1, st dentry = mnt->mnt_mountpoint; } res =is_subdir (dentry, path2->dentry); -spin_unlock (&vfsmount_lock); +br_read_unlock (vfsmount_lock);return res; }EXPORT_SYMBOL (path_is_under);
fs/namei.c +4 -3
@@ -601,15 +601,16 @@ int follow_up(struct path *path) {struct vfsmount *parent;struct dentry *mountpoint; -spin_lock (&vfsmount_lock); + +br_read_lock (vfsmount_lock); parent = path->mnt->mnt_parent;if (parent == path->mnt) { -spin_unlock (&vfsmount_lock); +br_read_unlock (vfsmount_lock);return 0 ; }mntget (parent); mountpoint =dget (path->mnt->mnt_mountpoint); -spin_unlock (&vfsmount_lock); +br_read_unlock (vfsmount_lock);dput (path->dentry); path->dentry = mountpoint;mntput (path->mnt);
fs/namespace.c +111 -66
487 lines not on this page · raw message
fs/pnode.c +9 -2
@@ -126,6 +126,9 @@ static int do_make_slave(struct vfsmountreturn 0 ; } +/* +* vfsmount lock must be held for write +*/ void change_mnt_propagation (struct vfsmount *mnt,int type) {if (type ==MS_SHARED ) {
@@ -270,12 +273,12 @@ int propagate_mnt(struct vfsmount *dest_ prev_src_mnt = child; } out: -spin_lock (&vfsmount_lock); +br_write_lock (vfsmount_lock);while (!list_empty (&tmp_list)) { child = list_first_entry(&tmp_list, struct vfsmount, mnt_hash);umount_tree (child,0 , &umount_list); } -spin_unlock (&vfsmount_lock); +br_write_unlock (vfsmount_lock);release_mounts (&umount_list);return ret; }
@@ -296,6 +299,8 @@ static inline int do_refcount_check(stru* other mounts its parent propagates to. * Check if any of these mounts that **do not have submounts** * have more references than 'refcnt'. If so return busy. +* +* vfsmount lock must be held for read or write */ int propagate_mount_busy (struct vfsmount *mnt,int refcnt) {
@@ -353,6 +358,8 @@ static void __propagate_umount(struct vf* collect all mounts that receive propagation from the mount in @list, * and return these additional mounts in the same list. * @list: the list of mounts to be unmounted. +* +* vfsmount lock must be held for write */ int propagate_umount (struct list_head *list) {
fs/internal.h +4 -1
@@ -9,6 +9,8 @@* 2 of the License, or (at your option) any later version. */ +#include <linux/lglock.h> +struct super_block ;struct linux_binprm ;struct path ;
@@ -70,7 +72,8 @@ extern struct vfsmount *copy_tree(structextern void __init mnt_init (void ); -extern spinlock_t vfsmount_lock; +DECLARE_BRLOCK (vfsmount_lock); +/* * fs_struct.c
PATCH 8/52 fs: scale mntget/mntput
In reply to [email protected]Improve scalability of mntget/mntput by using per-cpu counters protected by the reader side of the brlock vfsmount_lock. If the mnt_hash field of the vfsmount structure is attached to a list, then it is mounted which contributes to its refcount, so the per-cpu counters need not be summed.
MNT_PSEUDO keeps track of whether the vfsmount is actually a pseudo filesystem that will never be attached (such as sockfs).
No extra atomics in the common case because atomic mnt refcount is now replaced with per-CPU spinlock. Code will be bigger and more complex however. With the previous per-cpu locking patch, mount lookups and common case refcounting are now per-cpu and should be ideally scalable. path lookups (and hence path_get/path_put) within the same vfsmount should now be more scalable, however this will often be hidden by dcache_lock on final dput, and d_lock on common path elements (eg. cwd or root dentry).
[Note: this is not for merging. Un-attached operation (lazy umount) may not be
uncommon and will be slowed down and actually have worse scalablilty after this patch. I need to think about how to do fast refcounting with unattached mounts.]
--- drivers/mtd/mtdchar.c | 1 fs/internal.h | 1 fs/libfs.c | 1 fs/namespace.c | 155 +++++++++++++++++++++++++++++++++++++++++++------- fs/pnode.c | 4 - include/linux/mount.h | 26 +------- 6 files changed, 144 insertions(+), 44 deletions(-)
fs/namespace.c +134 -21
@@ -138,6 +138,64 @@ void mnt_release_group_id(struct vfsmoun mnt->mnt_group_id =0 ; } +/* +* vfsmount lock must be held for read +*/ +static inline void add_mnt_count (struct vfsmount *mnt,int n) +{ +#ifdef CONFIG_SMP + (*per_cpu_ptr (mnt->mnt_count,smp_processor_id ())) += n; +#else + mnt->mnt_count += n; +#endif +} + +static inline void set_mnt_count (struct vfsmount *mnt,int n) +{ +#ifdef CONFIG_SMP +preempt_disable (); + (*per_cpu_ptr (mnt->mnt_count,smp_processor_id ())) = n; +preempt_enable (); +#else + mnt->mnt_count = n; +#endif +} + +/* +* vfsmount lock must be held for read +*/ +static inline void inc_mnt_count (struct vfsmount *mnt) +{ +add_mnt_count (mnt,1 ); +} + +/* +* vfsmount lock must be held for read +*/ +static inline void dec_mnt_count (struct vfsmount *mnt) +{ +add_mnt_count (mnt,-1 ); +} + +/* +* vfsmount lock must be held for write +*/ +unsigned int count_mnt_count (struct vfsmount *mnt) +{ +#ifdef CONFIG_SMP +unsigned int count =0 ; +int cpu; + +for_each_possible_cpu (cpu) { + count += *per_cpu_ptr (mnt->mnt_count, cpu); + } + +return count; +#else +return mnt->mnt_count; +#endif +} +struct vfsmount *alloc_vfsmnt (const char *name) {struct vfsmount *mnt =kmem_cache_zalloc (mnt_cache,GFP_KERNEL );
@@ -154,7 +212,15 @@ struct vfsmount *alloc_vfsmnt(const char goto out_free_id; } -atomic_set (&mnt->mnt_count,1 ); +#ifdef CONFIG_SMP + mnt->mnt_count =alloc_percpu (int); +if (!mnt->mnt_count) + goto out_free_devname; +#else + mnt->mnt_count =0 ; +#endif +set_mnt_count (mnt,1 ); +INIT_LIST_HEAD (&mnt->mnt_hash);INIT_LIST_HEAD (&mnt->mnt_child);INIT_LIST_HEAD (&mnt->mnt_mounts);
@@ -166,7 +232,7 @@ struct vfsmount *alloc_vfsmnt(const char#ifdef CONFIG_SMP mnt->mnt_writers =alloc_percpu (int);if (!mnt->mnt_writers) - goto out_free_devname; + goto out_free_mntcount;#else mnt->mnt_writers =0 ;#endif
@@ -174,6 +240,8 @@ struct vfsmount *alloc_vfsmnt(const charreturn mnt;#ifdef CONFIG_SMP +out_free_mntcount: +free_percpu (mnt->mnt_count); out_free_devname:kfree (mnt->mnt_devname);#endif
@@ -591,7 +659,7 @@ static struct vfsmount *clone_mnt(struct goto out_free; } - mnt->mnt_flags = old->mnt_flags; +WARN_ON (mnt->mnt_flags &MNT_WRITE_HOLD );atomic_inc (&sb->s_active); mnt->mnt_sb = sb; mnt->mnt_root =dget (root);
@@ -638,6 +706,11 @@ static inline void __mntput(struct vfsmo/* * atomic_dec_and_lock() used to deal with ->mnt_count decrements * provides barriers, so count_mnt_writers() below is safe. AV +* XXX: We no longer have an atomic_dec_and_lock, so load of +* mnt_writers may be moved up into the vfsmount lock critical section? +* Do we need an smp_mb()? I don't see how it is possible because an +* elevated write count should also have elevated ref count so we'd +* never get here. */ WARN_ON (count_mnt_writers (mnt));dput (mnt->mnt_root);
@@ -648,45 +721,76 @@ static inline void __mntput(struct vfsmovoid mntput_no_expire (struct vfsmount *mnt) { repeat: -if (atomic_add_unless (&mnt->mnt_count,-1 ,1 )) +if (likely (!list_empty (&mnt->mnt_hash) || + mnt->mnt_flags &MNT_PSEUDO )) { +br_read_lock (vfsmount_lock); +if (unlikely (list_empty (&mnt->mnt_hash) && + (!(mnt->mnt_flags &MNT_PSEUDO )))) { +br_read_unlock (vfsmount_lock); + goto repeat; + } +dec_mnt_count (mnt); +br_read_unlock (vfsmount_lock);return ; + } +br_write_lock (vfsmount_lock); -if (!atomic_dec_and_test (&mnt->mnt_count)) { +dec_mnt_count (mnt); +if (count_mnt_count (mnt)) {br_write_unlock (vfsmount_lock);return ; } -if (likely (!mnt->mnt_pinned)) { +if (unlikely (mnt->mnt_pinned)) { +add_mnt_count (mnt, mnt->mnt_pinned +1 ); + mnt->mnt_pinned =0 ;br_write_unlock (vfsmount_lock); -__mntput (mnt); -return ; +acct_auto_close_mnt (mnt); + goto repeat; } -atomic_add (mnt->mnt_pinned +1 , &mnt->mnt_count); - mnt->mnt_pinned =0 ;br_write_unlock (vfsmount_lock); -acct_auto_close_mnt (mnt); - goto repeat; +__mntput (mnt); }EXPORT_SYMBOL (mntput_no_expire); +void mntput (struct vfsmount *mnt) +{ +if (mnt) { +/* avoid cacheline pingpong, hope gcc doesn't get "smart" */ +if (unlikely (mnt->mnt_expiry_mark)) + mnt->mnt_expiry_mark =0 ; +mntput_no_expire (mnt); + } +} +EXPORT_SYMBOL (mntput); + +struct vfsmount *mntget (struct vfsmount *mnt) +{ +if (mnt) { +preempt_disable (); +inc_mnt_count (mnt); +preempt_enable (); + } +return mnt; +} +EXPORT_SYMBOL (mntget); +void mnt_pin (struct vfsmount *mnt) {br_write_lock (vfsmount_lock); mnt->mnt_pinned++;br_write_unlock (vfsmount_lock); } -EXPORT_SYMBOL (mnt_pin);void mnt_unpin (struct vfsmount *mnt) {br_write_lock (vfsmount_lock);if (mnt->mnt_pinned) { -atomic_inc (&mnt->mnt_count); +inc_mnt_count (mnt); mnt->mnt_pinned--; }br_write_unlock (vfsmount_lock); } -EXPORT_SYMBOL (mnt_unpin);static inline void mangle (struct seq_file *m,const char *s)
@@ -982,12 +1086,13 @@ int may_umount_tree(struct vfsmount *mntint minimum_refs =0 ;struct vfsmount *p; -br_read_lock (vfsmount_lock); +/* write lock needed for count_mnt_count */ +br_write_lock (vfsmount_lock);for (p = mnt; p; p =next_mnt (p, mnt)) { - actual_refs +=atomic_read (&p->mnt_count); + actual_refs +=count_mnt_count (p); minimum_refs +=2 ; } -br_read_unlock (vfsmount_lock); +br_write_unlock (vfsmount_lock);if (actual_refs > minimum_refs)return 0 ;
@@ -1014,10 +1119,10 @@ int may_umount(struct vfsmount *mnt) {int ret =1 ;down_read (&namespace_sem); -br_read_lock (vfsmount_lock); +br_write_lock (vfsmount_lock);if (propagate_mount_busy (mnt,2 )) ret =0 ; -br_read_unlock (vfsmount_lock); +br_write_unlock (vfsmount_lock);up_read (&namespace_sem);return ret; }
@@ -1099,8 +1204,16 @@ static int do_umount(struct vfsmount *mn flags & (MNT_FORCE | MNT_DETACH))return -EINVAL ; -if (atomic_read (&mnt->mnt_count) !=2 ) +/* +* probably don't strictly need the lock here if we examined +* all race cases, but it's a slowpath. +*/ +br_write_lock (vfsmount_lock); +if (count_mnt_count (mnt) !=2 ) { +br_write_lock (vfsmount_lock);return -EBUSY ; + } +br_write_unlock (vfsmount_lock);if (!xchg (&mnt->mnt_expiry_mark,1 ))return -EAGAIN ;
include/linux/mount.h +5 -21
@@ -31,6 +31,7 @@ struct mnt_namespace;#define MNT_SHRINKABLE 0x100#define MNT_WRITE_HOLD 0x200 +#define MNT_PSEUDO 0x400#define MNT_SHARED 0x1000/* if the vfsmount is a shared mount */ #define MNT_UNBINDABLE 0x2000/* if the vfsmount is a unbindable mount */
@@ -67,19 +68,15 @@ struct vfsmount {struct mnt_namespace *mnt_ns;/* containing namespace */ int mnt_id;/* mount identifier */ int mnt_group_id;/* peer group identifier */ -/* -* We put mnt_count & mnt_expiry_mark at the end of struct vfsmount -* to let these frequently modified fields in a separate cache line -* (so that reads of mnt_flags wont ping-pong on SMP machines) -*/ -atomic_t mnt_count;int mnt_expiry_mark;/* true if marked for expiry */ int mnt_pinned;int mnt_ghosts;#ifdef CONFIG_SMP int __percpu *mnt_writers; +int __percpu *mnt_count;#else int mnt_writers; +int mnt_count;#endif };
@@ -92,13 +89,6 @@ static inline int *get_mnt_writers_ptr(s#endif } -static inline struct vfsmount *mntget (struct vfsmount *mnt) -{ -if (mnt) -atomic_inc (&mnt->mnt_count); -return mnt; -} -struct file ;/* forward dec */ extern int mnt_want_write (struct vfsmount *mnt);
@@ -106,18 +96,12 @@ extern int mnt_want_write_file(struct fiextern int mnt_clone_write (struct vfsmount *mnt);extern void mnt_drop_write (struct vfsmount *mnt);extern void mntput_no_expire (struct vfsmount *mnt); +extern void mntput (struct vfsmount *mnt); +extern struct vfsmount *mntget (struct vfsmount *mnt);extern void mnt_pin (struct vfsmount *mnt);extern void mnt_unpin (struct vfsmount *mnt);extern int __mnt_is_readonly (struct vfsmount *mnt); -static inline void mntput (struct vfsmount *mnt) -{ -if (mnt) { - mnt->mnt_expiry_mark =0 ; -mntput_no_expire (mnt); - } -} -extern struct vfsmount *do_kern_mount (const char *fstype,int flags,const char *name,void *data);
fs/pnode.c +2 -2
@@ -288,7 +288,7 @@ out:*/ static inline int do_refcount_check (struct vfsmount *mnt,int count) { -int mycount =atomic_read (&mnt->mnt_count) - mnt->mnt_ghosts; +int mycount =count_mnt_count (mnt) - mnt->mnt_ghosts;return (mycount > count); }
@@ -300,7 +300,7 @@ static inline int do_refcount_check(stru* Check if any of these mounts that **do not have submounts** * have more references than 'refcnt'. If so return busy. * -* vfsmount lock must be held for read or write +* vfsmount lock must be held for write */ int propagate_mount_busy (struct vfsmount *mnt,int refcnt) {
fs/internal.h +1 -0
@@ -63,6 +63,7 @@ extern int copy_mount_string(const voidextern void free_vfsmnt (struct vfsmount *);extern struct vfsmount *alloc_vfsmnt (const char *); +extern unsigned int count_mnt_count (struct vfsmount *mnt);extern struct vfsmount *__lookup_mnt (struct vfsmount *,struct dentry *,int );extern void mnt_set_mountpoint (struct vfsmount *,struct dentry *,struct vfsmount *);
fs/libfs.c +1 -0
@@ -241,6 +241,7 @@ int get_sb_pseudo(struct file_system_typd_instantiate (dentry, root); s->s_root = dentry; s->s_flags |=MS_ACTIVE ; + mnt->mnt_flags |=MNT_PSEUDO ;simple_set_mnt (mnt, s);return 0 ;
drivers/mtd/mtdchar.c +1 -0
@@ -1048,6 +1048,7 @@ err_unregister_chdev: static void __exit cleanup_mtdchar(void) {unregister_mtd_user (&mtdchar_notifier); + mtd_inode_mnt &= ~MNT_PSEUDO ;mntput (mtd_inode_mnt);unregister_filesystem (&mtd_inodefs_type);__unregister_chrdev (MTD_CHAR_MAJOR ,0 ,1 <<MINORBITS ,"mtd" );
PATCH 9/52 fs: dcache scale hash
In reply to [email protected]Add a new lock, dcache_hash_lock, to protect the dcache hash table from concurrent modification. d_hash is also protected by d_lock.
--- fs/dcache.c | 35 ++++++++++++++++++++++++----------- include/linux/dcache.h | 3 +++ 2 files changed, 27 insertions(+), 11 deletions(-)
fs/dcache.c +28 -11
@@ -35,12 +35,27 @@#include <linux/hardirq.h> #include "internal.h" +/* +* Usage: +* dcache_hash_lock protects dcache hash table +* +* Ordering: +* dcache_lock +* dentry->d_lock +* dcache_hash_lock +* +* if (dentry1 < dentry2) +* dentry1->d_lock +* dentry2->d_lock +*/ int sysctl_vfs_cache_pressure__read_mostly =100 ;EXPORT_SYMBOL_GPL (sysctl_vfs_cache_pressure); -__cacheline_aligned_in_smp DEFINE_SPINLOCK (dcache_lock ); +__cacheline_aligned_in_smp DEFINE_SPINLOCK (dcache_hash_lock ); +__cacheline_aligned_in_smp DEFINE_SPINLOCK (dcache_lock );__cacheline_aligned_in_smp DEFINE_SEQLOCK (rename_lock ); +EXPORT_SYMBOL (dcache_hash_lock);EXPORT_SYMBOL (dcache_lock);static struct kmem_cache *dentry_cache__read_mostly ;
@@ -1480,17 +1495,20 @@ int d_validate(struct dentry *dentry, st goto out;spin_lock (&dcache_lock); +spin_lock (&dcache_hash_lock); base =d_hash (dparent, dentry->d_name.hash);hlist_for_each (lhp,base) {/* hlist_for_each_entry_rcu() not required for d_hash list * as it is parsed under dcache_lock */ if (dentry == hlist_entry(lhp, struct dentry, d_hash)) { +spin_unlock (&dcache_hash_lock);__dget_locked (dentry);spin_unlock (&dcache_lock);return 1 ; } } +spin_unlock (&dcache_hash_lock);spin_unlock (&dcache_lock); out:return 0 ;
@@ -1567,7 +1585,9 @@ void d_rehash(struct dentry * entry) {spin_lock (&dcache_lock);spin_lock (&entry->d_lock); +spin_lock (&dcache_hash_lock);_d_rehash (entry); +spin_unlock (&dcache_hash_lock);spin_unlock (&entry->d_lock);spin_unlock (&dcache_lock); }
@@ -1647,8 +1667,6 @@ static void switch_names(struct dentry **/ static void d_move_locked (struct dentry * dentry,struct dentry * target) { -struct hlist_head *list; -if (!dentry->d_inode)printk (KERN_WARNING "VFS: moving negative dcache entry\n" );
@@ -1665,14 +1683,11 @@ static void d_move_locked(struct dentry }/* Move the dentry to the target hash queue, if on different bucket */ -if (d_unhashed (dentry)) - goto already_unhashed; - -hlist_del_rcu (&dentry->d_hash); - -already_unhashed: - list =d_hash (target->d_parent, target->d_name.hash); -__d_rehash (dentry, list); +spin_lock (&dcache_hash_lock); +if (!d_unhashed (dentry)) +hlist_del_rcu (&dentry->d_hash); +__d_rehash (dentry,d_hash (target->d_parent, target->d_name.hash)); +spin_unlock (&dcache_hash_lock);/* Unhash the target: dput() will then get rid of it */ __d_drop (target);
@@ -1869,7 +1884,9 @@ struct dentry *d_materialise_unique(stru found_lock:spin_lock (&actual->d_lock); found: +spin_lock (&dcache_hash_lock);_d_rehash (actual); +spin_unlock (&dcache_hash_lock);spin_unlock (&actual->d_lock);spin_unlock (&dcache_lock); out_nolock:
include/linux/dcache.h +3 -0
@@ -188,6 +188,7 @@ d_iput: no no no yes#define DCACHE_CANT_MOUNT 0x0100 +extern spinlock_t dcache_hash_lock;extern spinlock_t dcache_lock;extern seqlock_t rename_lock;
@@ -211,7 +212,9 @@ static inline void __d_drop(struct dentr {if (!(dentry->d_flags &DCACHE_UNHASHED )) { dentry->d_flags |=DCACHE_UNHASHED ; +spin_lock (&dcache_hash_lock);hlist_del_rcu (&dentry->d_hash); +spin_unlock (&dcache_hash_lock); } }
PATCH 10/52 fs: dcache scale lru
In reply to [email protected]Add a new lock, dcache_lru_lock, to protect the dcache hash table from concurrent modification. d_lru is also protected by d_lock.
Move lru scanning out from underneath dcache_lock. XXX Maybe do that in another patch
--- fs/dcache.c | 105 ++++++++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 85 insertions(+), 20 deletions(-)
fs/dcache.c +85 -20
@@ -37,11 +37,19 @@/* * Usage: -* dcache_hash_lock protects dcache hash table +* dcache_hash_lock protects: +* - the dcache hash table +* dcache_lru_lock protects: +* - the dcache lru lists and counters +* d_lock protects: +* - d_flags +* - d_name +* - d_lru * * Ordering: * dcache_lock * dentry->d_lock +* dcache_lru_lock * dcache_hash_lock * * if (dentry1 < dentry2)
@@ -52,6 +60,7 @@ int sysctl_vfs_cache_pressure __read_mosEXPORT_SYMBOL_GPL (sysctl_vfs_cache_pressure);__cacheline_aligned_in_smp DEFINE_SPINLOCK (dcache_hash_lock ); +static __cacheline_aligned_in_smp DEFINE_SPINLOCK (dcache_lru_lock );__cacheline_aligned_in_smp DEFINE_SPINLOCK (dcache_lock );__cacheline_aligned_in_smp DEFINE_SEQLOCK (rename_lock );
@@ -138,37 +147,56 @@ static void dentry_iput(struct dentry * }/* -* dentry_lru_(add|add_tail|del|del_init) must be called with dcache_lock held. +* dentry_lru_(add|add_tail|del|del_init) must be called with d_lock held +* to protect list_empty(d_lru) condition. */ static void dentry_lru_add (struct dentry *dentry) { +spin_lock (&dcache_lru_lock);list_add (&dentry->d_lru, &dentry->d_sb->s_dentry_lru); dentry->d_sb->s_nr_dentry_unused++; dentry_stat.nr_unused++; +spin_unlock (&dcache_lru_lock); }static void dentry_lru_add_tail (struct dentry *dentry) { +spin_lock (&dcache_lru_lock);list_add_tail (&dentry->d_lru, &dentry->d_sb->s_dentry_lru); dentry->d_sb->s_nr_dentry_unused++; dentry_stat.nr_unused++; +spin_unlock (&dcache_lru_lock); +} + +static void __dentry_lru_del (struct dentry *dentry) +{ +list_del (&dentry->d_lru); + dentry->d_sb->s_nr_dentry_unused--; + dentry_stat.nr_unused--; +} + +static void __dentry_lru_del_init (struct dentry *dentry) +{ +list_del_init (&dentry->d_lru); + dentry->d_sb->s_nr_dentry_unused--; + dentry_stat.nr_unused--; }static void dentry_lru_del (struct dentry *dentry) {if (!list_empty (&dentry->d_lru)) { -list_del (&dentry->d_lru); - dentry->d_sb->s_nr_dentry_unused--; - dentry_stat.nr_unused--; +spin_lock (&dcache_lru_lock); +__dentry_lru_del (dentry); +spin_unlock (&dcache_lru_lock); } }static void dentry_lru_del_init (struct dentry *dentry) {if (likely (!list_empty (&dentry->d_lru))) { -list_del_init (&dentry->d_lru); - dentry->d_sb->s_nr_dentry_unused--; - dentry_stat.nr_unused--; +spin_lock (&dcache_lru_lock); +__dentry_lru_del_init (dentry); +spin_unlock (&dcache_lru_lock); } }
@@ -179,6 +207,8 @@ static void dentry_lru_del_init(struct d* The dentry must already be unhashed and removed from the LRU. * * If this is the root of the dentry tree, return NULL. +* +* dcache_lock and d_lock must be held by caller, are dropped by d_kill. */ static struct dentry *d_kill (struct dentry *dentry) __releases(dentry->d_lock)
@@ -333,11 +363,19 @@ int d_invalidate(struct dentry * dentry)EXPORT_SYMBOL (d_invalidate);/* This should be called _only_ with dcache_lock held */ +static inline struct dentry *__dget_locked_dlock (struct dentry *dentry) +{ +atomic_inc (&dentry->d_count); +dentry_lru_del_init (dentry); +return dentry; +}static inline struct dentry *__dget_locked (struct dentry *dentry) {atomic_inc (&dentry->d_count); +spin_lock (&dentry->d_lock);dentry_lru_del_init (dentry); +spin_unlock (&dentry->d_lock);return dentry; }
@@ -416,7 +454,7 @@ restart:list_for_each_entry (dentry, &inode->i_dentry, d_alias) {spin_lock (&dentry->d_lock);if (!atomic_read (&dentry->d_count)) { -__dget_locked (dentry); +__dget_locked_dlock (dentry);__d_drop (dentry);spin_unlock (&dentry->d_lock);spin_unlock (&dcache_lock);
@@ -449,17 +487,18 @@ static void prune_one_dentry(struct dent* Prune ancestors. Locking is simpler than in dput(), * because dcache_lock needs to be taken anyway. */ -spin_lock (&dcache_lock);while (dentry) { -if (!atomic_dec_and_lock (&dentry->d_count, &dentry->d_lock)) +spin_lock (&dcache_lock); +if (!atomic_dec_and_lock (&dentry->d_count, &dentry->d_lock)) { +spin_unlock (&dcache_lock);return ; + }if (dentry->d_op && dentry->d_op->d_delete) dentry->d_op->d_delete (dentry);dentry_lru_del_init (dentry);__d_drop (dentry); dentry =d_kill (dentry); -spin_lock (&dcache_lock); } }
@@ -480,10 +519,11 @@ static void __shrink_dcache_sb(struct suBUG_ON (!sb);BUG_ON ((flags &DCACHE_REFERENCED ) && count ==NULL ); -spin_lock (&dcache_lock);if (count !=NULL )/* called from prune_dcache() and shrink_dcache_parent() */ cnt = *count; +relock: +spin_lock (&dcache_lru_lock); restart:if (count ==NULL )list_splice_init (&sb->s_dentry_lru, &tmp);
@@ -493,7 +533,10 @@ restart: struct dentry, d_lru);BUG_ON (dentry->d_sb != sb); -spin_lock (&dentry->d_lock); +if (!spin_trylock (&dentry->d_lock)) { +spin_unlock (&dcache_lru_lock); + goto relock; + }/* * If we are honouring the DCACHE_REFERENCED flag and * the dentry has this flag set, don't free it. Clear
@@ -511,13 +554,22 @@ restart:if (!cnt)break ; } -cond_resched_lock (&dcache_lock); +cond_resched_lock (&dcache_lru_lock); } } +spin_unlock (&dcache_lru_lock); + +spin_lock (&dcache_lock); +again: +spin_lock (&dcache_lru_lock);/* lru_lock also protects tmp list */ while (!list_empty (&tmp)) { dentry = list_entry(tmp.prev, struct dentry, d_lru); -dentry_lru_del_init (dentry); -spin_lock (&dentry->d_lock); + +if (!spin_trylock (&dentry->d_lock)) { +spin_unlock (&dcache_lru_lock); + goto again; + } +__dentry_lru_del_init (dentry);/* * We found an inuse dentry which was not removed from * the LRU because of laziness during lookup. Do not free
@@ -527,17 +579,22 @@ restart:spin_unlock (&dentry->d_lock);continue ; } + +spin_unlock (&dcache_lru_lock);prune_one_dentry (dentry); -/* dentry->d_lock was dropped in prune_one_dentry() */ -cond_resched_lock (&dcache_lock); +/* dcache_lock and dentry->d_lock dropped */ +spin_lock (&dcache_lock); +spin_lock (&dcache_lru_lock); } +spin_unlock (&dcache_lock); +if (count ==NULL && !list_empty (&sb->s_dentry_lru)) goto restart;if (count !=NULL ) *count = cnt;if (!list_empty (&referenced))list_splice (&referenced, &sb->s_dentry_lru); -spin_unlock (&dcache_lock); +spin_unlock (&dcache_lru_lock); }/**
@@ -645,7 +702,9 @@ static void shrink_dcache_for_umount_sub/* detach this root from the system */ spin_lock (&dcache_lock); +spin_lock (&dentry->d_lock);dentry_lru_del_init (dentry); +spin_unlock (&dentry->d_lock);__d_drop (dentry);spin_unlock (&dcache_lock);
@@ -659,7 +718,9 @@ static void shrink_dcache_for_umount_subspin_lock (&dcache_lock);list_for_each_entry (loop, &dentry->d_subdirs, d_u.d_child) { +spin_lock (&loop->d_lock);dentry_lru_del_init (loop); +spin_unlock (&loop->d_lock);__d_drop (loop);cond_resched_lock (&dcache_lock); }
@@ -843,13 +904,17 @@ resume: struct dentry *dentry = list_entry(tmp, struct dentry, d_u.d_child); next = tmp->next; +spin_lock (&dentry->d_lock);dentry_lru_del_init (dentry); +spin_unlock (&dentry->d_lock);/* * move only zero ref count dentries to the end * of the unused list for prune_dcache */ if (!atomic_read (&dentry->d_count)) { +spin_lock (&dentry->d_lock);dentry_lru_add_tail (dentry); +spin_unlock (&dentry->d_lock); found++; }
PATCH 11/52 fs: dcache scale nr_dentry
In reply to [email protected]Make dentry_stat_t.nr_dentry an atomic_t type, and move it from under dcache_lock.
--- fs/dcache.c | 20 +++++++++----------- include/linux/dcache.h | 4 ++-- kernel/sysctl.c | 6 ++++++ 3 files changed, 17 insertions(+), 13 deletions(-)
fs/dcache.c +9 -11
@@ -88,6 +88,7 @@ static struct hlist_head *dentry_hashtab/* Statistics gathering. */ struct dentry_stat_t dentry_stat = { + .nr_dentry =ATOMIC_INIT (0 ), .age_limit =45 , };
@@ -106,11 +107,11 @@ static void d_callback(struct rcu_head * }/* -* no dcache_lock, please. The caller must decrement dentry_stat.nr_dentry -* inside dcache_lock. +* no dcache_lock, please. */ static void d_free (struct dentry *dentry) { +atomic_dec (&dentry_stat.nr_dentry);if (dentry->d_op && dentry->d_op->d_release) dentry->d_op->d_release (dentry);/* if dentry was never inserted into hash, immediate free is OK */
@@ -217,7 +218,6 @@ static struct dentry *d_kill(struct dentstruct dentry *parent;list_del (&dentry->d_u.d_child); - dentry_stat.nr_dentry--;/* For d_free, below */ /*drops the locks, at that point nobody can reach this dentry */ dentry_iput (dentry);if (IS_ROOT (dentry))
@@ -787,10 +787,7 @@ static void shrink_dcache_for_umount_sub struct dentry, d_u.d_child); } out: -/* several dentries were freed, need to correct nr_dentry */ -spin_lock (&dcache_lock); - dentry_stat.nr_dentry -= detached; -spin_unlock (&dcache_lock); +return ; }/*
@@ -1047,11 +1044,12 @@ struct dentry *d_alloc(struct dentry * pINIT_LIST_HEAD (&dentry->d_u.d_child); } -spin_lock (&dcache_lock); -if (parent) +if (parent) { +spin_lock (&dcache_lock);list_add (&dentry->d_u.d_child, &parent->d_subdirs); - dentry_stat.nr_dentry++; -spin_unlock (&dcache_lock); +spin_unlock (&dcache_lock); + } +atomic_inc (&dentry_stat.nr_dentry);return dentry; }
include/linux/dcache.h +2 -2
@@ -37,8 +37,8 @@ struct qstr { };struct dentry_stat_t { -int nr_dentry; -int nr_unused; +atomic_t nr_dentry; +int nr_unused;/* protected by dcache_lru_lock */ int age_limit;/* age in seconds */ int want_pages;/* pages requested by system */ int dummy[2 ];
kernel/sysctl.c +6 -0
@@ -1371,6 +1371,12 @@ static struct ctl_table fs_table[] = { .extra2 = &sysctl_nr_open_max, }, { +/* +* dentry_stat has an atomic_t member, so this is a bit of +* a hack, but it works for the moment, and I won't bother +* changing it now because we'll probably want to change to +* a more scalable counter anyway. +*/ .procname = "dentry-state", .data = &dentry_stat, .maxlen = 6*sizeof(int),
PATCH 12/52 fs: dcache scale dentry refcount
In reply to [email protected]Make d_count non-atomic and protect it with d_lock. This allows us to ensure a 0 refcount dentry remains 0 without dcache_lock. It is also fairly natural when we start protecting many other dentry members with d_lock.
--- arch/powerpc/platforms/cell/spufs/inode.c | 2 drivers/infiniband/hw/ipath/ipath_fs.c | 2 fs/autofs4/expire.c | 8 +- fs/autofs4/root.c | 6 - fs/coda/dir.c | 2 fs/configfs/dir.c | 3 fs/configfs/inode.c | 2 fs/dcache.c | 107 ++++++++++++++++++++++-------- fs/ecryptfs/inode.c | 2 fs/exportfs/expfs.c | 9 ++ fs/hpfs/namei.c | 2 fs/locks.c | 2 fs/namei.c | 2 fs/nfs/dir.c | 12 +-- fs/nfsd/vfs.c | 5 - fs/notify/fsnotify.c | 11 ++- fs/notify/inotify/inotify.c | 14 +++ fs/smbfs/dir.c | 8 +- fs/smbfs/proc.c | 8 +- include/linux/dcache.h | 29 ++++---- kernel/cgroup.c | 2 21 files changed, 162 insertions(+), 76 deletions(-)
fs/dcache.c +82 -27
@@ -45,6 +45,7 @@* - d_flags * - d_name * - d_lru +* - d_count * * Ordering: * dcache_lock
@@ -112,6 +113,7 @@ static void d_callback(struct rcu_head *static void d_free (struct dentry *dentry) {atomic_dec (&dentry_stat.nr_dentry); +BUG_ON (dentry->d_count);if (dentry->d_op && dentry->d_op->d_release) dentry->d_op->d_release (dentry);/* if dentry was never inserted into hash, immediate free is OK */
@@ -263,13 +265,23 @@ void dput(struct dentry *dentry)return ; repeat: -if (atomic_read (&dentry->d_count) ==1 ) +if (dentry->d_count ==1 )might_sleep (); -if (!atomic_dec_and_lock (&dentry->d_count, &dcache_lock)) -return ; -spin_lock (&dentry->d_lock); -if (atomic_read (&dentry->d_count)) { +if (dentry->d_count ==1 ) { +if (!spin_trylock (&dcache_lock)) { +/* +* Something of a livelock possibility we could avoid +* by taking dcache_lock and trying again, but we +* want to reduce dcache_lock anyway so this will +* get improved. +*/ +spin_unlock (&dentry->d_lock); + goto repeat; + } + } + dentry->d_count--; +if (dentry->d_count) {spin_unlock (&dentry->d_lock);spin_unlock (&dcache_lock);return ;
@@ -347,7 +359,7 @@ int d_invalidate(struct dentry * dentry)* working directory or similar). */ spin_lock (&dentry->d_lock); -if (atomic_read (&dentry->d_count) >1 ) { +if (dentry->d_count >1 ) {if (dentry->d_inode &&S_ISDIR (dentry->d_inode->i_mode)) {spin_unlock (&dentry->d_lock);spin_unlock (&dcache_lock);
@@ -362,29 +374,60 @@ int d_invalidate(struct dentry * dentry) }EXPORT_SYMBOL (d_invalidate); -/* This should be called _only_ with dcache_lock held */ +/* This must be called with dcache_lock and d_lock held */ static inline struct dentry *__dget_locked_dlock (struct dentry *dentry) { -atomic_inc (&dentry->d_count); + dentry->d_count++;dentry_lru_del_init (dentry);return dentry; } +/* This should be called _only_ with dcache_lock held */ static inline struct dentry *__dget_locked (struct dentry *dentry) { -atomic_inc (&dentry->d_count);spin_lock (&dentry->d_lock); -dentry_lru_del_init (dentry); +__dget_locked_dlock (dentry);spin_unlock (&dentry->d_lock);return dentry; } +struct dentry *dget_locked_dlock (struct dentry *dentry) +{ +return __dget_locked_dlock (dentry); +} +struct dentry *dget_locked (struct dentry *dentry) {return __dget_locked (dentry); }EXPORT_SYMBOL (dget_locked); +struct dentry *dget_parent (struct dentry *dentry) +{ +struct dentry *ret; + +repeat: +spin_lock (&dentry->d_lock); + ret = dentry->d_parent; +if (!ret) + goto out; +if (dentry == ret) { + ret->d_count++; + goto out; + } +if (!spin_trylock (&ret->d_lock)) { +spin_unlock (&dentry->d_lock); + goto repeat; + } +BUG_ON (!ret->d_count); + ret->d_count++; +spin_unlock (&ret->d_lock); +out: +spin_unlock (&dentry->d_lock); +return ret; +} +EXPORT_SYMBOL (dget_parent); +/** * d_find_alias - grab a hashed alias of inode * @inode: inode in question
@@ -453,7 +496,7 @@ restart:spin_lock (&dcache_lock);list_for_each_entry (dentry, &inode->i_dentry, d_alias) {spin_lock (&dentry->d_lock); -if (!atomic_read (&dentry->d_count)) { +if (!dentry->d_count) {__dget_locked_dlock (dentry);__d_drop (dentry);spin_unlock (&dentry->d_lock);
@@ -489,7 +532,10 @@ static void prune_one_dentry(struct dent*/ while (dentry) {spin_lock (&dcache_lock); -if (!atomic_dec_and_lock (&dentry->d_count, &dentry->d_lock)) { +spin_lock (&dentry->d_lock); + dentry->d_count--; +if (dentry->d_count) { +spin_unlock (&dentry->d_lock);spin_unlock (&dcache_lock);return ; }
@@ -575,7 +621,7 @@ again:* the LRU because of laziness during lookup. Do not free * it - just keep it off the LRU list. */ -if (atomic_read (&dentry->d_count)) { +if (dentry->d_count) {spin_unlock (&dentry->d_lock);continue ; }
@@ -736,7 +782,7 @@ static void shrink_dcache_for_umount_sub do { struct inode *inode; - if (atomic_read(&dentry->d_count) != 0) { + if (dentry->d_count != 0) { printk(KERN_ERR "BUG: Dentry %p{i=%lx,n=%s}" " still in use (%d)"
@@ -745,7 +791,7 @@ static void shrink_dcache_for_umount_sub dentry->d_inode ? dentry->d_inode->i_ino :0UL , dentry->d_name.name, -atomic_read (&dentry->d_count), + dentry->d_count, dentry->d_sb->s_type->name, dentry->d_sb->s_id);BUG ();
@@ -755,7 +801,9 @@ static void shrink_dcache_for_umount_sub parent =NULL ; else { parent = dentry->d_parent; -atomic_dec (&parent->d_count); +spin_lock (&parent->d_lock); + parent->d_count--; +spin_unlock (&parent->d_lock); }list_del (&dentry->d_u.d_child);
@@ -810,7 +858,9 @@ void shrink_dcache_for_umount(struct sup dentry = sb->s_root; sb->s_root =NULL ; -atomic_dec (&dentry->d_count); +spin_lock (&dentry->d_lock); + dentry->d_count--; +spin_unlock (&dentry->d_lock);shrink_dcache_for_umount_subtree (dentry);while (!hlist_empty (&sb->s_anon)) {
@@ -903,17 +953,15 @@ resume:spin_lock (&dentry->d_lock);dentry_lru_del_init (dentry); -spin_unlock (&dentry->d_lock);/* * move only zero ref count dentries to the end * of the unused list for prune_dcache */ -if (!atomic_read (&dentry->d_count)) { -spin_lock (&dentry->d_lock); +if (!dentry->d_count) {dentry_lru_add_tail (dentry); -spin_unlock (&dentry->d_lock); found++; } +spin_unlock (&dentry->d_lock);/* * We can return to the caller if we have found some (this
@@ -1023,7 +1071,7 @@ struct dentry *d_alloc(struct dentry * pmemcpy (dname, name->name, name->len); dname[name->len] =0 ; -atomic_set (&dentry->d_count,1 ); + dentry->d_count =1 ; dentry->d_flags =DCACHE_UNHASHED ;spin_lock_init (&dentry->d_lock); dentry->d_inode =NULL ;
@@ -1497,7 +1545,7 @@ struct dentry * __d_lookup(struct dentry goto next; } -atomic_inc (&dentry->d_count); + dentry->d_count++; found = dentry;spin_unlock (&dentry->d_lock);break ;
@@ -1558,6 +1606,7 @@ int d_validate(struct dentry *dentry, st goto out;spin_lock (&dcache_lock); +spin_lock (&dentry->d_lock);spin_lock (&dcache_hash_lock); base =d_hash (dparent, dentry->d_name.hash);hlist_for_each (lhp,base) {
@@ -1566,12 +1615,14 @@ int d_validate(struct dentry *dentry, st*/ if (dentry == hlist_entry(lhp, struct dentry, d_hash)) {spin_unlock (&dcache_hash_lock); -__dget_locked (dentry); +__dget_locked_dlock (dentry); +spin_unlock (&dentry->d_lock);spin_unlock (&dcache_lock);return 1 ; } }spin_unlock (&dcache_hash_lock); +spin_unlock (&dentry->d_lock);spin_unlock (&dcache_lock); out:return 0 ;
@@ -1608,7 +1659,7 @@ void d_delete(struct dentry * dentry)spin_lock (&dcache_lock);spin_lock (&dentry->d_lock); isdir =S_ISDIR (dentry->d_inode->i_mode); -if (atomic_read (&dentry->d_count) ==1 ) { +if (dentry->d_count ==1 ) { dentry->d_flags &= ~DCACHE_CANT_MOUNT ;dentry_iput (dentry);fsnotify_nameremove (dentry, isdir);
@@ -2314,11 +2365,15 @@ resume: this_parent = dentry; goto repeat; } -atomic_dec (&dentry->d_count); +spin_lock (&dentry->d_lock); + dentry->d_count--; +spin_unlock (&dentry->d_lock); }if (this_parent != root) { next = this_parent->d_u.d_child.next; -atomic_dec (&this_parent->d_count); +spin_lock (&this_parent->d_lock); + this_parent->d_count--; +spin_unlock (&this_parent->d_lock); this_parent = this_parent->d_parent; goto resume; }
include/linux/dcache.h +15 -14
@@ -87,7 +87,7 @@ full_name_hash(const unsigned char *name#endif struct dentry { -atomic_t d_count; +unsigned int d_count;/* protected by d_lock */ unsigned int d_flags;/* protected by d_lock */ spinlock_t d_lock;/* per dentry lock */ int d_mounted;
@@ -334,17 +334,28 @@ extern char *dentry_path(struct dentry ** needs and they take necessary precautions) you should hold dcache_lock * and call dget_locked() instead of dget(). */ - +static inline struct dentry *dget_dlock (struct dentry *dentry) +{ +if (dentry) { +BUG_ON (!dentry->d_count); + dentry->d_count++; + } +return dentry; +}static inline struct dentry *dget (struct dentry *dentry) {if (dentry) { -BUG_ON (!atomic_read (&dentry->d_count)); -atomic_inc (&dentry->d_count); +spin_lock (&dentry->d_lock); +dget_dlock (dentry); +spin_unlock (&dentry->d_lock); }return dentry; }extern struct dentry *dget_locked (struct dentry *); +extern struct dentry *dget_locked_dlock (struct dentry *); + +extern struct dentry *dget_parent (struct dentry *dentry);/** * d_unhashed - is dentry hashed
@@ -375,16 +386,6 @@ static inline void dont_mount(struct denspin_unlock (&dentry->d_lock); } -static inline struct dentry *dget_parent (struct dentry *dentry) -{ -struct dentry *ret; - -spin_lock (&dentry->d_lock); - ret =dget (dentry->d_parent); -spin_unlock (&dentry->d_lock); -return ret; -} -extern void dput (struct dentry *);static inline int d_mountpoint (struct dentry *dentry)
fs/configfs/dir.c +1 -2
@@ -399,8 +399,7 @@ static void remove_dir(struct dentry * dif (d->d_inode)simple_rmdir (parent->d_inode,d); -pr_debug (" o %s removing done (%d)\n" ,d->d_name.name, -atomic_read (&d->d_count)); +pr_debug (" o %s removing done (%d)\n" ,d->d_name.name, d->d_count);dput (parent); }
fs/locks.c +1 -1
@@ -1375,7 +1375,7 @@ int generic_setlease(struct file *filp,if ((arg ==F_RDLCK ) && (atomic_read (&inode->i_writecount) >0 )) goto out;if ((arg ==F_WRLCK ) - && ((atomic_read (&dentry->d_count) >1 ) + && (dentry->d_count >1 || (atomic_read (&inode->i_count) >1 ))) goto out; }
fs/namei.c +1 -1
@@ -2154,7 +2154,7 @@ void dentry_unhash(struct dentry *dentryshrink_dcache_parent (dentry);spin_lock (&dcache_lock);spin_lock (&dentry->d_lock); -if (atomic_read (&dentry->d_count) ==2 ) +if (dentry->d_count ==2 )__d_drop (dentry);spin_unlock (&dentry->d_lock);spin_unlock (&dcache_lock);
fs/autofs4/expire.c +4 -4
@@ -198,7 +198,7 @@ static int autofs4_tree_busy(struct vfsmelse ino_count++; -if (atomic_read (&p->d_count) > ino_count) { +if (p->d_count > ino_count) { top_ino->last_used = jiffies;dput (p);return 1 ;
@@ -347,7 +347,7 @@ struct dentry *autofs4_expire_indirect(s/* Path walk currently on this dentry? */ ino_count =atomic_read (&ino->count) +2 ; -if (atomic_read (&dentry->d_count) > ino_count) +if (dentry->d_count > ino_count) goto next;/* Can we umount this guy */
@@ -369,7 +369,7 @@ struct dentry *autofs4_expire_indirect(sif (!exp_leaves) {/* Path walk currently on this dentry? */ ino_count =atomic_read (&ino->count) +1 ; -if (atomic_read (&dentry->d_count) > ino_count) +if (dentry->d_count > ino_count) goto next;if (!autofs4_tree_busy (mnt, dentry, timeout, do_now)) {
@@ -383,7 +383,7 @@ struct dentry *autofs4_expire_indirect(s }else {/* Path walk currently on this dentry? */ ino_count =atomic_read (&ino->count) +1 ; -if (atomic_read (&dentry->d_count) > ino_count) +if (dentry->d_count > ino_count) goto next; expired =autofs4_check_leaves (mnt, dentry, timeout, do_now);
fs/coda/dir.c +1 -1
@@ -612,7 +612,7 @@ static int coda_dentry_revalidate(structif (cii->c_flags &C_FLUSH )coda_flag_inode_children (inode,C_FLUSH ); -if (atomic_read (&de->d_count) >1 ) +if (de->d_count >1 )/* pretend it's valid, but don't change the flags */ goto out;
fs/ecryptfs/inode.c +1 -1
@@ -255,7 +255,7 @@ int ecryptfs_lookup_and_interpose_lower( ecryptfs_dentry->d_parent)); lower_inode = lower_dentry->d_inode;fsstack_copy_attr_atime (ecryptfs_dir_inode, lower_dir_dentry->d_inode); -BUG_ON (!atomic_read (&lower_dentry->d_count)); +BUG_ON (!lower_dentry->d_count);ecryptfs_set_dentry_private (ecryptfs_dentry,kmem_cache_alloc (ecryptfs_dentry_info_cache,GFP_KERNEL ));
fs/nfs/dir.c +5 -5
@@ -1357,7 +1357,7 @@ static int nfs_sillyrename(struct inodedfprintk (VFS ,"NFS: silly-rename(%s/%s, ct=%d)\n" , dentry->d_parent->d_name.name, dentry->d_name.name, -atomic_read (&dentry->d_count)); + dentry->d_count);nfs_inc_stats (dir,NFSIOS_SILLYRENAME );/*
@@ -1466,7 +1466,7 @@ static int nfs_unlink(struct inode *dir,spin_lock (&dcache_lock);spin_lock (&dentry->d_lock); -if (atomic_read (&dentry->d_count) >1 ) { +if (dentry->d_count >1 ) {spin_unlock (&dentry->d_lock);spin_unlock (&dcache_lock);/* Start asynchronous writeout of the inode */
@@ -1614,7 +1614,7 @@ static int nfs_rename(struct inode *old_dfprintk (VFS ,"NFS: rename(%s/%s -> %s/%s, ct=%d)\n" , old_dentry->d_parent->d_name.name, old_dentry->d_name.name, new_dentry->d_parent->d_name.name, new_dentry->d_name.name, -atomic_read (&new_dentry->d_count)); + new_dentry->d_count);/* * For non-directories, check whether the target is busy and if so,
@@ -1632,7 +1632,7 @@ static int nfs_rename(struct inode *old_ rehash = new_dentry; } -if (atomic_read (&new_dentry->d_count) >2 ) { +if (new_dentry->d_count >2 ) {int err;/* copy the target dentry's name */
@@ -1655,7 +1655,7 @@ static int nfs_rename(struct inode *old_/* * ... prune child dentries and writebacks if needed. */ -if (atomic_read (&old_dentry->d_count) >1 ) { +if (old_dentry->d_count >1 ) {if (S_ISREG (old_inode->i_mode))nfs_wb_all (old_inode);shrink_dcache_parent (old_dentry);
fs/nfsd/vfs.c +2 -3
@@ -1752,8 +1752,7 @@ nfsd_rename(struct svc_rqst *rqstp, stru goto out_dput_new;if (svc_msnfs (ffhp) && - ((atomic_read (&odentry->d_count) >1 ) - || (atomic_read (&ndentry->d_count) >1 ))) { + ((odentry->d_count >1 ) || (ndentry->d_count >1 ))) { host_err = -EPERM ; goto out_dput_new; }
@@ -1839,7 +1838,7 @@ nfsd_unlink(struct svc_rqst *rqstp, struif (type !=S_IFDIR ) {/* It's UNLINK */ #ifdef MSNFS if ((fhp->fh_export->ex_flags &NFSEXP_MSNFS ) && - (atomic_read (&rdentry->d_count) >1 )) { + (rdentry->d_count >1 )) { host_err = -EPERM ; } else#endif
fs/exportfs/expfs.c +8 -1
@@ -74,12 +74,19 @@ static struct dentry * find_disconnected_root(struct dentry *dentry) {dget (dentry); +again:spin_lock (&dentry->d_lock);while (!IS_ROOT (dentry) && (dentry->d_parent->d_flags &DCACHE_DISCONNECTED )) {struct dentry *parent = dentry->d_parent; -dget (parent); + +if (!spin_trylock (&parent->d_lock)) { +spin_unlock (&dentry->d_lock); + goto again; + } +dget_dlock (parent);spin_unlock (&dentry->d_lock); +spin_unlock (&parent->d_lock);dput (dentry); dentry = parent;spin_lock (&dentry->d_lock);
fs/notify/inotify/inotify.c +12 -2
@@ -335,18 +335,28 @@ void inotify_dentry_parent_queue_event(sif (!(dentry->d_flags &DCACHE_INOTIFY_PARENT_WATCHED ))return ; +again:spin_lock (&dentry->d_lock); parent = dentry->d_parent; +if (parent != dentry && !spin_trylock (&parent->d_lock)) { +spin_unlock (&dentry->d_lock); + goto again; + } inode = parent->d_inode;if (inotify_inode_watched (inode)) { -dget (parent); +dget_dlock (parent);spin_unlock (&dentry->d_lock); +if (parent != dentry) +spin_unlock (&parent->d_lock);inotify_inode_queue_event (inode, mask, cookie, name, dentry->d_inode);dput (parent); - }else + }else {spin_unlock (&dentry->d_lock); +if (parent != dentry) +spin_unlock (&parent->d_lock); + } }EXPORT_SYMBOL_GPL (inotify_dentry_parent_queue_event);
fs/smbfs/dir.c +7 -1
@@ -406,6 +406,7 @@ void smb_renew_times(struct dentry * dentry) {dget (dentry); +again:spin_lock (&dentry->d_lock);for (;;) {struct dentry *parent;
@@ -414,8 +415,13 @@ smb_renew_times(struct dentry * dentry)if (IS_ROOT (dentry))break ; parent = dentry->d_parent; -dget (parent); +if (!spin_trylock (&parent->d_lock)) { +spin_unlock (&dentry->d_lock); + goto again; + } +dget_dlock (parent);spin_unlock (&dentry->d_lock); +spin_unlock (&parent->d_lock);dput (dentry); dentry = parent;spin_lock (&dentry->d_lock);
fs/smbfs/proc.c +7 -1
@@ -332,6 +332,7 @@ static int smb_build_path(struct smb_sb_* and store it in reversed order [see reverse_string()] */ dget (entry); +again:spin_lock (&entry->d_lock);while (!IS_ROOT (entry)) {struct dentry *parent;
@@ -350,6 +351,7 @@ static int smb_build_path(struct smb_sb_dput (entry);return len; } +reverse_string (path, len); path += len;if (unicode) {
@@ -361,7 +363,11 @@ static int smb_build_path(struct smb_sb_ maxlen -= len+1 ; parent = entry->d_parent; -dget (parent); +if (!spin_trylock (&parent->d_lock)) { +spin_unlock (&entry->d_lock); + goto again; + } +dget_dlock (parent);spin_unlock (&entry->d_lock);dput (entry); entry = parent;
kernel/cgroup.c +0 -2
@@ -3545,9 +3545,7 @@ again:list_del (&cgrp->sibling);cgroup_unlock_hierarchy (cgrp->root); -spin_lock (&cgrp->dentry->d_lock); d =dget (cgrp->dentry); -spin_unlock (&d->d_lock);cgroup_d_remove_dir (d);dput (d);
arch/powerpc/platforms/cell/spufs/inode.c +1 -1
@@ -161,7 +161,7 @@ static void spufs_prune_dir(struct dentrspin_lock (&dcache_lock);spin_lock (&dentry->d_lock);if (!(d_unhashed (dentry)) && dentry->d_inode) { -dget_locked (dentry); +dget_locked_dlock (dentry);__d_drop (dentry);spin_unlock (&dentry->d_lock);simple_unlink (dir->d_inode, dentry);
drivers/infiniband/hw/ipath/ipath_fs.c +1 -1
@@ -276,7 +276,7 @@ static int remove_file(struct dentry *paspin_lock (&dcache_lock);spin_lock (&tmp->d_lock);if (!(d_unhashed (tmp) && tmp->d_inode)) { -dget_locked (tmp); +dget_locked_dlock (tmp);__d_drop (tmp);spin_unlock (&tmp->d_lock);spin_unlock (&dcache_lock);
fs/configfs/inode.c +1 -1
@@ -252,7 +252,7 @@ void configfs_drop_dentry(struct configfspin_lock (&dcache_lock);spin_lock (&dentry->d_lock);if (!(d_unhashed (dentry) && dentry->d_inode)) { -dget_locked (dentry); +dget_locked_dlock (dentry);__d_drop (dentry);spin_unlock (&dentry->d_lock);spin_unlock (&dcache_lock);
fs/notify/fsnotify.c +9 -2
@@ -88,13 +88,18 @@ void __fsnotify_parent(struct dentry *deif (!(dentry->d_flags &DCACHE_FSNOTIFY_PARENT_WATCHED ))return ; +again:spin_lock (&dentry->d_lock); parent = dentry->d_parent; +if (parent != dentry && !spin_trylock (&parent->d_lock)) { +spin_unlock (&dentry->d_lock); + goto again; + } p_inode = parent->d_inode;if (fsnotify_inode_watches_children (p_inode)) {if (p_inode->i_fsnotify_mask & mask) { -dget (parent); +dget_dlock (parent); send = true; } }else {
@@ -104,11 +109,13 @@ void __fsnotify_parent(struct dentry *de* children and update their d_flags to let them know p_inode * doesn't care about them any more. */ -dget (parent); +dget_dlock (parent); should_update_children = true; }spin_unlock (&dentry->d_lock); +if (parent != dentry) +spin_unlock (&parent->d_lock);if (send) {/* we are notifying a parent so come up with the new mask which
fs/ceph/dir.c +3 -1
@@ -149,7 +149,9 @@ more: di =ceph_dentry (dentry); } -atomic_inc (&dentry->d_count); +spin_lock (&dentry->d_lock); + dentry->d_count++; +spin_unlock (&dentry->d_lock);spin_unlock (&dcache_lock);spin_unlock (&inode->i_lock);
fs/ceph/inode.c +2 -2
@@ -863,8 +863,8 @@ static struct dentry *splice_dentry(stru }else if (realdn) {dout ("dn %p (%d) spliced with %p (%d) " "inode %p ino %llx.%llx\n" , - dn,atomic_read (&dn->d_count), - realdn,atomic_read (&realdn->d_count), + dn, dn->d_count, + realdn, realdn->d_count, realdn->d_inode,ceph_vinop (realdn->d_inode));dput (dn); dn = realdn;
fs/ceph/mds_client.c +1 -1
@@ -1371,7 +1371,7 @@ retry: *base =ceph_ino (temp->d_inode); *plen = len;dout ("build_path on %p %d built %llx '%.*s'\n" , - dentry,atomic_read (&dentry->d_count), *base, len, path); + dentry, dentry->d_count, *base, len, path);return path; }
drivers/infiniband/hw/qib/qib_fs.c +1 -1
@@ -454,7 +454,7 @@ static int remove_file(struct dentry *paspin_lock (&dcache_lock);spin_lock (&tmp->d_lock);if (!(d_unhashed (tmp) && tmp->d_inode)) { -dget_locked (tmp); +dget_locked_dlock (tmp);__d_drop (tmp);spin_unlock (&tmp->d_lock);spin_unlock (&dcache_lock);
PATCH 13/52 fs: dcache scale d_unhashed
In reply to [email protected]Protect d_unhashed(dentry) condition with d_lock.
--- arch/powerpc/platforms/cell/spufs/inode.c | 3 + fs/configfs/configfs_internal.h | 2 + fs/dcache.c | 58 ++++++++++++++++++++++++------ fs/libfs.c | 29 ++++++++++----- fs/ocfs2/dcache.c | 5 ++ fs/seq_file.c | 3 + security/tomoyo/realpath.c | 2 + 7 files changed, 81 insertions(+), 21 deletions(-)
fs/libfs.c +20 -9
@@ -16,6 +16,11 @@#include <asm/uaccess.h> +static inline int simple_positive (struct dentry *dentry) +{ +return dentry->d_inode && !d_unhashed (dentry); +} +int simple_getattr (struct vfsmount *mnt,struct dentry *dentry,struct kstat *stat) {
@@ -100,8 +105,10 @@ loff_t dcache_dir_lseek(struct file *filwhile (n && p != &file->f_path.dentry->d_subdirs) {struct dentry *next; next = list_entry(p, struct dentry, d_u.d_child); -if (!d_unhashed (next) && next->d_inode) +spin_lock (&next->d_lock); +if (simple_positive (next)) n--; +spin_unlock (&next->d_lock); p = p->next; }list_add_tail (&cursor->d_u.d_child, p);
@@ -155,9 +162,13 @@ int dcache_readdir(struct file * filp, vfor (p=q->next; p != &dentry->d_subdirs; p=p->next) {struct dentry *next; next = list_entry(p, struct dentry, d_u.d_child); -if (d_unhashed (next) || !next->d_inode) +spin_lock_nested (&next->d_lock,DENTRY_D_LOCK_NESTED ); +if (!simple_positive (next)) { +spin_unlock (&next->d_lock);continue ; + } +spin_unlock (&next->d_lock);spin_unlock (&dcache_lock); if (filldir(dirent, next->d_name.name, next->d_name.len, filp->f_pos,
@@ -262,20 +273,20 @@ int simple_link(struct dentry *old_dentrreturn 0 ; } -static inline int simple_positive (struct dentry *dentry) -{ -return dentry->d_inode && !d_unhashed (dentry); -} -int simple_empty (struct dentry *dentry) {struct dentry *child;int ret =0 ;spin_lock (&dcache_lock); -list_for_each_entry (child, &dentry->d_subdirs, d_u.d_child) -if (simple_positive (child)) +list_for_each_entry (child, &dentry->d_subdirs, d_u.d_child) { +spin_lock_nested (&child->d_lock,DENTRY_D_LOCK_NESTED ); +if (simple_positive (child)) { +spin_unlock (&child->d_lock); goto out; + } +spin_unlock (&child->d_lock); + } ret =1 ; out:spin_unlock (&dcache_lock);
fs/seq_file.c +5 -0
@@ -6,10 +6,13 @@*/ #include <linux/fs.h> +#include <linux/mount.h> #include <linux/module.h> #include <linux/seq_file.h> #include <linux/slab.h> +#include "internal.h" +#include <asm/uaccess.h> #include <asm/page.h>
@@ -463,7 +466,9 @@ int seq_path_root(struct seq_file *m, stchar *p;spin_lock (&dcache_lock); +br_read_lock (vfsmount_lock); p =__d_path (path, root, buf, size); +br_read_unlock (vfsmount_lock);spin_unlock (&dcache_lock); res =PTR_ERR (p);if (!IS_ERR (p)) {
fs/dcache.c +59 -12
234 lines not on this page · raw message
arch/powerpc/platforms/cell/spufs/inode.c +3 -0
@@ -165,6 +165,9 @@ static void spufs_prune_dir(struct dentr__d_drop (dentry);spin_unlock (&dentry->d_lock);simple_unlink (dir->d_inode, dentry); +/* XXX: what is dcache_lock protecting here? Other +* filesystems (IB, configfs) release dcache_lock +* before unlink */ spin_unlock (&dcache_lock);dput (dentry); }else {
fs/configfs/configfs_internal.h +2 -0
@@ -121,6 +121,7 @@ static inline struct config_item *configstruct config_item * item =NULL ;spin_lock (&dcache_lock); +spin_lock (&dentry->d_lock);if (!d_unhashed (dentry)) {struct configfs_dirent * sd = dentry->d_fsdata;if (sd->s_type &CONFIGFS_ITEM_LINK ) {
@@ -129,6 +130,7 @@ static inline struct config_item *config }else item =config_item_get (sd->s_element); } +spin_unlock (&dentry->d_lock);spin_unlock (&dcache_lock);return item;
fs/ocfs2/dcache.c +4 -1
@@ -156,13 +156,16 @@ struct dentry *ocfs2_find_local_alias(stlist_for_each (p, &inode->i_dentry) { dentry = list_entry(p, struct dentry, d_alias); +spin_lock (&dentry->d_lock);if (ocfs2_match_dentry (dentry, parent_blkno, skip_unhashed)) {mlog (0 ,"dentry found: %.*s\n" , dentry->d_name.len, dentry->d_name.name); -dget_locked (dentry); +dget_locked_dlock (dentry); +spin_unlock (&dentry->d_lock);break ; } +spin_unlock (&dentry->d_lock); dentry =NULL ; }
security/tomoyo/realpath.c +3 -0
@@ -17,6 +17,7 @@#include <linux/magic.h> #include <linux/slab.h> #include "common.h" +#include "../../fs/internal.h" /** * tomoyo_encode: Convert binary string to ascii string.
@@ -92,8 +93,10 @@ int tomoyo_realpath_from_path2(struct pastruct path ns_root = {.mnt =NULL , .dentry =NULL };spin_lock (&dcache_lock); +br_read_lock (vfsmount_lock);/* go to whatever namespace root we are under */ sp =__d_path (path, &ns_root, newname, newname_len); +br_read_unlock (vfsmount_lock);spin_unlock (&dcache_lock);/* Prepend "/proc" prefix if using internal proc vfs mount. */ if (!IS_ERR(sp) && (path->mnt->mnt_flags & MNT_INTERNAL) &&
drivers/usb/core/inode.c +3 -0
@@ -351,10 +351,13 @@ static int usbfs_empty (struct dentry *dlist_for_each (list, &dentry->d_subdirs) { struct dentry *de = list_entry(list, struct dentry, d_u.d_child); +spin_lock (&de->d_lock);if (usbfs_positive (de)) { +spin_unlock (&de->d_lock);spin_unlock (&dcache_lock);return 0 ; } +spin_unlock (&de->d_lock); }spin_unlock (&dcache_lock);
fs/ceph/dir.c +3 -2
@@ -135,6 +135,7 @@ more: fi->at_end =1 ; goto out_unlock; } +spin_lock (&dentry->d_lock); if (!d_unhashed(dentry) && dentry->d_inode && ceph_snap(dentry->d_inode) != CEPH_SNAPDIR && ceph_ino(dentry->d_inode) != CEPH_INO_CEPH &&
@@ -144,13 +145,13 @@ more: dentry->d_name.len, dentry->d_name.name, di->offset, filp->f_pos,d_unhashed (dentry) ?" unhashed" :"" , !dentry->d_inode ? " null" : ""); +spin_unlock (&dentry->d_lock); p = p->prev; dentry = list_entry(p, struct dentry, d_u.d_child); di =ceph_dentry (dentry); } -spin_lock (&dentry->d_lock); - dentry->d_count++; +dget_dlock (dentry);spin_unlock (&dentry->d_lock);spin_unlock (&dcache_lock);spin_unlock (&inode->i_lock);
PATCH 14/52 fs: dcache scale subdirs
In reply to [email protected]Protect d_subdirs and d_child with d_lock, except in filesystems that aren't using dcache_lock for these anyway (eg. using i_mutex).
XXX: probably don't need parent lock in inotify (because child lock should stabilize parent). Also, possibly some filesystems don't need so much locking (eg. of child dentry when modifying d_child, so long as parent is locked)... but be on the safe side. Hmm, maybe we should just say d_child list is protected by d_parent->d_lock. d_parent could remain protected with d_lock.
XXX: leave dcache_lock in there until remove dcache_lock patch
--- drivers/usb/core/inode.c | 8 +- fs/autofs4/expire.c | 87 +++++++++++++++------- fs/autofs4/root.c | 6 + fs/ceph/dir.c | 6 + fs/ceph/inode.c | 8 +- fs/coda/cache.c | 2 fs/dcache.c | 164 ++++++++++++++++++++++++++++++++++--------- fs/libfs.c | 40 ++++++---- fs/ncpfs/dir.c | 3 fs/ncpfs/ncplib_kernel.h | 4 + fs/notify/fsnotify.c | 4 - fs/notify/inotify/inotify.c | 4 - fs/smbfs/cache.c | 4 + include/linux/dcache.h | 1 kernel/cgroup.c | 19 ++++ security/selinux/selinuxfs.c | 12 ++- 16 files changed, 283 insertions(+), 89 deletions(-)
fs/dcache.c +130 -34
@@ -47,6 +47,8 @@* - d_lru * - d_count * - d_unhashed() +* - d_parent and d_subdirs +* - childrens' d_child and d_parent * * Ordering: * dcache_lock
@@ -219,7 +221,8 @@ static void dentry_lru_del_init(struct d* * If this is the root of the dentry tree, return NULL. * -* dcache_lock and d_lock must be held by caller, are dropped by d_kill. +* dcache_lock and d_lock and d_parent->d_lock must be held by caller, and +* are dropped by d_kill. */ static struct dentry *d_kill (struct dentry *dentry) __releases(dentry->d_lock)
@@ -228,12 +231,14 @@ static struct dentry *d_kill(struct dentstruct dentry *parent;list_del (&dentry->d_u.d_child); -/*drops the locks, at that point nobody can reach this dentry */ -dentry_iput (dentry); +if (dentry->d_parent && dentry != dentry->d_parent) +spin_unlock (&dentry->d_parent->d_lock);if (IS_ROOT (dentry)) parent =NULL ;else parent = dentry->d_parent; +/*drops the locks, at that point nobody can reach this dentry */ +dentry_iput (dentry);d_free (dentry);return parent; }
@@ -269,6 +274,7 @@ static struct dentry *d_kill(struct dentvoid dput (struct dentry *dentry) { +struct dentry *parent =NULL ;if (!dentry)return ;
@@ -287,10 +293,20 @@ repeat:spin_unlock (&dentry->d_lock); goto repeat; } + parent = dentry->d_parent; +if (parent && parent != dentry) { +if (!spin_trylock (&parent->d_lock)) { +spin_unlock (&dentry->d_lock); +spin_unlock (&dcache_lock); + goto repeat; + } + } } dentry->d_count--;if (dentry->d_count) {spin_unlock (&dentry->d_lock); +if (parent && parent != dentry) +spin_unlock (&parent->d_lock);spin_unlock (&dcache_lock);return ; }
@@ -310,6 +326,8 @@ repeat:dentry_lru_add (dentry); }spin_unlock (&dentry->d_lock); +if (parent && parent != dentry) +spin_unlock (&parent->d_lock);spin_unlock (&dcache_lock);return ;
@@ -545,10 +563,22 @@ static void prune_one_dentry(struct dent* because dcache_lock needs to be taken anyway. */ while (dentry) { +struct dentry *parent =NULL ; +spin_lock (&dcache_lock); +again:spin_lock (&dentry->d_lock); +if (dentry->d_parent && dentry != dentry->d_parent) { +if (!spin_trylock (&dentry->d_parent->d_lock)) { +spin_unlock (&dentry->d_lock); + goto again; + } + parent = dentry->d_parent; + } dentry->d_count--;if (dentry->d_count) { +if (parent) +spin_unlock (&parent->d_lock);spin_unlock (&dentry->d_lock);spin_unlock (&dcache_lock);return ;
@@ -626,20 +656,27 @@ again: dentry = list_entry(tmp.prev, struct dentry, d_lru);if (!spin_trylock (&dentry->d_lock)) { +again1:spin_unlock (&dcache_lru_lock); goto again; } -__dentry_lru_del_init (dentry);/* * We found an inuse dentry which was not removed from * the LRU because of laziness during lookup. Do not free * it - just keep it off the LRU list. */ if (dentry->d_count) { +__dentry_lru_del_init (dentry);spin_unlock (&dentry->d_lock);continue ; } - +if (dentry->d_parent && dentry->d_parent != dentry) { +if (!spin_trylock (&dentry->d_parent->d_lock)) { +spin_unlock (&dentry->d_lock); + goto again1; + } + } +__dentry_lru_del_init (dentry);spin_unlock (&dcache_lru_lock);prune_one_dentry (dentry);/* dcache_lock and dentry->d_lock dropped */
@@ -776,14 +813,15 @@ static void shrink_dcache_for_umount_sub/* this is a branch with children - detach all of them * from the system in one go */ spin_lock (&dcache_lock); +spin_lock (&dentry->d_lock);list_for_each_entry (loop, &dentry->d_subdirs, d_u.d_child) { -spin_lock (&loop->d_lock); +spin_lock_nested (&loop->d_lock,DENTRY_D_LOCK_NESTED );dentry_lru_del_init (loop);__d_drop (loop);spin_unlock (&loop->d_lock); -cond_resched_lock (&dcache_lock); } +spin_unlock (&dentry->d_lock);spin_unlock (&dcache_lock);/* move to the first child */
@@ -811,16 +849,17 @@ static void shrink_dcache_for_umount_subBUG (); } -if (IS_ROOT (dentry)) +if (IS_ROOT (dentry)) { parent =NULL ; -else { +list_del (&dentry->d_u.d_child); + }else { parent = dentry->d_parent;spin_lock (&parent->d_lock); parent->d_count--; +list_del (&dentry->d_u.d_child);spin_unlock (&parent->d_lock); } -list_del (&dentry->d_u.d_child); detached++; inode = dentry->d_inode;
@@ -905,6 +944,7 @@ int have_submounts(struct dentry *parentspin_lock (&dcache_lock);if (d_mountpoint (parent)) goto positive; +spin_lock (&this_parent->d_lock); repeat: next = this_parent->d_subdirs.next; resume:
@@ -912,22 +952,34 @@ resume:struct list_head *tmp = next; struct dentry *dentry = list_entry(tmp, struct dentry, d_u.d_child); next = tmp->next; + +spin_lock_nested (&dentry->d_lock,DENTRY_D_LOCK_NESTED );/* Have we found a mount point ? */ -if (d_mountpoint (dentry)) +if (d_mountpoint (dentry)) { +spin_unlock (&dentry->d_lock); +spin_unlock (&this_parent->d_lock); goto positive; + }if (!list_empty (&dentry->d_subdirs)) { +spin_unlock (&this_parent->d_lock); +spin_release (&dentry->d_lock.dep_map,1 , _RET_IP_); this_parent = dentry; +spin_acquire (&this_parent->d_lock.dep_map,0 ,1 , _RET_IP_); goto repeat; } +spin_unlock (&dentry->d_lock); }/* * All done at this level ... ascend and resume the search. */ if (this_parent != parent) { next = this_parent->d_u.d_child.next; +spin_unlock (&this_parent->d_lock); this_parent = this_parent->d_parent; +spin_lock (&this_parent->d_lock); goto resume; } +spin_unlock (&this_parent->d_lock);spin_unlock (&dcache_lock);return 0 ;/* No mount points found in tree */ positive:
@@ -957,6 +1009,7 @@ static int select_parent(struct dentry *int found =0 ;spin_lock (&dcache_lock); +spin_lock (&this_parent->d_lock); repeat: next = this_parent->d_subdirs.next; resume:
@@ -964,8 +1017,9 @@ resume:struct list_head *tmp = next; struct dentry *dentry = list_entry(tmp, struct dentry, d_u.d_child); next = tmp->next; +BUG_ON (this_parent == dentry); -spin_lock (&dentry->d_lock); +spin_lock_nested (&dentry->d_lock,DENTRY_D_LOCK_NESTED );dentry_lru_del_init (dentry);/* * move only zero ref count dentries to the end
@@ -975,33 +1029,45 @@ resume:dentry_lru_add_tail (dentry); found++; } -spin_unlock (&dentry->d_lock);/* * We can return to the caller if we have found some (this * ensures forward progress). We'll be coming back to find * the rest. */ -if (found &&need_resched ()) +if (found &&need_resched ()) { +spin_unlock (&dentry->d_lock); goto out; + }/* * Descend a level if the d_subdirs list is non-empty. */ if (!list_empty (&dentry->d_subdirs)) { +spin_unlock (&this_parent->d_lock); +spin_release (&dentry->d_lock.dep_map,1 , _RET_IP_); this_parent = dentry; +spin_acquire (&this_parent->d_lock.dep_map,0 ,1 , _RET_IP_); goto repeat; } + +spin_unlock (&dentry->d_lock); }/* * All done at this level ... ascend and resume the search. */ if (this_parent != parent) { +struct dentry *tmp; next = this_parent->d_u.d_child.next; - this_parent = this_parent->d_parent; + tmp = this_parent->d_parent; +spin_unlock (&this_parent->d_lock); +BUG_ON (tmp == this_parent); + this_parent = tmp; +spin_lock (&this_parent->d_lock); goto resume; } out: +spin_unlock (&this_parent->d_lock);spin_unlock (&dcache_lock);return found; }
@@ -1098,19 +1164,20 @@ struct dentry *d_alloc(struct dentry * pINIT_LIST_HEAD (&dentry->d_lru);INIT_LIST_HEAD (&dentry->d_subdirs);INIT_LIST_HEAD (&dentry->d_alias); - -if (parent) { - dentry->d_parent =dget (parent); - dentry->d_sb = parent->d_sb; - }else { -INIT_LIST_HEAD (&dentry->d_u.d_child); - } +INIT_LIST_HEAD (&dentry->d_u.d_child);if (parent) {spin_lock (&dcache_lock); +spin_lock (&parent->d_lock); +spin_lock_nested (&dentry->d_lock,DENTRY_D_LOCK_NESTED ); + dentry->d_parent =dget_dlock (parent); + dentry->d_sb = parent->d_sb;list_add (&dentry->d_u.d_child, &parent->d_subdirs); +spin_unlock (&dentry->d_lock); +spin_unlock (&parent->d_lock);spin_unlock (&dcache_lock); } +atomic_inc (&dentry_stat.nr_dentry);return dentry;
@@ -1802,15 +1869,26 @@ static void d_move_locked(struct dentry/* * XXXX: do we really need to take target->d_lock? */ -if (d_ancestor (dentry, target)) { -spin_lock (&dentry->d_lock); -spin_lock_nested (&target->d_lock,DENTRY_D_LOCK_NESTED ); - }else if (d_ancestor (target, dentry) || target < dentry) { -spin_lock (&target->d_lock); -spin_lock_nested (&dentry->d_lock,DENTRY_D_LOCK_NESTED ); - }else { -spin_lock (&dentry->d_lock); -spin_lock_nested (&target->d_lock,DENTRY_D_LOCK_NESTED ); +BUG_ON (d_ancestor (dentry, target)); +BUG_ON (d_ancestor (target, dentry)); + +if (dentry->d_parent == target->d_parent) +spin_lock (&dentry->d_parent->d_lock); +else { +if (d_ancestor (dentry->d_parent, target->d_parent)) { +spin_lock (&dentry->d_parent->d_lock); +spin_lock_nested (&target->d_parent->d_lock,DENTRY_D_LOCK_NESTED ); + }else { +spin_lock (&target->d_parent->d_lock); +spin_lock_nested (&dentry->d_parent->d_lock,DENTRY_D_LOCK_NESTED ); + } + } +if (target < dentry) { +spin_lock_nested (&target->d_lock,2 ); +spin_lock_nested (&dentry->d_lock,3 ); + }else { +spin_lock_nested (&dentry->d_lock,2 ); +spin_lock_nested (&target->d_lock,3 ); }/* Move the dentry to the target hash queue, if on different bucket */
@@ -1843,6 +1921,10 @@ static void d_move_locked(struct dentry }list_add (&dentry->d_u.d_child, &dentry->d_parent->d_subdirs); +if (target->d_parent != dentry->d_parent) +spin_unlock (&dentry->d_parent->d_lock); +if (target->d_parent != target) +spin_unlock (&target->d_parent->d_lock);spin_unlock (&target->d_lock);fsnotify_d_move (dentry);spin_unlock (&dentry->d_lock);
@@ -1943,6 +2025,13 @@ static void __d_materialise_dentry(struc dparent = dentry->d_parent; aparent = anon->d_parent; +/* XXX: hack */ +/* returns with anon->d_lock held! */ +spin_lock (&aparent->d_lock); +spin_lock (&dparent->d_lock); +spin_lock (&dentry->d_lock); +spin_lock (&anon->d_lock); + dentry->d_parent = (aparent == anon) ? dentry : aparent;list_del (&dentry->d_u.d_child);if (!IS_ROOT (dentry))
@@ -1957,6 +2046,10 @@ static void __d_materialise_dentry(strucelse INIT_LIST_HEAD (&anon->d_u.d_child); +spin_unlock (&dentry->d_lock); +spin_unlock (&dparent->d_lock); +spin_unlock (&aparent->d_lock); + anon->d_flags &= ~DCACHE_DISCONNECTED ; }
@@ -1992,7 +2085,6 @@ struct dentry *d_materialise_unique(stru/* Is this an anonymous mountpoint that we could splice * into our tree? */ if (IS_ROOT (alias)) { -spin_lock (&alias->d_lock);__d_materialise_dentry (dentry, alias);__d_drop (alias); goto found;
@@ -2396,6 +2488,7 @@ void d_genocide(struct dentry *root)struct list_head *next;spin_lock (&dcache_lock); +spin_lock (&this_parent->d_lock); repeat: next = this_parent->d_subdirs.next; resume:
@@ -2409,8 +2502,10 @@ resume:continue ; }if (!list_empty (&dentry->d_subdirs)) { -spin_unlock (&dentry->d_lock); +spin_unlock (&this_parent->d_lock); +spin_release (&dentry->d_lock.dep_map,1 , _RET_IP_); this_parent = dentry; +spin_acquire (&this_parent->d_lock.dep_map,0 ,1 , _RET_IP_); goto repeat; } dentry->d_count--;
@@ -2418,12 +2513,13 @@ resume: }if (this_parent != root) { next = this_parent->d_u.d_child.next; -spin_lock (&this_parent->d_lock); this_parent->d_count--;spin_unlock (&this_parent->d_lock); this_parent = this_parent->d_parent; +spin_lock (&this_parent->d_lock); goto resume; } +spin_unlock (&this_parent->d_lock);spin_unlock (&dcache_lock); }
fs/libfs.c +25 -15
@@ -81,7 +81,8 @@ int dcache_dir_close(struct inode *inodeloff_t dcache_dir_lseek (struct file *file,loff_t offset,int origin) { -mutex_lock (&file->f_path.dentry->d_inode->i_mutex); +struct dentry *dentry = file->f_path.dentry; +mutex_lock (&dentry->d_inode->i_mutex);switch (origin) {case 1 : offset += file->f_pos;
@@ -89,7 +90,7 @@ loff_t dcache_dir_lseek(struct file *filif (offset >=0 )break ;default : -mutex_unlock (&file->f_path.dentry->d_inode->i_mutex); +mutex_unlock (&dentry->d_inode->i_mutex);return -EINVAL ; }if (offset != file->f_pos) {
@@ -99,23 +100,27 @@ loff_t dcache_dir_lseek(struct file *filstruct dentry *cursor = file->private_data;loff_t n = file->f_pos -2 ; -spin_lock (&dcache_lock); +spin_lock (&dentry->d_lock); +spin_lock_nested (&cursor->d_lock,DENTRY_D_LOCK_NESTED );list_del (&cursor->d_u.d_child); - p = file->f_path.dentry->d_subdirs.next; -while (n && p != &file->f_path.dentry->d_subdirs) { +spin_unlock (&cursor->d_lock); + p = dentry->d_subdirs.next; +while (n && p != &dentry->d_subdirs) {struct dentry *next; next = list_entry(p, struct dentry, d_u.d_child); -spin_lock (&next->d_lock); +spin_lock_nested (&next->d_lock,DENTRY_D_LOCK_NESTED );if (simple_positive (next)) n--;spin_unlock (&next->d_lock); p = p->next; } +spin_lock_nested (&cursor->d_lock,DENTRY_D_LOCK_NESTED );list_add_tail (&cursor->d_u.d_child, p); -spin_unlock (&dcache_lock); +spin_unlock (&cursor->d_lock); +spin_unlock (&dentry->d_lock); } } -mutex_unlock (&file->f_path.dentry->d_inode->i_mutex); +mutex_unlock (&dentry->d_inode->i_mutex);return offset; }
@@ -155,9 +160,12 @@ int dcache_readdir(struct file * filp, v i++;/* fallthrough */ default : -spin_lock (&dcache_lock); -if (filp->f_pos ==2 ) +spin_lock (&dentry->d_lock); +if (filp->f_pos ==2 ) { +spin_lock_nested (&cursor->d_lock,DENTRY_D_LOCK_NESTED );list_move (q, &dentry->d_subdirs); +spin_unlock (&cursor->d_lock); + }for (p=q->next; p != &dentry->d_subdirs; p=p->next) {struct dentry *next;
@@ -169,19 +177,21 @@ int dcache_readdir(struct file * filp, v }spin_unlock (&next->d_lock); -spin_unlock (&dcache_lock); +spin_unlock (&dentry->d_lock);if (filldir (dirent, next->d_name.name, next->d_name.len, filp->f_pos, next->d_inode->i_ino,dt_type (next->d_inode)) <0 )return 0 ; -spin_lock (&dcache_lock); +spin_lock (&dentry->d_lock); +spin_lock_nested (&next->d_lock,DENTRY_D_LOCK_NESTED );/* next is still alive */ list_move (q, p); +spin_unlock (&next->d_lock); p = q; filp->f_pos++; } -spin_unlock (&dcache_lock); +spin_unlock (&dentry->d_lock); }return 0 ; }
@@ -278,7 +288,7 @@ int simple_empty(struct dentry *dentry)struct dentry *child;int ret =0 ; -spin_lock (&dcache_lock); +spin_lock (&dentry->d_lock);list_for_each_entry (child, &dentry->d_subdirs, d_u.d_child) {spin_lock_nested (&child->d_lock,DENTRY_D_LOCK_NESTED );if (simple_positive (child)) {
@@ -289,7 +299,7 @@ int simple_empty(struct dentry *dentry) } ret =1 ; out: -spin_unlock (&dcache_lock); +spin_unlock (&dentry->d_lock);return ret; }
fs/notify/inotify/inotify.c +3 -1
@@ -185,17 +185,19 @@ static void set_dentry_child_flags(struclist_for_each_entry (alias, &inode->i_dentry, d_alias) {struct dentry *child; +spin_lock (&alias->d_lock);list_for_each_entry (child, &alias->d_subdirs, d_u.d_child) {if (!child->d_inode)continue ; -spin_lock (&child->d_lock); +spin_lock_nested (&child->d_lock,DENTRY_D_LOCK_NESTED );if (watched) child->d_flags |=DCACHE_INOTIFY_PARENT_WATCHED ;else child->d_flags &=~DCACHE_INOTIFY_PARENT_WATCHED ;spin_unlock (&child->d_lock); } +spin_unlock (&alias->d_lock); }spin_unlock (&dcache_lock); }
include/linux/dcache.h +1 -0
@@ -342,6 +342,7 @@ static inline struct dentry *dget_dlock( }return dentry; } +static inline struct dentry *dget (struct dentry *dentry) {if (dentry) {
drivers/usb/core/inode.c +5 -3
@@ -348,18 +348,20 @@ static int usbfs_empty (struct dentry *dstruct list_head *list;spin_lock (&dcache_lock); - +spin_lock (&dentry->d_lock);list_for_each (list, &dentry->d_subdirs) { struct dentry *de = list_entry(list, struct dentry, d_u.d_child); -spin_lock (&de->d_lock); + +spin_lock_nested (&de->d_lock,DENTRY_D_LOCK_NESTED );if (usbfs_positive (de)) {spin_unlock (&de->d_lock); +spin_unlock (&dentry->d_lock);spin_unlock (&dcache_lock);return 0 ; }spin_unlock (&de->d_lock); } - +spin_unlock (&dentry->d_lock);spin_unlock (&dcache_lock);return 1 ; }
fs/autofs4/expire.c +59 -28
169 lines not on this page · raw message
fs/autofs4/root.c +6 -0
@@ -135,10 +135,13 @@ static int autofs4_dir_open(struct inode* it. */ spin_lock (&dcache_lock); +spin_lock (&dentry->d_lock);if (!d_mountpoint (dentry) &&list_empty (&dentry->d_subdirs)) { +spin_unlock (&dentry->d_lock);spin_unlock (&dcache_lock);return -ENOENT ; } +spin_unlock (&dentry->d_lock);spin_unlock (&dcache_lock); out:
@@ -246,7 +249,9 @@ static void *autofs4_follow_link(struct lookup_type =autofs4_need_mount (nd->flags);spin_lock (&sbi->fs_lock);spin_lock (&dcache_lock); +spin_lock (&dentry->d_lock);if (!(lookup_type || ino->flags &AUTOFS_INF_PENDING )) { +spin_unlock (&dentry->d_lock);spin_unlock (&dcache_lock);spin_unlock (&sbi->fs_lock); goto follow;
@@ -268,6 +273,7 @@ static void *autofs4_follow_link(struct goto follow; } +spin_unlock (&dentry->d_lock);spin_unlock (&dcache_lock);spin_unlock (&sbi->fs_lock); follow:
fs/coda/cache.c +2 -0
@@ -87,6 +87,7 @@ static void coda_flag_children(struct destruct dentry *de;spin_lock (&dcache_lock); +spin_lock (&parent->d_lock);list_for_each (child, &parent->d_subdirs) { de = list_entry(child, struct dentry, d_u.d_child);
@@ -95,6 +96,7 @@ static void coda_flag_children(struct decontinue ;coda_flag_inode (de->d_inode, flag); } +spin_unlock (&parent->d_lock);spin_unlock (&dcache_lock);return ; }
fs/ncpfs/dir.c +3 -0
@@ -365,6 +365,7 @@ ncp_dget_fpos(struct dentry *dentry, str/* If a pointer is invalid, we search the dentry. */ spin_lock (&dcache_lock); +spin_lock (&parent->d_lock); next = parent->d_subdirs.next;while (next != &parent->d_subdirs) { dent = list_entry(next, struct dentry, d_u.d_child);
@@ -373,11 +374,13 @@ ncp_dget_fpos(struct dentry *dentry, strdget_locked (dent); else dent =NULL ; +spin_unlock (&parent->d_lock);spin_unlock (&dcache_lock); goto out; } next = next->next; } +spin_unlock (&parent->d_lock);spin_unlock (&dcache_lock);return NULL ;
fs/ncpfs/ncplib_kernel.h +4 -0
@@ -193,6 +193,7 @@ ncp_renew_dentries(struct dentry *parentstruct dentry *dentry;spin_lock (&dcache_lock); +spin_lock (&parent->d_lock); next = parent->d_subdirs.next;while (next != &parent->d_subdirs) { dentry = list_entry(next, struct dentry, d_u.d_child);
@@ -204,6 +205,7 @@ ncp_renew_dentries(struct dentry *parent next = next->next; } +spin_unlock (&parent->d_lock);spin_unlock (&dcache_lock); }
@@ -215,6 +217,7 @@ ncp_invalidate_dircache_entries(struct dstruct dentry *dentry;spin_lock (&dcache_lock); +spin_lock (&parent->d_lock); next = parent->d_subdirs.next;while (next != &parent->d_subdirs) { dentry = list_entry(next, struct dentry, d_u.d_child);
@@ -222,6 +225,7 @@ ncp_invalidate_dircache_entries(struct dncp_age_dentry (server, dentry); next = next->next; } +spin_unlock (&parent->d_lock);spin_unlock (&dcache_lock); }
fs/smbfs/cache.c +4 -0
@@ -63,6 +63,7 @@ smb_invalidate_dircache_entries(struct dstruct dentry *dentry;spin_lock (&dcache_lock); +spin_lock (&parent->d_lock); next = parent->d_subdirs.next;while (next != &parent->d_subdirs) { dentry = list_entry(next, struct dentry, d_u.d_child);
@@ -70,6 +71,7 @@ smb_invalidate_dircache_entries(struct dsmb_age_dentry (server, dentry); next = next->next; } +spin_unlock (&parent->d_lock);spin_unlock (&dcache_lock); }
@@ -97,6 +99,7 @@ smb_dget_fpos(struct dentry *dentry, str/* If a pointer is invalid, we search the dentry. */ spin_lock (&dcache_lock); +spin_lock (&parent->d_lock); next = parent->d_subdirs.next;while (next != &parent->d_subdirs) { dent = list_entry(next, struct dentry, d_u.d_child);
@@ -111,6 +114,7 @@ smb_dget_fpos(struct dentry *dentry, str } dent =NULL ; out_unlock: +spin_unlock (&parent->d_lock);spin_unlock (&dcache_lock);return dent; }
kernel/cgroup.c +17 -2
@@ -870,23 +870,31 @@ static void cgroup_clear_directory(strucBUG_ON (!mutex_is_locked (&dentry->d_inode->i_mutex));spin_lock (&dcache_lock); +spin_lock (&dentry->d_lock); node = dentry->d_subdirs.next;while (node != &dentry->d_subdirs) { struct dentry *d = list_entry(node, struct dentry, d_u.d_child); + +spin_lock_nested (&d->d_lock,DENTRY_D_LOCK_NESTED );list_del_init (node);if (d->d_inode) {/* This should never be called on a cgroup * directory with child cgroups */ BUG_ON (d->d_inode->i_mode &S_IFDIR ); - d =dget_locked (d); +dget_locked_dlock (d); +spin_unlock (&d->d_lock); +spin_unlock (&dentry->d_lock);spin_unlock (&dcache_lock);d_delete (d);simple_unlink (dentry->d_inode, d);dput (d);spin_lock (&dcache_lock); - } +spin_lock (&dentry->d_lock); + }else +spin_unlock (&d->d_lock); node = dentry->d_subdirs.next; } +spin_unlock (&dentry->d_lock);spin_unlock (&dcache_lock); }
@@ -895,10 +903,17 @@ static void cgroup_clear_directory(struc*/ static void cgroup_d_remove_dir (struct dentry *dentry) { +struct dentry *parent; +cgroup_clear_directory (dentry);spin_lock (&dcache_lock); + parent = dentry->d_parent; +spin_lock (&parent->d_lock); +spin_lock (&dentry->d_lock);list_del_init (&dentry->d_u.d_child); +spin_unlock (&dentry->d_lock); +spin_unlock (&parent->d_lock);spin_unlock (&dcache_lock);remove_dir (dentry); }
security/selinux/selinuxfs.c +10 -2
@@ -942,22 +942,30 @@ static void sel_remove_entries(struct destruct list_head *node;spin_lock (&dcache_lock); +spin_lock (&de->d_lock); node = de->d_subdirs.next;while (node != &de->d_subdirs) { struct dentry *d = list_entry(node, struct dentry, d_u.d_child); + +spin_lock_nested (&d->d_lock,DENTRY_D_LOCK_NESTED );list_del_init (node);if (d->d_inode) { - d =dget_locked (d); +dget_locked_dlock (d); +spin_unlock (&de->d_lock); +spin_unlock (&d->d_lock);spin_unlock (&dcache_lock);d_delete (d);simple_unlink (de->d_inode, d);dput (d);spin_lock (&dcache_lock); - } +spin_lock (&de->d_lock); + }else +spin_unlock (&d->d_lock); node = de->d_subdirs.next; } +spin_unlock (&de->d_lock);spin_unlock (&dcache_lock); }
fs/notify/fsnotify.c +3 -1
@@ -62,17 +62,19 @@ void __fsnotify_update_child_dentry_flag/* run all of the children of the original inode and fix their * d_flags to indicate parental interest (their parent is the * original inode) */ +spin_lock (&alias->d_lock);list_for_each_entry (child, &alias->d_subdirs, d_u.d_child) {if (!child->d_inode)continue ; -spin_lock (&child->d_lock); +spin_lock_nested (&child->d_lock,DENTRY_D_LOCK_NESTED );if (watched) child->d_flags |=DCACHE_FSNOTIFY_PARENT_WATCHED ;else child->d_flags &= ~DCACHE_FSNOTIFY_PARENT_WATCHED ;spin_unlock (&child->d_lock); } +spin_unlock (&alias->d_lock); }spin_unlock (&dcache_lock); }
fs/ceph/dir.c +5 -1
@@ -112,6 +112,7 @@ static int __dcache_readdir(struct file last);spin_lock (&dcache_lock); +spin_lock (&parent->d_lock);/* start at beginning? */ if (filp->f_pos == 2 || (last &&
@@ -135,7 +136,7 @@ more: fi->at_end =1 ; goto out_unlock; } -spin_lock (&dentry->d_lock); +spin_lock_nested (&dentry->d_lock,DENTRY_D_LOCK_NESTED ); if (!d_unhashed(dentry) && dentry->d_inode && ceph_snap(dentry->d_inode) != CEPH_SNAPDIR && ceph_ino(dentry->d_inode) != CEPH_INO_CEPH &&
@@ -153,6 +154,7 @@ more:dget_dlock (dentry);spin_unlock (&dentry->d_lock); +spin_unlock (&parent->d_lock);spin_unlock (&dcache_lock);spin_unlock (&inode->i_lock);
@@ -177,6 +179,7 @@ more:spin_lock (&inode->i_lock);spin_lock (&dcache_lock); +spin_lock (&parent->d_lock); last = dentry;
@@ -193,6 +196,7 @@ more: err = -EAGAIN ; out_unlock: +spin_unlock (&parent->d_lock);spin_unlock (&dcache_lock);if (last) {
fs/ceph/inode.c +6 -2
@@ -826,11 +826,13 @@ static void ceph_set_dentry_offset(strucspin_unlock (&inode->i_lock);spin_lock (&dcache_lock); -spin_lock (&dn->d_lock); +spin_lock (&dir->d_lock); +spin_lock_nested (&dn->d_lock,DENTRY_D_LOCK_NESTED );list_move (&dn->d_u.d_child, &dir->d_subdirs);dout ("set_dentry_offset %p %lld (%p %p)\n" , dn, di->offset, dn->d_u.d_child.prev, dn->d_u.d_child.next);spin_unlock (&dn->d_lock); +spin_unlock (&dir->d_lock);spin_unlock (&dcache_lock); }
@@ -1212,9 +1214,11 @@ retry_lookup: }else {/* reorder parent's d_subdirs */ spin_lock (&dcache_lock); -spin_lock (&dn->d_lock); +spin_lock (&parent->d_lock); +spin_lock_nested (&dn->d_lock,DENTRY_D_LOCK_NESTED );list_move (&dn->d_u.d_child, &parent->d_subdirs);spin_unlock (&dn->d_lock); +spin_unlock (&parent->d_lock);spin_unlock (&dcache_lock); }
Peter Zijlstra
maintainerRe: [patch 14/52] fs: dcache scale subdirs
Quoted 4 lines · On Thu, 2010-06-24 at 13:02 +1000, [email protected] wrote:
plain text document attachment (fs-dcache-scale-d_subdirs.patch) Protect
d_subdirsandd_childwithd_lock, except in filesystems that aren't usingdcache_lockfor these anyway (eg. usingi_mutex).
Quoted 8 lines
XXX: probably don't need parent lock in inotify (because child lock should stabilize parent). Also, possibly some filesystems don't need so much locking (eg. of child dentry when modifying
d_child, so long as parent is locked)... but be on the safe side. Hmm, maybe we should just sayd_childlist is protected byd_parent->d_lock.d_parentcould remain protected withd_lock.XXX: leave
dcache_lockin there until removedcache_lockpatch
This still suffers the problem John found, right?
Re: [patch 14/52] fs: dcache scale subdirs
In reply to [email protected]Quoted 2 lines · [email protected] writes:
Protect
d_subdirsandd_childwithd_lock, except in filesystems that aren't usingdcache_lockfor these anyway (eg. usingi_mutex).
Different locking for different file systems seems a bit confusing. Could the be unified?
-Andi
Signature · 1 line
[email protected] -- Speaking for myself only.
Re: [patch 14/52] fs: dcache scale subdirs
Quoted 7 lines · Andi Kleen
Quoted 2 lines · [email protected] writes:
Protect
d_subdirsandd_childwithd_lock, except in filesystems that aren't usingdcache_lockfor these anyway (eg. usingi_mutex).Different locking for different file systems seems a bit confusing. Could the be unified?
Yes well it is a bit misleading. It should always be modified under spinlocks, but some filesystems are using i_mutex for read access, which should be fine (and not require knowledge of any other code).
PATCH 15/52 fs: dcache scale inode alias list
In reply to [email protected]Add a new lock, dcache_inode_lock, to protect the inode's i_dentry list from concurrent modification. d_alias is also protected by d_lock.
--- fs/affs/amigaffs.c | 2 + fs/dcache.c | 56 +++++++++++++++++++++++++++++++++++++++----- fs/exportfs/expfs.c | 4 +++ fs/nfs/getroot.c | 4 +++ fs/notify/fsnotify.c | 2 + fs/notify/inotify/inotify.c | 2 + fs/ocfs2/dcache.c | 3 +- fs/sysfs/dir.c | 3 ++ include/linux/dcache.h | 1 9 files changed, 70 insertions(+), 7 deletions(-)
fs/dcache.c +53 -6
@@ -37,6 +37,8 @@/* * Usage: +* dcache_inode_lock protects: +* - i_dentry, d_alias, d_inode * dcache_hash_lock protects: * - the dcache hash table * dcache_lru_lock protects:
@@ -49,12 +51,14 @@* - d_unhashed() * - d_parent and d_subdirs * - childrens' d_child and d_parent +* - d_alias, d_inode * * Ordering: * dcache_lock -* dentry->d_lock -* dcache_lru_lock -* dcache_hash_lock +* dcache_inode_lock +* dentry->d_lock +* dcache_lru_lock +* dcache_hash_lock * * If there is an ancestor relationship: * dentry->d_parent->...->d_parent->d_lock
@@ -70,11 +74,13 @@int sysctl_vfs_cache_pressure__read_mostly =100 ;EXPORT_SYMBOL_GPL (sysctl_vfs_cache_pressure); +__cacheline_aligned_in_smp DEFINE_SPINLOCK (dcache_inode_lock );__cacheline_aligned_in_smp DEFINE_SPINLOCK (dcache_hash_lock );static __cacheline_aligned_in_smp DEFINE_SPINLOCK (dcache_lru_lock );__cacheline_aligned_in_smp DEFINE_SPINLOCK (dcache_lock );__cacheline_aligned_in_smp DEFINE_SEQLOCK (rename_lock ); +EXPORT_SYMBOL (dcache_inode_lock);EXPORT_SYMBOL (dcache_hash_lock);EXPORT_SYMBOL (dcache_lock);
@@ -139,6 +145,7 @@ static void d_free(struct dentry *dentry*/ static void dentry_iput (struct dentry * dentry) __releases(dentry->d_lock) +__releases (dcache_inode_lock)__releases (dcache_lock) {struct inode *inode = dentry->d_inode;
@@ -146,6 +153,7 @@ static void dentry_iput(struct dentry * dentry->d_inode =NULL ;list_del_init (&dentry->d_alias);spin_unlock (&dentry->d_lock); +spin_unlock (&dcache_inode_lock);spin_unlock (&dcache_lock);if (!inode->i_nlink)fsnotify_inoderemove (inode);
@@ -155,6 +163,7 @@ static void dentry_iput(struct dentry *iput (inode); }else {spin_unlock (&dentry->d_lock); +spin_unlock (&dcache_inode_lock);spin_unlock (&dcache_lock); } }
@@ -226,6 +235,7 @@ static void dentry_lru_del_init(struct d*/ static struct dentry *d_kill (struct dentry *dentry) __releases(dentry->d_lock) +__releases (dcache_inode_lock)__releases (dcache_lock) {struct dentry *parent;
@@ -290,15 +300,20 @@ repeat:* want to reduce dcache_lock anyway so this will * get improved. */ +drop1:spin_unlock (&dentry->d_lock); goto repeat; } +if (!spin_trylock (&dcache_inode_lock)) { +drop2: +spin_unlock (&dcache_lock); + goto drop1; + } parent = dentry->d_parent;if (parent && parent != dentry) {if (!spin_trylock (&parent->d_lock)) { -spin_unlock (&dentry->d_lock); -spin_unlock (&dcache_lock); - goto repeat; +spin_unlock (&dcache_inode_lock); + goto drop2; } } }
@@ -328,6 +343,7 @@ repeat:spin_unlock (&dentry->d_lock);if (parent && parent != dentry)spin_unlock (&parent->d_lock); +spin_unlock (&dcache_inode_lock);spin_unlock (&dcache_lock);return ;
@@ -510,7 +526,9 @@ struct dentry * d_find_alias(struct inodif (!list_empty (&inode->i_dentry)) {spin_lock (&dcache_lock); +spin_lock (&dcache_inode_lock); de =__d_find_alias (inode,0 ); +spin_unlock (&dcache_inode_lock);spin_unlock (&dcache_lock); }return de;
@@ -526,18 +544,21 @@ void d_prune_aliases(struct inode *inodestruct dentry *dentry; restart:spin_lock (&dcache_lock); +spin_lock (&dcache_inode_lock);list_for_each_entry (dentry, &inode->i_dentry, d_alias) {spin_lock (&dentry->d_lock);if (!dentry->d_count) {__dget_locked_dlock (dentry);__d_drop (dentry);spin_unlock (&dentry->d_lock); +spin_unlock (&dcache_inode_lock);spin_unlock (&dcache_lock);dput (dentry); goto restart; }spin_unlock (&dentry->d_lock); } +spin_unlock (&dcache_inode_lock);spin_unlock (&dcache_lock); }EXPORT_SYMBOL (d_prune_aliases);
@@ -566,6 +587,7 @@ static void prune_one_dentry(struct dentstruct dentry *parent =NULL ;spin_lock (&dcache_lock); +spin_lock (&dcache_inode_lock); again:spin_lock (&dentry->d_lock);if (dentry->d_parent && dentry != dentry->d_parent) {
@@ -580,6 +602,7 @@ again:if (parent)spin_unlock (&parent->d_lock);spin_unlock (&dentry->d_lock); +spin_unlock (&dcache_inode_lock);spin_unlock (&dcache_lock);return ; }
@@ -650,6 +673,7 @@ restart:spin_unlock (&dcache_lru_lock);spin_lock (&dcache_lock); +spin_lock (&dcache_inode_lock); again:spin_lock (&dcache_lru_lock);/* lru_lock also protects tmp list */ while (!list_empty (&tmp)) {
@@ -681,8 +705,10 @@ again1:prune_one_dentry (dentry);/* dcache_lock and dentry->d_lock dropped */ spin_lock (&dcache_lock); +spin_lock (&dcache_inode_lock);spin_lock (&dcache_lru_lock); } +spin_unlock (&dcache_inode_lock);spin_unlock (&dcache_lock);if (count ==NULL && !list_empty (&sb->s_dentry_lru))
@@ -1198,9 +1224,11 @@ EXPORT_SYMBOL(d_alloc_name);/* the caller must hold dcache_lock */ static void __d_instantiate (struct dentry *dentry,struct inode *inode) { +spin_lock (&dentry->d_lock);if (inode)list_add (&dentry->d_alias, &inode->i_dentry); dentry->d_inode = inode; +spin_unlock (&dentry->d_lock);fsnotify_d_instantiate (dentry, inode); }
@@ -1223,7 +1251,9 @@ void d_instantiate(struct dentry *entry, {BUG_ON (!list_empty (&entry->d_alias));spin_lock (&dcache_lock); +spin_lock (&dcache_inode_lock);__d_instantiate (entry, inode); +spin_unlock (&dcache_inode_lock);spin_unlock (&dcache_lock);security_d_instantiate (entry, inode); }
@@ -1284,7 +1314,9 @@ struct dentry *d_instantiate_unique(struBUG_ON (!list_empty (&entry->d_alias));spin_lock (&dcache_lock); +spin_lock (&dcache_inode_lock); result =__d_instantiate_unique (entry, inode); +spin_unlock (&dcache_inode_lock);spin_unlock (&dcache_lock);if (!result) {
@@ -1375,8 +1407,10 @@ struct dentry *d_obtain_alias(struct ino tmp->d_parent = tmp;/* make sure dput doesn't croak */ spin_lock (&dcache_lock); +spin_lock (&dcache_inode_lock); res =__d_find_alias (inode,0 );if (res) { +spin_unlock (&dcache_inode_lock);spin_unlock (&dcache_lock);dput (tmp); goto out_iput;
@@ -1391,6 +1425,7 @@ struct dentry *d_obtain_alias(struct inolist_add (&tmp->d_alias, &inode->i_dentry);hlist_add_head (&tmp->d_hash, &inode->i_sb->s_anon);spin_unlock (&tmp->d_lock); +spin_unlock (&dcache_inode_lock);spin_unlock (&dcache_lock);return tmp;
@@ -1423,9 +1458,11 @@ struct dentry *d_splice_alias(struct inoif (inode &&S_ISDIR (inode->i_mode)) {spin_lock (&dcache_lock); +spin_lock (&dcache_inode_lock); new =__d_find_alias (inode,1 );if (new) {BUG_ON (!(new->d_flags &DCACHE_DISCONNECTED )); +spin_unlock (&dcache_inode_lock);spin_unlock (&dcache_lock);security_d_instantiate (new, inode);d_move (new, dentry);
@@ -1433,6 +1470,7 @@ struct dentry *d_splice_alias(struct ino }else {/* already taking dcache_lock, so d_add() by hand */ __d_instantiate (dentry, inode); +spin_unlock (&dcache_inode_lock);spin_unlock (&dcache_lock);security_d_instantiate (dentry, inode);d_rehash (dentry);
@@ -1507,8 +1545,10 @@ struct dentry *d_add_ci(struct dentry *d* already has a dentry. */ spin_lock (&dcache_lock); +spin_lock (&dcache_inode_lock);if (!S_ISDIR (inode->i_mode) ||list_empty (&inode->i_dentry)) {__d_instantiate (found, inode); +spin_unlock (&dcache_inode_lock);spin_unlock (&dcache_lock);security_d_instantiate (found, inode);return found;
@@ -1520,6 +1560,7 @@ struct dentry *d_add_ci(struct dentry *d*/ new = list_entry(inode->i_dentry.next, struct dentry, d_alias);dget_locked (new); +spin_unlock (&dcache_inode_lock);spin_unlock (&dcache_lock);security_d_instantiate (found, inode);d_move (new, found);
@@ -1738,6 +1779,7 @@ void d_delete(struct dentry * dentry)* Are we the only user? */ spin_lock (&dcache_lock); +spin_lock (&dcache_inode_lock);spin_lock (&dentry->d_lock); isdir =S_ISDIR (dentry->d_inode->i_mode);if (dentry->d_count ==1 ) {
@@ -1751,6 +1793,7 @@ void d_delete(struct dentry * dentry)__d_drop (dentry);spin_unlock (&dentry->d_lock); +spin_unlock (&dcache_inode_lock);spin_unlock (&dcache_lock);fsnotify_nameremove (dentry, isdir);
@@ -2003,6 +2046,7 @@ out_unalias:d_move_locked (alias, dentry); ret = alias; out_err: +spin_unlock (&dcache_inode_lock);spin_unlock (&dcache_lock);if (m2)mutex_unlock (m2);
@@ -2068,6 +2112,7 @@ struct dentry *d_materialise_unique(struBUG_ON (!d_unhashed (dentry));spin_lock (&dcache_lock); +spin_lock (&dcache_inode_lock);if (!inode) { actual = dentry;
@@ -2111,6 +2156,7 @@ found:_d_rehash (actual);spin_unlock (&dcache_hash_lock);spin_unlock (&actual->d_lock); +spin_unlock (&dcache_inode_lock);spin_unlock (&dcache_lock); out_nolock:if (actual == dentry) {
@@ -2122,6 +2168,7 @@ out_nolock:return actual; shouldnt_be_hashed: +spin_unlock (&dcache_inode_lock);spin_unlock (&dcache_lock);BUG (); }
include/linux/dcache.h +1 -0
@@ -188,6 +188,7 @@ d_iput: no no no yes#define DCACHE_CANT_MOUNT 0x0100 +extern spinlock_t dcache_inode_lock;extern spinlock_t dcache_hash_lock;extern spinlock_t dcache_lock;extern seqlock_t rename_lock;
fs/notify/inotify/inotify.c +2 -0
@@ -182,6 +182,7 @@ static void set_dentry_child_flags(strucstruct dentry *alias;spin_lock (&dcache_lock); +spin_lock (&dcache_inode_lock);list_for_each_entry (alias, &inode->i_dentry, d_alias) {struct dentry *child;
@@ -199,6 +200,7 @@ static void set_dentry_child_flags(struc }spin_unlock (&alias->d_lock); } +spin_unlock (&dcache_inode_lock);spin_unlock (&dcache_lock); }
fs/exportfs/expfs.c +4 -0
@@ -48,8 +48,10 @@ find_acceptable_alias(struct dentry *resreturn result;spin_lock (&dcache_lock); +spin_lock (&dcache_inode_lock);list_for_each_entry (dentry, &result->d_inode->i_dentry, d_alias) {dget_locked (dentry); +spin_unlock (&dcache_inode_lock);spin_unlock (&dcache_lock);if (toput)dput (toput);
@@ -58,8 +60,10 @@ find_acceptable_alias(struct dentry *resreturn dentry; }spin_lock (&dcache_lock); +spin_lock (&dcache_inode_lock); toput = dentry; } +spin_unlock (&dcache_inode_lock);spin_unlock (&dcache_lock);if (toput)
fs/affs/amigaffs.c +2 -0
@@ -129,6 +129,7 @@ affs_fix_dcache(struct dentry *dentry, ustruct list_head *head, *next;spin_lock (&dcache_lock); +spin_lock (&dcache_inode_lock); head = &inode->i_dentry; next = head->next;while (next != head) {
@@ -139,6 +140,7 @@ affs_fix_dcache(struct dentry *dentry, u } next = next->next; } +spin_unlock (&dcache_inode_lock);spin_unlock (&dcache_lock); }
fs/ocfs2/dcache.c +2 -1
@@ -152,7 +152,7 @@ struct dentry *ocfs2_find_local_alias(ststruct dentry *dentry =NULL ;spin_lock (&dcache_lock); - +spin_lock (&dcache_inode_lock);list_for_each (p, &inode->i_dentry) { dentry = list_entry(p, struct dentry, d_alias);
@@ -170,6 +170,7 @@ struct dentry *ocfs2_find_local_alias(st dentry =NULL ; } +spin_unlock (&dcache_inode_lock);spin_unlock (&dcache_lock);return dentry;
fs/nfs/getroot.c +4 -0
@@ -65,7 +65,11 @@ static int nfs_superblock_set_dummy_root* Oops, since the test for IS_ROOT() will fail. */ spin_lock (&dcache_lock); +spin_lock (&dcache_inode_lock); +spin_lock (&sb->s_root->d_lock);list_del_init (&sb->s_root->d_alias); +spin_unlock (&sb->s_root->d_lock); +spin_unlock (&dcache_inode_lock);spin_unlock (&dcache_lock); }return 0 ;
fs/notify/fsnotify.c +2 -0
@@ -54,6 +54,7 @@ void __fsnotify_update_child_dentry_flag watched =fsnotify_inode_watches_children (inode);spin_lock (&dcache_lock); +spin_lock (&dcache_inode_lock);/* run all of the dentries associated with this inode. Since this is a * directory, there damn well better only be one item on this list */ list_for_each_entry (alias, &inode->i_dentry, d_alias) {
@@ -76,6 +77,7 @@ void __fsnotify_update_child_dentry_flag }spin_unlock (&alias->d_lock); } +spin_unlock (&dcache_inode_lock);spin_unlock (&dcache_lock); }
PATCH 16/52 fs: dcache RCU for multi-step operaitons
In reply to [email protected]The remaining usages for dcache_lock is to allow atomic, multi-step read-side operations over the directory tree by excluding modifications to the tree. Also, to walk in the leaf->root direction in the tree where we don't have a natural d_lock ordering.
This could be accomplished by taking every d_lock, but this would mean a huge number of locks and actually gets very tricky.
Solve this instead by using the rename seqlock for multi-step read-side operations. Insert operations are not serialised. Delete operations are tricky when walking up the directory our parent might have been deleted when dropping locks so also need to check and retry for that.
XXX: hmm, we could of course just take the rename lock if there is any worry about livelock. Most of these are slow paths.
--- drivers/staging/pohmelfs/path_entry.c | 15 ++- fs/autofs4/waitq.c | 16 +++ fs/dcache.c | 151 +++++++++++++++++++++++++++------- fs/nfs/namespace.c | 14 ++- fs/seq_file.c | 1 5 files changed, 163 insertions(+), 34 deletions(-)
fs/dcache.c +131 -30
@@ -80,6 +80,7 @@ static __cacheline_aligned_in_smp DEFINE__cacheline_aligned_in_smp DEFINE_SPINLOCK (dcache_lock );__cacheline_aligned_in_smp DEFINE_SEQLOCK (rename_lock ); +EXPORT_SYMBOL (rename_lock);EXPORT_SYMBOL (dcache_inode_lock);EXPORT_SYMBOL (dcache_hash_lock);EXPORT_SYMBOL (dcache_lock);
@@ -961,11 +962,15 @@ void shrink_dcache_for_umount(struct sup* Return true if the parent or its subdirectories contain * a mount point */ -int have_submounts (struct dentry *parent) { -struct dentry *this_parent = parent; +struct dentry *this_parent;struct list_head *next; +unsigned seq; + +rename_retry: + this_parent = parent; + seq =read_seqbegin (&rename_lock);spin_lock (&dcache_lock);if (d_mountpoint (parent))
@@ -999,17 +1004,38 @@ resume:* All done at this level ... ascend and resume the search. */ if (this_parent != parent) { - next = this_parent->d_u.d_child.next; +struct dentry *tmp; +struct dentry *child; + + tmp = this_parent->d_parent; +rcu_read_lock ();spin_unlock (&this_parent->d_lock); - this_parent = this_parent->d_parent; + child = this_parent; + this_parent = tmp;spin_lock (&this_parent->d_lock); +/* might go back up the wrong parent if we have had a rename +* or deletion */ +if (this_parent != child->d_parent || +// d_unlinked(this_parent) || XXX +read_seqretry (&rename_lock, seq)) { +spin_unlock (&this_parent->d_lock); +spin_unlock (&dcache_lock); +rcu_read_unlock (); + goto rename_retry; + } +rcu_read_unlock (); + next = child->d_u.d_child.next; goto resume; }spin_unlock (&this_parent->d_lock);spin_unlock (&dcache_lock); +if (read_seqretry (&rename_lock, seq)) + goto rename_retry;return 0 ;/* No mount points found in tree */ positive:spin_unlock (&dcache_lock); +if (read_seqretry (&rename_lock, seq)) + goto rename_retry;return 1 ; }EXPORT_SYMBOL (have_submounts);
@@ -1030,9 +1056,15 @@ EXPORT_SYMBOL(have_submounts);*/ static int select_parent (struct dentry * parent) { -struct dentry *this_parent = parent; +struct dentry *this_parent;struct list_head *next; -int found =0 ; +unsigned seq; +int found; + +rename_retry: + found =0 ; + this_parent = parent; + seq =read_seqbegin (&rename_lock);spin_lock (&dcache_lock);spin_lock (&this_parent->d_lock);
@@ -1043,7 +1075,6 @@ resume:struct list_head *tmp = next; struct dentry *dentry = list_entry(tmp, struct dentry, d_u.d_child); next = tmp->next; -BUG_ON (this_parent == dentry);spin_lock_nested (&dentry->d_lock,DENTRY_D_LOCK_NESTED );dentry_lru_del_init (dentry);
@@ -1084,17 +1115,33 @@ resume:*/ if (this_parent != parent) {struct dentry *tmp; - next = this_parent->d_u.d_child.next; +struct dentry *child; + tmp = this_parent->d_parent; +rcu_read_lock ();spin_unlock (&this_parent->d_lock); -BUG_ON (tmp == this_parent); + child = this_parent; this_parent = tmp;spin_lock (&this_parent->d_lock); +/* might go back up the wrong parent if we have had a rename +* or deletion */ +if (this_parent != child->d_parent || +// d_unlinked(this_parent) || XXX +read_seqretry (&rename_lock, seq)) { +spin_unlock (&this_parent->d_lock); +spin_unlock (&dcache_lock); +rcu_read_unlock (); + goto rename_retry; + } +rcu_read_unlock (); + next = child->d_u.d_child.next; goto resume; } out:spin_unlock (&this_parent->d_lock);spin_unlock (&dcache_lock); +if (read_seqretry (&rename_lock, seq)) + goto rename_retry;return found; }
@@ -1606,7 +1653,7 @@ EXPORT_SYMBOL(d_add_ci);struct dentry *d_lookup (struct dentry * parent,struct qstr * name) {struct dentry * dentry =NULL ; -unsigned long seq; +unsigned seq; do { seq = read_seqbegin(&rename_lock);
@@ -2211,12 +2258,20 @@ static int prepend_name(char **buffer, ichar *__d_path (const struct path *path,struct path *root,char *buffer,int buflen) { -struct dentry *dentry = path->dentry; -struct vfsmount *vfsmnt = path->mnt; -char *end = buffer + buflen; +struct dentry *dentry; +struct vfsmount *vfsmnt; +char *end;char *retval; +unsigned seq; +rename_retry: + dentry = path->dentry; + vfsmnt = path->mnt; + end = buffer + buflen;prepend (&end, &buflen,"\0" ,1 ); + + seq =read_seqbegin (&rename_lock); +rcu_read_lock ();spin_lock (&dentry->d_lock); unlinked: if (d_unlinked(dentry) &&
@@ -2253,12 +2308,16 @@ unlinked: retval = end;spin_unlock (&dentry->d_lock); dentry = parent; +spin_lock (&dentry->d_lock);if (d_unlinked (dentry)) goto unlinked; } out:spin_unlock (&dentry->d_lock); +rcu_read_unlock (); +if (read_seqretry (&rename_lock, seq)) + goto rename_retry;return retval; global_root:
@@ -2267,6 +2326,7 @@ global_root: goto Elong; root->mnt = vfsmnt; root->dentry = dentry; +/* XXX: this could wrongly modify root if we rename retry */ goto out; Elong:
@@ -2349,12 +2409,19 @@ char *dynamic_dname(struct dentry *dentr*/ char *dentry_path (struct dentry *dentry,char *buf,int buflen) { -char *end = buf + buflen; +char *end;char *retval; +unsigned seq; + +rename_retry: + end = buf + buflen; +prepend (&end, &buflen,"\0" ,1 ); + seq =read_seqbegin (&rename_lock);spin_lock (&dcache_lock); +br_read_lock (vfsmount_lock); +rcu_read_lock ();/* protect parent */ spin_lock (&dentry->d_lock); -prepend (&end, &buflen,"\0" ,1 ); unlinked:if (d_unlinked (dentry) && (prepend (&end, &buflen,"//deleted" ,9 ) !=0 ))
@@ -2380,13 +2447,17 @@ unlinked:if (d_unlinked (dentry)) goto unlinked; } +out:spin_unlock (&dentry->d_lock); +rcu_read_unlock (); +br_read_unlock (vfsmount_lock);spin_unlock (&dcache_lock); +if (read_seqretry (&rename_lock, seq)) + goto rename_retry;return retval; Elong: -spin_unlock (&dentry->d_lock); -spin_unlock (&dcache_lock); -return ERR_PTR (-ENAMETOOLONG ); + retval =ERR_PTR (-ENAMETOOLONG ); + goto out; }/*
@@ -2481,25 +2552,25 @@ out:int is_subdir (struct dentry *new_dentry,struct dentry *old_dentry) {int result; -unsigned long seq; +unsigned seq;if (new_dentry == old_dentry)return 1 ; -/* -* Need rcu_readlock to protect against the d_parent trashing -* due to d_move -*/ -rcu_read_lock ();do {/* for restarting inner loop in case of seq retry */ seq =read_seqbegin (&rename_lock); +/* +* Need rcu_readlock to protect against the d_parent trashing +* due to d_move +*/ +rcu_read_lock ();if (d_ancestor (old_dentry, new_dentry)) result =1 ;else result =0 ; +rcu_read_unlock (); }while (read_seqretry (&rename_lock, seq)); -rcu_read_unlock ();return result; }
@@ -2531,9 +2602,13 @@ EXPORT_SYMBOL(path_is_under);void d_genocide (struct dentry *root) { -struct dentry *this_parent = root; +struct dentry *this_parent;struct list_head *next; +unsigned seq; +rename_retry: + this_parent = root; + seq =read_seqbegin (&rename_lock);spin_lock (&dcache_lock);spin_lock (&this_parent->d_lock); repeat:
@@ -2543,6 +2618,7 @@ resume:struct list_head *tmp = next; struct dentry *dentry = list_entry(tmp, struct dentry, d_u.d_child); next = tmp->next; +spin_lock_nested (&dentry->d_lock,DENTRY_D_LOCK_NESTED );if (d_unhashed (dentry) || !dentry->d_inode) {spin_unlock (&dentry->d_lock);
@@ -2555,19 +2631,44 @@ resume:spin_acquire (&this_parent->d_lock.dep_map,0 ,1 , _RET_IP_); goto repeat; } - dentry->d_count--; +if (!(dentry->d_flags &DCACHE_GENOCIDE )) { + dentry->d_flags |=DCACHE_GENOCIDE ; + dentry->d_count--; + }spin_unlock (&dentry->d_lock); }if (this_parent != root) { - next = this_parent->d_u.d_child.next; - this_parent->d_count--; +struct dentry *tmp; +struct dentry *child; + + tmp = this_parent->d_parent; +if (!(this_parent->d_flags &DCACHE_GENOCIDE )) { + this_parent->d_flags |=DCACHE_GENOCIDE ; + this_parent->d_count--; + } +rcu_read_lock ();spin_unlock (&this_parent->d_lock); - this_parent = this_parent->d_parent; + child = this_parent; + this_parent = tmp;spin_lock (&this_parent->d_lock); +/* might go back up the wrong parent if we have had a rename +* or deletion */ +if (this_parent != child->d_parent || +// d_unlinked(this_parent) || XXX +read_seqretry (&rename_lock, seq)) { +spin_unlock (&this_parent->d_lock); +spin_unlock (&dcache_lock); +rcu_read_unlock (); + goto rename_retry; + } +rcu_read_unlock (); + next = child->d_u.d_child.next; goto resume; }spin_unlock (&this_parent->d_lock);spin_unlock (&dcache_lock); +if (read_seqretry (&rename_lock, seq)) + goto rename_retry; }/**
fs/seq_file.c +1 -0
@@ -470,6 +470,7 @@ int seq_path_root(struct seq_file *m, st p =__d_path (path, root, buf, size);br_read_unlock (vfsmount_lock);spin_unlock (&dcache_lock); + res =PTR_ERR (p);if (!IS_ERR (p)) {char *end =mangle_path (buf, p, esc);
drivers/staging/pohmelfs/path_entry.c +12 -3
@@ -83,10 +83,11 @@ out:int pohmelfs_path_length (struct pohmelfs_inode *pi) {struct dentry *d, *root, *first; -int len =1 ;/* Root slash */ +int len; +unsigned seq; - first = d =d_find_alias (&pi->vfs_inode); -if (!d) { + first =d_find_alias (&pi->vfs_inode); +if (!first) {dprintk ("%s: ino: %llu, mode: %o.\n" , __func__, pi->ino, pi->vfs_inode.i_mode);return -ENOENT ; }
@@ -95,6 +96,11 @@ int pohmelfs_path_length(struct pohmelfs root =dget (current->fs->root.dentry);spin_unlock (¤t->fs->lock); +rename_retry: + len =1 ;/* Root slash */ + d = first; + seq =read_seqbegin (&rename_lock); +rcu_read_lock ();spin_lock (&dcache_lock);if (!IS_ROOT (d) &&d_unhashed (d))
@@ -105,6 +111,9 @@ int pohmelfs_path_length(struct pohmelfs d = d->d_parent; }spin_unlock (&dcache_lock); +rcu_read_unlock (); +if (read_seqretry (&rename_lock, seq)) + goto rename_retry;dput (root);dput (first);
fs/autofs4/waitq.c +14 -2
@@ -186,16 +186,25 @@ static int autofs4_getpath(struct autofs {struct dentry *root = sbi->sb->s_root;struct dentry *tmp; -char *buf = *name; +char *buf;char *p; -int len =0 ; +int len; +unsigned seq; +rename_retry: + buf = *name; + len =0 ; + seq =read_seqbegin (&rename_lock); +rcu_read_lock ();spin_lock (&dcache_lock);for (tmp = dentry ; tmp != root ; tmp = tmp->d_parent) len += tmp->d_name.len +1 ;if (!len || --len >NAME_MAX ) {spin_unlock (&dcache_lock); +rcu_read_unlock (); +if (read_seqretry (&rename_lock, seq)) + goto rename_retry;return 0 ; }
@@ -209,6 +218,9 @@ static int autofs4_getpath(struct autofsstrncpy (p, tmp->d_name.name, tmp->d_name.len); }spin_unlock (&dcache_lock); +rcu_read_unlock (); +if (read_seqretry (&rename_lock, seq)) + goto rename_retry;return len; }
fs/nfs/namespace.c +13 -1
@@ -49,11 +49,17 @@ char *nfs_path(const char *base,const struct dentry *dentry, char *buffer, ssize_t buflen) { - char *end = buffer+buflen; + char *end;int namelen; +unsigned seq; +rename_retry: + end = buffer+buflen; *--end ='\0' ; buflen--; + + seq =read_seqbegin (&rename_lock); +rcu_read_lock ();spin_lock (&dcache_lock);while (!IS_ROOT (dentry) && dentry != droot) { namelen = dentry->d_name.len;
@@ -66,6 +72,9 @@ char *nfs_path(const char *base, dentry = dentry->d_parent; }spin_unlock (&dcache_lock); +rcu_read_unlock (); +if (read_seqretry (&rename_lock, seq)) + goto rename_retry;if (*end !='/' ) {if (--buflen <0 ) goto Elong;
@@ -83,6 +92,9 @@ char *nfs_path(const char *base,return end; Elong_unlock:spin_unlock (&dcache_lock); +rcu_read_unlock (); +if (read_seqretry (&rename_lock, seq)) + goto rename_retry; Elong:return ERR_PTR (-ENAMETOOLONG ); }
include/linux/dcache.h +1 -0
@@ -187,6 +187,7 @@ d_iput: no no no yes#define DCACHE_FSNOTIFY_PARENT_WATCHED 0x0080/* Parent inode is watched by some fsnotify listener */ #define DCACHE_CANT_MOUNT 0x0100 +#define DCACHE_GENOCIDE 0x0200extern spinlock_t dcache_inode_lock;extern spinlock_t dcache_hash_lock;
Peter Zijlstra
maintainerRe: [patch 16/52] fs: dcache RCU for multi-step operaitons
Quoted 9 lines · On Thu, 2010-06-24 at 13:02 +1000, [email protected] wrote:
plain text document attachment (fs-dcache_lock-multi-step.patch) The remaining usages for
dcache_lockis to allow atomic, multi-step read-side operations over the directory tree by excluding modifications to the tree. Also, to walk in theleaf->rootdirection in the tree where we don't have a naturald_lockordering.This could be accomplished by taking every
d_lock, but this would mean a huge number of locks and actually gets very tricky.
Quoted 7 lines
Solve this instead by using the rename seqlock for multi-step read-side operations. Insert operations are not serialised. Delete operations are tricky when walking up the directory our parent might have been deleted when dropping locks so also need to check and retry for that.
XXX: hmm, we could of course just take the rename lock if there is any worry about livelock. Most of these are slow paths.
Ah, does this address John's issue?
Re: [patch 16/52] fs: dcache RCU for multi-step operaitons
Quoted 15 lines · Peter Zijlstra
Quoted 14 lines · On Thu, 2010-06-24 at 13:02 +1000, [email protected] wrote:
plain text document attachment (fs-dcache_lock-multi-step.patch) The remaining usages for
dcache_lockis to allow atomic, multi-step read-side operations over the directory tree by excluding modifications to the tree. Also, to walk in theleaf->rootdirection in the tree where we don't have a naturald_lockordering.This could be accomplished by taking every
d_lock, but this would mean a huge number of locks and actually gets very tricky.Solve this instead by using the rename seqlock for multi-step read-side operations. Insert operations are not serialised. Delete operations are tricky when walking up the directory our parent might have been deleted when dropping locks so also need to check and retry for that.
Quoted 5 lines
Quoted 2 lines
XXX: hmm, we could of course just take the rename lock if there is any worry about livelock. Most of these are slow paths.
Ah, does this address John's issue?
This is where John's issue is introduced. I actually again couldn't see the problem (thought I saw a problem, then lost it!).
Got to think about it and test more... I couldn't reproduce the problem mind you, but I was testing mainline wheras bug was seen on -rt.
Re: [patch 16/52] fs: dcache RCU for multi-step operaitons
Quoted 16 lines · Nick Piggin
Quoted 15 lines · Peter Zijlstra
Quoted 14 lines · On Thu, 2010-06-24 at 13:02 +1000, [email protected] wrote:
plain text document attachment (fs-dcache_lock-multi-step.patch) The remaining usages for
dcache_lockis to allow atomic, multi-step read-side operations over the directory tree by excluding modifications to the tree. Also, to walk in theleaf->rootdirection in the tree where we don't have a naturald_lockordering.This could be accomplished by taking every
d_lock, but this would mean a huge number of locks and actually gets very tricky.Solve this instead by using the rename seqlock for multi-step read-side operations. Insert operations are not serialised. Delete operations are tricky when walking up the directory our parent might have been deleted when dropping locks so also need to check and retry for that.
Quoted 8 lines
Quoted 5 lines
Quoted 2 lines
XXX: hmm, we could of course just take the rename lock if there is any worry about livelock. Most of these are slow paths.
Ah, does this address John's issue?
This is where John's issue is introduced. I actually again couldn't see the problem (thought I saw a problem, then lost it!).
Ok. So I need to review this new set in full, but the issue we tripped with the patches in -rt was the following:
In select parent, when it does the dentry ascending, it has to release the current dentry lock so it can aquire the parents dentry (for proper ordering). At the point it released the lock, before it grabs the parent's lock, there is nothing that is preventing dput from being called on the next dentry, it grabbing the parent and dentry's d_lock and killing it. Then back in select_parent, when we try to lock the next entry, it no longer exists and we oops.
So I can't see anything that is protecting the dentry (or even the new parent dentry, should everything be killed under it) at this point. The dentries d_count might be zero, we don't hold the d_lock, and we don't hold the parent's d_lock. What am I missing here?
Quoted 2 lines
Got to think about it and test more... I couldn't reproduce the problem mind you, but I was testing mainline wheras bug was seen on -rt.
Yea, -rt may be allowing the race to more easily occur (I only found I could trigger it on a UP machine) since we can be preempted right after we released the dentry lock as we tried to grab the parent. Then dput would jump in and wreck things. I had some pretty clear trace logs that were easily repeated (well, took about an hour or two to trigger), that showed about the same order of operations every time.
I don't remember if there's another lock being held at the point select_parent is called, so on mainline it might be harder to trigger (having to actually get the dput race on another cpu timed right).
thanks
-john
Re: [patch 16/52] fs: dcache RCU for multi-step operaitons
In reply to [email protected]Quoted 9 lines · On Thu, 2010-06-24 at 13:02 +1000, [email protected] wrote:
plain text document attachment (fs-dcache_lock-multi-step.patch) The remaining usages for
dcache_lockis to allow atomic, multi-step read-side operations over the directory tree by excluding modifications to the tree. Also, to walk in theleaf->rootdirection in the tree where we don't have a naturald_lockordering.This could be accomplished by taking every
d_lock, but this would mean a huge number of locks and actually gets very tricky.
Quoted 7 lines
Solve this instead by using the rename seqlock for multi-step read-side operations. Insert operations are not serialised. Delete operations are tricky when walking up the directory our parent might have been deleted when dropping locks so also need to check and retry for that.
XXX: hmm, we could of course just take the rename lock if there is any worry about livelock. Most of these are slow paths.
I'll try to point out exactly the spot I think we were hitting in the -rt tree (once the dcache_lock is removed).
Quoted 29 lines
fs/dcache.c +8
-3@@ -1030,9 +1056,15 @@ EXPORT_SYMBOL(have_submounts);*/ static int select_parent (struct dentry * parent) { -struct dentry *this_parent = parent; +struct dentry *this_parent;struct list_head *next; -int found =0 ; +unsigned seq; +int found; + +rename_retry: + found =0 ; + this_parent = parent; + seq =read_seqbegin (&rename_lock);spin_lock (&dcache_lock);spin_lock (&this_parent->d_lock);@@ -1043,7 +1075,6 @@ resume:struct list_head *tmp = next; struct dentry *dentry = list_entry(tmp, struct dentry, d_u.d_child); next = tmp->next; -BUG_ON (this_parent == dentry);spin_lock_nested (&dentry->d_lock,DENTRY_D_LOCK_NESTED );dentry_lru_del_init (dentry);@@ -1084,17 +1115,33 @@ resume:*/ if (this_parent != parent) {
fs/dcache.c resume: · line 1124
Quoted 10 lines
fs/dcache.c +4
-2struct dentry *tmp; - next = this_parent->d_u.d_child.next; +struct dentry *child; + tmp = this_parent->d_parent; +rcu_read_lock ();spin_unlock (&this_parent->d_lock); -BUG_ON (tmp == this_parent); + child = this_parent; this_parent = tmp;
Ok. So right here, we get preempted, or dput() is called by another cpu on the child dentry, or the child->d_u.d_child.next dentry and its d_kill'ed.
fs/dcache.c resume: · line 1027
Quoted 13 lines
fs/dcache.c +12
-0spin_lock (&this_parent->d_lock); +/* might go back up the wrong parent if we have had a rename +* or deletion */ +if (this_parent != child->d_parent || +// d_unlinked(this_parent) || XXX +read_seqretry (&rename_lock, seq)) { +spin_unlock (&this_parent->d_lock); +spin_unlock (&dcache_lock); +rcu_read_unlock (); + goto rename_retry; + } +rcu_read_unlock (); + next = child->d_u.d_child.next;
Then at this point, next may point to junk.
fs/dcache.c resume: · line 1146
Quoted 9 lines
fs/dcache.c +2
-0goto resume; } out:spin_unlock (&this_parent->d_lock);spin_unlock (&dcache_lock); +if (read_seqretry (&rename_lock, seq)) + goto rename_retry;return found; }
thanks
-john
Re: [patch 16/52] fs: dcache RCU for multi-step operaitons
Quoted 66 lines · John Stultz
Quoted 9 lines · On Thu, 2010-06-24 at 13:02 +1000, [email protected] wrote:
plain text document attachment (fs-dcache_lock-multi-step.patch) The remaining usages for
dcache_lockis to allow atomic, multi-step read-side operations over the directory tree by excluding modifications to the tree. Also, to walk in theleaf->rootdirection in the tree where we don't have a naturald_lockordering.This could be accomplished by taking every
d_lock, but this would mean a huge number of locks and actually gets very tricky.Quoted 7 lines
Solve this instead by using the rename seqlock for multi-step read-side operations. Insert operations are not serialised. Delete operations are tricky when walking up the directory our parent might have been deleted when dropping locks so also need to check and retry for that.
XXX: hmm, we could of course just take the rename lock if there is any worry about livelock. Most of these are slow paths.
I'll try to point out exactly the spot I think we were hitting in the -rt tree (once the
dcache_lockis removed).Quoted 29 lines
fs/dcache.c +8
-3@@ -1030,9 +1056,15 @@ EXPORT_SYMBOL(have_submounts);*/ static int select_parent (struct dentry * parent) { -struct dentry *this_parent = parent; +struct dentry *this_parent;struct list_head *next; -int found =0 ; +unsigned seq; +int found; + +rename_retry: + found =0 ; + this_parent = parent; + seq =read_seqbegin (&rename_lock);spin_lock (&dcache_lock);spin_lock (&this_parent->d_lock);@@ -1043,7 +1075,6 @@ resume:struct list_head *tmp = next; struct dentry *dentry = list_entry(tmp, struct dentry, d_u.d_child); next = tmp->next; -BUG_ON (this_parent == dentry);spin_lock_nested (&dentry->d_lock,DENTRY_D_LOCK_NESTED );dentry_lru_del_init (dentry);@@ -1084,17 +1115,33 @@ resume:*/ if (this_parent != parent) {fs/dcache.c resume: · line 1124
Quoted 10 lines
fs/dcache.c +4
-2struct dentry *tmp; - next = this_parent->d_u.d_child.next; +struct dentry *child; + tmp = this_parent->d_parent; +rcu_read_lock ();spin_unlock (&this_parent->d_lock); -BUG_ON (tmp == this_parent); + child = this_parent; this_parent = tmp;Ok. So right here, we get preempted, or
dput()is called by another cpu on the child dentry, or thechild->d_u.d_child.nextdentry and its d_kill'ed.
Quoted 15 lines
fs/dcache.c resume: · line 1027
Quoted 13 lines
fs/dcache.c +12
-0spin_lock (&this_parent->d_lock); +/* might go back up the wrong parent if we have had a rename +* or deletion */ +if (this_parent != child->d_parent || +// d_unlinked(this_parent) || XXX +read_seqretry (&rename_lock, seq)) { +spin_unlock (&this_parent->d_lock); +spin_unlock (&dcache_lock); +rcu_read_unlock (); + goto rename_retry; + } +rcu_read_unlock (); + next = child->d_u.d_child.next;Then at this point, next may point to junk.
But see the test above it. We ensure that child->d_parent still points to this_parent with this_parent d_lock held. Oh, I'm not clearing d_parent! d_kill() should have
dentry->d_parent = NULL;
when it removes dentry from the list.
That should fix it I'd hope.
Thanks,
Nick
PATCH 17/52 fs: dcache remove dcache_lock
In reply to [email protected]dcache_lock no longer protects anything. remove it.
--- Documentation/filesystems/Locking | 2 arch/powerpc/platforms/cell/spufs/inode.c | 5 - drivers/infiniband/hw/ipath/ipath_fs.c | 6 - drivers/infiniband/hw/qib/qib_fs.c | 3 drivers/staging/pohmelfs/path_entry.c | 2 drivers/usb/core/inode.c | 3 fs/affs/amigaffs.c | 2 fs/autofs4/autofs_i.h | 3 fs/autofs4/expire.c | 24 ++--- fs/autofs4/root.c | 14 +-- fs/autofs4/waitq.c | 7 - fs/ceph/dir.c | 6 - fs/ceph/inode.c | 4 fs/coda/cache.c | 2 fs/configfs/configfs_internal.h | 2 fs/configfs/inode.c | 6 - fs/dcache.c | 135 ++++-------------------------- fs/exportfs/expfs.c | 4 fs/namei.c | 9 -- fs/ncpfs/dir.c | 3 fs/ncpfs/ncplib_kernel.h | 4 fs/nfs/dir.c | 3 fs/nfs/getroot.c | 2 fs/nfs/namespace.c | 3 fs/notify/fsnotify.c | 2 fs/notify/inotify/inotify.c | 4 fs/ocfs2/dcache.c | 2 fs/seq_file.c | 2 fs/smbfs/cache.c | 4 include/linux/dcache.h | 17 +-- include/linux/fs.h | 4 include/linux/fsnotify_backend.h | 5 - kernel/cgroup.c | 6 - security/selinux/selinuxfs.c | 4 security/tomoyo/realpath.c | 2 35 files changed, 67 insertions(+), 239 deletions(-)
fs/dcache.c +20 -115
@@ -54,11 +54,10 @@* - d_alias, d_inode * * Ordering: -* dcache_lock -* dcache_inode_lock -* dentry->d_lock -* dcache_lru_lock -* dcache_hash_lock +* dcache_inode_lock +* dentry->d_lock +* dcache_lru_lock +* dcache_hash_lock * * If there is an ancestor relationship: * dentry->d_parent->...->d_parent->d_lock
@@ -77,13 +76,11 @@ EXPORT_SYMBOL_GPL(sysctl_vfs_cache_press__cacheline_aligned_in_smp DEFINE_SPINLOCK (dcache_inode_lock );__cacheline_aligned_in_smp DEFINE_SPINLOCK (dcache_hash_lock );static __cacheline_aligned_in_smp DEFINE_SPINLOCK (dcache_lru_lock ); -__cacheline_aligned_in_smp DEFINE_SPINLOCK (dcache_lock );__cacheline_aligned_in_smp DEFINE_SEQLOCK (rename_lock );EXPORT_SYMBOL (rename_lock);EXPORT_SYMBOL (dcache_inode_lock);EXPORT_SYMBOL (dcache_hash_lock); -EXPORT_SYMBOL (dcache_lock);static struct kmem_cache *dentry_cache__read_mostly ;
@@ -125,7 +122,7 @@ static void d_callback(struct rcu_head * }/* -* no dcache_lock, please. +* no locks, please. */ static void d_free (struct dentry *dentry) {
@@ -147,7 +144,6 @@ static void d_free(struct dentry *dentrystatic void dentry_iput (struct dentry * dentry) __releases(dentry->d_lock)__releases (dcache_inode_lock) -__releases (dcache_lock) {struct inode *inode = dentry->d_inode;if (inode) {
@@ -155,7 +151,6 @@ static void dentry_iput(struct dentry *list_del_init (&dentry->d_alias);spin_unlock (&dentry->d_lock);spin_unlock (&dcache_inode_lock); -spin_unlock (&dcache_lock);if (!inode->i_nlink)fsnotify_inoderemove (inode);if (dentry->d_op && dentry->d_op->d_iput)
@@ -165,7 +160,6 @@ static void dentry_iput(struct dentry * }else {spin_unlock (&dentry->d_lock);spin_unlock (&dcache_inode_lock); -spin_unlock (&dcache_lock); } }
@@ -231,13 +225,12 @@ static void dentry_lru_del_init(struct d* * If this is the root of the dentry tree, return NULL. * -* dcache_lock and d_lock and d_parent->d_lock must be held by caller, and +* d_lock and d_parent->d_lock must be held by caller, and * are dropped by d_kill. */ static struct dentry *d_kill (struct dentry *dentry) __releases(dentry->d_lock)__releases (dcache_inode_lock) -__releases (dcache_lock) {struct dentry *parent;
@@ -294,21 +287,10 @@ repeat:might_sleep ();spin_lock (&dentry->d_lock);if (dentry->d_count ==1 ) { -if (!spin_trylock (&dcache_lock)) { -/* -* Something of a livelock possibility we could avoid -* by taking dcache_lock and trying again, but we -* want to reduce dcache_lock anyway so this will -* get improved. -*/ -drop1: -spin_unlock (&dentry->d_lock); - goto repeat; - }if (!spin_trylock (&dcache_inode_lock)) { drop2: -spin_unlock (&dcache_lock); - goto drop1; +spin_unlock (&dentry->d_lock); + goto repeat; } parent = dentry->d_parent;if (parent && parent != dentry) {
@@ -323,7 +305,6 @@ drop2:spin_unlock (&dentry->d_lock);if (parent && parent != dentry)spin_unlock (&parent->d_lock); -spin_unlock (&dcache_lock);return ; }
@@ -345,7 +326,6 @@ drop2:if (parent && parent != dentry)spin_unlock (&parent->d_lock);spin_unlock (&dcache_inode_lock); -spin_unlock (&dcache_lock);return ; unhash_it:
@@ -376,11 +356,9 @@ int d_invalidate(struct dentry * dentry)/* * If it's already been dropped, return OK. */ -spin_lock (&dcache_lock);spin_lock (&dentry->d_lock);if (d_unhashed (dentry)) {spin_unlock (&dentry->d_lock); -spin_unlock (&dcache_lock);return 0 ; }/*
@@ -389,9 +367,7 @@ int d_invalidate(struct dentry * dentry)*/ if (!list_empty (&dentry->d_subdirs)) {spin_unlock (&dentry->d_lock); -spin_unlock (&dcache_lock);shrink_dcache_parent (dentry); -spin_lock (&dcache_lock);spin_lock (&dentry->d_lock); }
@@ -408,19 +384,17 @@ int d_invalidate(struct dentry * dentry)if (dentry->d_count >1 ) {if (dentry->d_inode &&S_ISDIR (dentry->d_inode->i_mode)) {spin_unlock (&dentry->d_lock); -spin_unlock (&dcache_lock);return -EBUSY ; } }__d_drop (dentry);spin_unlock (&dentry->d_lock); -spin_unlock (&dcache_lock);return 0 ; }EXPORT_SYMBOL (d_invalidate); -/* This must be called with dcache_lock and d_lock held */ +/* This must be called with d_lock held */ static inline struct dentry *__dget_locked_dlock (struct dentry *dentry) { dentry->d_count++;
@@ -428,7 +402,7 @@ static inline struct dentry * __dget_locreturn dentry; } -/* This should be called _only_ with dcache_lock held */ +/* This must be called with d_lock held */ static inline struct dentry *__dget_locked (struct dentry *dentry) {spin_lock (&dentry->d_lock);
@@ -526,11 +500,9 @@ struct dentry * d_find_alias(struct inodstruct dentry *de =NULL ;if (!list_empty (&inode->i_dentry)) { -spin_lock (&dcache_lock);spin_lock (&dcache_inode_lock); de =__d_find_alias (inode,0 );spin_unlock (&dcache_inode_lock); -spin_unlock (&dcache_lock); }return de; }
@@ -544,7 +516,6 @@ void d_prune_aliases(struct inode *inode {struct dentry *dentry; restart: -spin_lock (&dcache_lock);spin_lock (&dcache_inode_lock);list_for_each_entry (dentry, &inode->i_dentry, d_alias) {spin_lock (&dentry->d_lock);
@@ -553,14 +524,12 @@ restart:__d_drop (dentry);spin_unlock (&dentry->d_lock);spin_unlock (&dcache_inode_lock); -spin_unlock (&dcache_lock);dput (dentry); goto restart; }spin_unlock (&dentry->d_lock); }spin_unlock (&dcache_inode_lock); -spin_unlock (&dcache_lock); }EXPORT_SYMBOL (d_prune_aliases);
@@ -574,20 +543,16 @@ EXPORT_SYMBOL(d_prune_aliases);*/ static void prune_one_dentry (struct dentry * dentry) __releases(dentry->d_lock) -__releases (dcache_lock) -__acquires (dcache_lock) {__d_drop (dentry); dentry =d_kill (dentry);/* -* Prune ancestors. Locking is simpler than in dput(), -* because dcache_lock needs to be taken anyway. +* Prune ancestors. */ while (dentry) {struct dentry *parent =NULL ; -spin_lock (&dcache_lock);spin_lock (&dcache_inode_lock); again:spin_lock (&dentry->d_lock);
@@ -604,7 +569,6 @@ again:spin_unlock (&parent->d_lock);spin_unlock (&dentry->d_lock);spin_unlock (&dcache_inode_lock); -spin_unlock (&dcache_lock);return ; }
@@ -673,7 +637,6 @@ restart: }spin_unlock (&dcache_lru_lock); -spin_lock (&dcache_lock);spin_lock (&dcache_inode_lock); again:spin_lock (&dcache_lru_lock);/* lru_lock also protects tmp list */
@@ -703,14 +666,13 @@ again1: }__dentry_lru_del_init (dentry);spin_unlock (&dcache_lru_lock); +prune_one_dentry (dentry); -/* dcache_lock and dentry->d_lock dropped */ -spin_lock (&dcache_lock); +/* dentry->d_lock dropped */ spin_lock (&dcache_inode_lock);spin_lock (&dcache_lru_lock); }spin_unlock (&dcache_inode_lock); -spin_unlock (&dcache_lock);if (count ==NULL && !list_empty (&sb->s_dentry_lru)) goto restart;
@@ -740,7 +702,6 @@ static void prune_dcache(int count)if (unused ==0 || count ==0 )return ; -spin_lock (&dcache_lock);if (count >= unused) prune_ratio =1 ;else
@@ -777,11 +738,9 @@ static void prune_dcache(int count)if (down_read_trylock (&sb->s_umount)) {if ((sb->s_root !=NULL ) && (!list_empty (&sb->s_dentry_lru))) { -spin_unlock (&dcache_lock);__shrink_dcache_sb (sb, &w_count,DCACHE_REFERENCED ); pruned -= w_count; -spin_lock (&dcache_lock); }up_read (&sb->s_umount); }
@@ -795,7 +754,6 @@ static void prune_dcache(int count)break ; }spin_unlock (&sb_lock); -spin_unlock (&dcache_lock); }/**
@@ -825,12 +783,10 @@ static void shrink_dcache_for_umount_subBUG_ON (!IS_ROOT (dentry));/* detach this root from the system */ -spin_lock (&dcache_lock);spin_lock (&dentry->d_lock);dentry_lru_del_init (dentry);__d_drop (dentry);spin_unlock (&dentry->d_lock); -spin_unlock (&dcache_lock);for (;;) {/* descend to the first leaf in the current subtree */
@@ -839,7 +795,6 @@ static void shrink_dcache_for_umount_sub/* this is a branch with children - detach all of them * from the system in one go */ -spin_lock (&dcache_lock);spin_lock (&dentry->d_lock);list_for_each_entry (loop, &dentry->d_subdirs, d_u.d_child) {
@@ -849,7 +804,6 @@ static void shrink_dcache_for_umount_subspin_unlock (&loop->d_lock); }spin_unlock (&dentry->d_lock); -spin_unlock (&dcache_lock);/* move to the first child */ dentry = list_entry(dentry->d_subdirs.next,
@@ -920,8 +874,7 @@ out:/* * destroy the dentries attached to a superblock on unmounting -* - we don't need to use dentry->d_lock, and only need dcache_lock when -* removing the dentry from the system lists and hashes because: +* - we don't need to use dentry->d_lock because: * - the superblock is detached from all mountings and open files, so the * dentry trees will not be rearranged by the VFS * - s_umount is write-locked, so the memory pressure shrinker will ignore
@@ -972,7 +925,6 @@ rename_retry: this_parent = parent; seq =read_seqbegin (&rename_lock); -spin_lock (&dcache_lock);if (d_mountpoint (parent)) goto positive;spin_lock (&this_parent->d_lock);
@@ -1019,7 +971,6 @@ resume:// d_unlinked(this_parent) || XXX read_seqretry(&rename_lock, seq)) {spin_unlock (&this_parent->d_lock); -spin_unlock (&dcache_lock);rcu_read_unlock (); goto rename_retry; }
@@ -1028,12 +979,10 @@ resume: goto resume; }spin_unlock (&this_parent->d_lock); -spin_unlock (&dcache_lock);if (read_seqretry (&rename_lock, seq)) goto rename_retry;return 0 ;/* No mount points found in tree */ positive: -spin_unlock (&dcache_lock);if (read_seqretry (&rename_lock, seq)) goto rename_retry;return 1 ;
@@ -1066,7 +1015,6 @@ rename_retry: this_parent = parent; seq =read_seqbegin (&rename_lock); -spin_lock (&dcache_lock);spin_lock (&this_parent->d_lock); repeat: next = this_parent->d_subdirs.next;
@@ -1129,7 +1077,6 @@ resume:// d_unlinked(this_parent) || XXX read_seqretry(&rename_lock, seq)) {spin_unlock (&this_parent->d_lock); -spin_unlock (&dcache_lock);rcu_read_unlock (); goto rename_retry; }
@@ -1139,7 +1086,6 @@ resume: } out:spin_unlock (&this_parent->d_lock); -spin_unlock (&dcache_lock);if (read_seqretry (&rename_lock, seq)) goto rename_retry;return found;
@@ -1240,7 +1186,6 @@ struct dentry *d_alloc(struct dentry * pINIT_LIST_HEAD (&dentry->d_u.d_child);if (parent) { -spin_lock (&dcache_lock);spin_lock (&parent->d_lock);spin_lock_nested (&dentry->d_lock,DENTRY_D_LOCK_NESTED ); dentry->d_parent =dget_dlock (parent);
@@ -1248,7 +1193,6 @@ struct dentry *d_alloc(struct dentry * plist_add (&dentry->d_u.d_child, &parent->d_subdirs);spin_unlock (&dentry->d_lock);spin_unlock (&parent->d_lock); -spin_unlock (&dcache_lock); }atomic_inc (&dentry_stat.nr_dentry);
@@ -1268,7 +1212,6 @@ struct dentry *d_alloc_name(struct dentr }EXPORT_SYMBOL (d_alloc_name); -/* the caller must hold dcache_lock */ static void __d_instantiate (struct dentry *dentry,struct inode *inode) {spin_lock (&dentry->d_lock);
@@ -1297,11 +1240,9 @@ static void __d_instantiate(struct dentrvoid d_instantiate (struct dentry *entry,struct inode * inode) {BUG_ON (!list_empty (&entry->d_alias)); -spin_lock (&dcache_lock);spin_lock (&dcache_inode_lock);__d_instantiate (entry, inode);spin_unlock (&dcache_inode_lock); -spin_unlock (&dcache_lock);security_d_instantiate (entry, inode); }EXPORT_SYMBOL (d_instantiate);
@@ -1360,11 +1301,9 @@ struct dentry *d_instantiate_unique(struBUG_ON (!list_empty (&entry->d_alias)); -spin_lock (&dcache_lock);spin_lock (&dcache_inode_lock); result =__d_instantiate_unique (entry, inode);spin_unlock (&dcache_inode_lock); -spin_unlock (&dcache_lock);if (!result) {security_d_instantiate (entry, inode);
@@ -1453,12 +1392,10 @@ struct dentry *d_obtain_alias(struct ino } tmp->d_parent = tmp;/* make sure dput doesn't croak */ -spin_lock (&dcache_lock);spin_lock (&dcache_inode_lock); res =__d_find_alias (inode,0 );if (res) {spin_unlock (&dcache_inode_lock); -spin_unlock (&dcache_lock);dput (tmp); goto out_iput; }
@@ -1474,7 +1411,6 @@ struct dentry *d_obtain_alias(struct inospin_unlock (&tmp->d_lock);spin_unlock (&dcache_inode_lock); -spin_unlock (&dcache_lock);return tmp; out_iput:
@@ -1504,21 +1440,18 @@ struct dentry *d_splice_alias(struct inostruct dentry *new =NULL ;if (inode &&S_ISDIR (inode->i_mode)) { -spin_lock (&dcache_lock);spin_lock (&dcache_inode_lock); new =__d_find_alias (inode,1 );if (new) {BUG_ON (!(new->d_flags &DCACHE_DISCONNECTED ));spin_unlock (&dcache_inode_lock); -spin_unlock (&dcache_lock);security_d_instantiate (new, inode);d_move (new, dentry);iput (inode); }else { -/* already taking dcache_lock, so d_add() by hand */ +/* already got dcache_inode_lock, so d_add() by hand */ __d_instantiate (dentry, inode);spin_unlock (&dcache_inode_lock); -spin_unlock (&dcache_lock);security_d_instantiate (dentry, inode);d_rehash (dentry); }
@@ -1591,12 +1524,10 @@ struct dentry *d_add_ci(struct dentry *d* Negative dentry: instantiate it unless the inode is a directory and * already has a dentry. */ -spin_lock (&dcache_lock);spin_lock (&dcache_inode_lock);if (!S_ISDIR (inode->i_mode) ||list_empty (&inode->i_dentry)) {__d_instantiate (found, inode);spin_unlock (&dcache_inode_lock); -spin_unlock (&dcache_lock);security_d_instantiate (found, inode);return found; }
@@ -1608,7 +1539,6 @@ struct dentry *d_add_ci(struct dentry *d new = list_entry(inode->i_dentry.next, struct dentry, d_alias);dget_locked (new);spin_unlock (&dcache_inode_lock); -spin_unlock (&dcache_lock);security_d_instantiate (found, inode);d_move (new, found);iput (inode);
@@ -1631,7 +1561,7 @@ EXPORT_SYMBOL(d_add_ci);* is returned. The caller must use dput to free the entry when it has * finished using it. %NULL is returned on failure. * -* __d_lookup is dcache_lock free. The hash list is protected using RCU. +* __d_lookup is global lock free. The hash list is protected using RCU. * Memory barriers are used while updating and doing lockless traversal. * To avoid races with d_move while rename is happening, d_lock is used. *
@@ -1643,7 +1573,7 @@ EXPORT_SYMBOL(d_add_ci);* * The dentry unused LRU is not updated even if lookup finds the required dentry * in there. It is updated in places such as prune_dcache, shrink_dcache_sb, -* select_parent and __dget_locked. This laziness saves lookup from dcache_lock +* select_parent and __dget_locked. This laziness saves lookup from LRU lock * acquisition. * * d_lookup() is protected against the concurrent renames in some unrelated
@@ -1774,25 +1704,22 @@ int d_validate(struct dentry *dentry, stif (dentry->d_parent != dparent) goto out; -spin_lock (&dcache_lock);spin_lock (&dentry->d_lock);spin_lock (&dcache_hash_lock); base =d_hash (dparent, dentry->d_name.hash);hlist_for_each (lhp,base) {/* hlist_for_each_entry_rcu() not required for d_hash list -* as it is parsed under dcache_lock +* as it is parsed under dcache_hash_lock */ if (dentry == hlist_entry(lhp, struct dentry, d_hash)) {spin_unlock (&dcache_hash_lock);__dget_locked_dlock (dentry);spin_unlock (&dentry->d_lock); -spin_unlock (&dcache_lock);return 1 ; } }spin_unlock (&dcache_hash_lock);spin_unlock (&dentry->d_lock); -spin_unlock (&dcache_lock); out:return 0 ; }
@@ -1825,7 +1752,6 @@ void d_delete(struct dentry * dentry)/* * Are we the only user? */ -spin_lock (&dcache_lock);spin_lock (&dcache_inode_lock);spin_lock (&dentry->d_lock); isdir =S_ISDIR (dentry->d_inode->i_mode);
@@ -1841,7 +1767,6 @@ void d_delete(struct dentry * dentry)spin_unlock (&dentry->d_lock);spin_unlock (&dcache_inode_lock); -spin_unlock (&dcache_lock);fsnotify_nameremove (dentry, isdir); }
@@ -1868,13 +1793,11 @@ static void _d_rehash(struct dentry * envoid d_rehash (struct dentry * entry) { -spin_lock (&dcache_lock);spin_lock (&entry->d_lock);spin_lock (&dcache_hash_lock);_d_rehash (entry);spin_unlock (&dcache_hash_lock);spin_unlock (&entry->d_lock); -spin_unlock (&dcache_lock); }EXPORT_SYMBOL (d_rehash);
@@ -2032,9 +1955,7 @@ static void d_move_locked(struct dentryvoid d_move (struct dentry * dentry,struct dentry * target) { -spin_lock (&dcache_lock);d_move_locked (dentry, target); -spin_unlock (&dcache_lock); }EXPORT_SYMBOL (d_move);
@@ -2061,13 +1982,12 @@ struct dentry *d_ancestor(struct dentry* This helper attempts to cope with remotely renamed directories * * It assumes that the caller is already holding -* dentry->d_parent->d_inode->i_mutex and the dcache_lock +* dentry->d_parent->d_inode->i_mutex * * Note: If ever the locking in lock_rename() changes, then please * remember to update this too... */ static struct dentry *__d_unalias (struct dentry *dentry,struct dentry *alias) -__releases (dcache_lock) {struct mutex *m1 =NULL , *m2 =NULL ;struct dentry *ret;
@@ -2094,7 +2014,6 @@ out_unalias: ret = alias; out_err:spin_unlock (&dcache_inode_lock); -spin_unlock (&dcache_lock);if (m2)mutex_unlock (m2);if (m1)
@@ -2158,7 +2077,6 @@ struct dentry *d_materialise_unique(struBUG_ON (!d_unhashed (dentry)); -spin_lock (&dcache_lock);spin_lock (&dcache_inode_lock);if (!inode) {
@@ -2204,7 +2122,6 @@ found:spin_unlock (&dcache_hash_lock);spin_unlock (&actual->d_lock);spin_unlock (&dcache_inode_lock); -spin_unlock (&dcache_lock); out_nolock:if (actual == dentry) {security_d_instantiate (dentry, inode);
@@ -2216,7 +2133,6 @@ out_nolock: shouldnt_be_hashed:spin_unlock (&dcache_inode_lock); -spin_unlock (&dcache_lock);BUG (); }EXPORT_SYMBOL_GPL (d_materialise_unique);
@@ -2249,8 +2165,7 @@ static int prepend_name(char **buffer, i* Returns a pointer into the buffer or an error code if the * path was too long. * -* "buflen" should be positive. Caller holds the dcache_lock and -* path->dentry->d_lock. +* "buflen" should be positive. Caller holds the path->dentry->d_lock. * * If path is not reachable from the supplied root, then the value of * root is changed (without modifying refcounts).
@@ -2371,12 +2286,10 @@ char *d_path(const struct path *path, chpath_get (&root);spin_unlock (¤t->fs->lock); -spin_lock (&dcache_lock);br_read_lock (vfsmount_lock); tmp = root; res =__d_path (path, &tmp, buf, buflen);br_read_unlock (vfsmount_lock); -spin_unlock (&dcache_lock);path_put (&root);return res;
@@ -2418,7 +2331,6 @@ rename_retry:prepend (&end, &buflen,"\0" ,1 ); seq =read_seqbegin (&rename_lock); -spin_lock (&dcache_lock);br_read_lock (vfsmount_lock);rcu_read_lock ();/* protect parent */ spin_lock (&dentry->d_lock);
@@ -2451,7 +2363,6 @@ out:spin_unlock (&dentry->d_lock);rcu_read_unlock ();br_read_unlock (vfsmount_lock); -spin_unlock (&dcache_lock);if (read_seqretry (&rename_lock, seq)) goto rename_retry;return retval;
@@ -2495,7 +2406,6 @@ SYSCALL_DEFINE2(getcwd, char __user *, bspin_unlock (¤t->fs->lock); error = -ENOENT ; -spin_lock (&dcache_lock);br_read_lock (vfsmount_lock);spin_lock (&pwd.dentry->d_lock);if (!d_unlinked (pwd.dentry)) {
@@ -2507,7 +2417,6 @@ SYSCALL_DEFINE2(getcwd, char __user *, b/* XXX: race here, have to close (eg. return unlinked from __d_path) */ cwd =__d_path (&pwd, &tmp, page,PAGE_SIZE );br_read_unlock (vfsmount_lock); -spin_unlock (&dcache_lock); error =PTR_ERR (cwd);if (IS_ERR (cwd))
@@ -2523,7 +2432,6 @@ SYSCALL_DEFINE2(getcwd, char __user *, b }else {spin_unlock (&pwd.dentry->d_lock);br_read_unlock (vfsmount_lock); -spin_unlock (&dcache_lock); } out:
@@ -2609,7 +2517,6 @@ void d_genocide(struct dentry *root) rename_retry: this_parent = root; seq =read_seqbegin (&rename_lock); -spin_lock (&dcache_lock);spin_lock (&this_parent->d_lock); repeat: next = this_parent->d_subdirs.next;
@@ -2657,7 +2564,6 @@ resume:// d_unlinked(this_parent) || XXX read_seqretry(&rename_lock, seq)) {spin_unlock (&this_parent->d_lock); -spin_unlock (&dcache_lock);rcu_read_unlock (); goto rename_retry; }
@@ -2666,7 +2572,6 @@ resume: goto resume; }spin_unlock (&this_parent->d_lock); -spin_unlock (&dcache_lock);if (read_seqretry (&rename_lock, seq)) goto rename_retry; }
fs/namei.c +2 -7
@@ -618,8 +618,8 @@ int follow_up(struct path *path)return 1 ; } -/* no need for dcache_lock, as serialization is taken care in -* namespace.c +/* +* serialization is taken care of in namespace.c */ static int __follow_mount (struct path *path) {
@@ -651,9 +651,6 @@ static void follow_mount(struct path *pa } } -/* no need for dcache_lock, as serialization is taken care in -* namespace.c -*/ int follow_down (struct path *path) {struct vfsmount *mounted;
@@ -2152,12 +2149,10 @@ void dentry_unhash(struct dentry *dentry {dget (dentry);shrink_dcache_parent (dentry); -spin_lock (&dcache_lock);spin_lock (&dentry->d_lock);if (dentry->d_count ==2 )__d_drop (dentry);spin_unlock (&dentry->d_lock); -spin_unlock (&dcache_lock); }int vfs_rmdir (struct inode *dir,struct dentry *dentry)
fs/seq_file.c +0 -2
@@ -465,11 +465,9 @@ int seq_path_root(struct seq_file *m, stif (size) {char *p; -spin_lock (&dcache_lock);br_read_lock (vfsmount_lock); p =__d_path (path, root, buf, size);br_read_unlock (vfsmount_lock); -spin_unlock (&dcache_lock); res =PTR_ERR (p);if (!IS_ERR (p)) {
include/linux/dcache.h +7 -10
@@ -150,13 +150,13 @@ struct dentry_operations {/* locking rules: -big lock dcache_lock d_lock may block -d_revalidate: no no no yes -d_hash no no no yes -d_compare: no yes yes no -d_delete: no yes no no -d_release: no no no yes -d_iput: no no no yes +big lock d_lock may block +d_revalidate: no no yes +d_hash no no yes +d_compare: no yes no +d_delete: no no no +d_release: no no yes +d_iput: no no yes */ /* d_flags entries */
@@ -191,7 +191,6 @@ d_iput: no no no yesextern spinlock_t dcache_inode_lock;extern spinlock_t dcache_hash_lock; -extern spinlock_t dcache_lock;extern seqlock_t rename_lock;/**
@@ -222,11 +221,9 @@ static inline void __d_drop(struct dentrstatic inline void d_drop (struct dentry *dentry) { -spin_lock (&dcache_lock);spin_lock (&dentry->d_lock);__d_drop (dentry);spin_unlock (&dentry->d_lock); -spin_unlock (&dcache_lock); }static inline int dname_external (struct dentry *dentry)
fs/notify/inotify/inotify.c +2 -2
@@ -181,7 +181,6 @@ static void set_dentry_child_flags(struc {struct dentry *alias; -spin_lock (&dcache_lock);spin_lock (&dcache_inode_lock);list_for_each_entry (alias, &inode->i_dentry, d_alias) {struct dentry *child;
@@ -201,7 +200,6 @@ static void set_dentry_child_flags(strucspin_unlock (&alias->d_lock); }spin_unlock (&dcache_inode_lock); -spin_unlock (&dcache_lock); }/*
@@ -269,6 +267,7 @@ void inotify_d_instantiate(struct dentryif (!inode)return ; +/* XXX: need parent lock in place of dcache_lock? */ spin_lock (&entry->d_lock); parent = entry->d_parent;if (parent->d_inode &&inotify_inode_watched (parent->d_inode))
@@ -283,6 +282,7 @@ void inotify_d_move(struct dentry *entry {struct dentry *parent; +/* XXX: need parent lock in place of dcache_lock? */ parent = entry->d_parent;if (inotify_inode_watched (parent->d_inode)) entry->d_flags |=DCACHE_INOTIFY_PARENT_WATCHED ;
fs/exportfs/expfs.c +0 -4
@@ -47,24 +47,20 @@ find_acceptable_alias(struct dentry *resif (acceptable (context, result))return result; -spin_lock (&dcache_lock);spin_lock (&dcache_inode_lock);list_for_each_entry (dentry, &result->d_inode->i_dentry, d_alias) {dget_locked (dentry);spin_unlock (&dcache_inode_lock); -spin_unlock (&dcache_lock);if (toput)dput (toput);if (dentry != result &&acceptable (context, dentry)) {dput (result);return dentry; } -spin_lock (&dcache_lock);spin_lock (&dcache_inode_lock); toput = dentry; }spin_unlock (&dcache_inode_lock); -spin_unlock (&dcache_lock);if (toput)dput (toput);
Documentation/filesystems/Locking +1 -1
@@ -17,7 +17,7 @@ prototypes: void (*d_iput)(struct dentry *, struct inode *); char *(*d_dname)((struct dentry *dentry, char *buffer, int buflen); -locking rules: +locking rules: XXX: update these!! none have BKL dcache_lock rename_lock ->d_lock may block d_revalidate: no no no yes
arch/powerpc/platforms/cell/spufs/inode.c +1 -4
@@ -158,21 +158,18 @@ static void spufs_prune_dir(struct dentrmutex_lock (&dir->d_inode->i_mutex);list_for_each_entry_safe (dentry, tmp, &dir->d_subdirs, d_u.d_child) { -spin_lock (&dcache_lock);spin_lock (&dentry->d_lock);if (!(d_unhashed (dentry)) && dentry->d_inode) {dget_locked_dlock (dentry);__d_drop (dentry);spin_unlock (&dentry->d_lock);simple_unlink (dir->d_inode, dentry); -/* XXX: what is dcache_lock protecting here? Other +/* XXX: what was dcache_lock protecting here? Other * filesystems (IB, configfs) release dcache_lock * before unlink */ -spin_unlock (&dcache_lock);dput (dentry); }else {spin_unlock (&dentry->d_lock); -spin_unlock (&dcache_lock); } }shrink_dcache_parent (dir);
drivers/infiniband/hw/ipath/ipath_fs.c +1 -5
@@ -273,18 +273,14 @@ static int remove_file(struct dentry *pa goto bail; } -spin_lock (&dcache_lock);spin_lock (&tmp->d_lock);if (!(d_unhashed (tmp) && tmp->d_inode)) {dget_locked_dlock (tmp);__d_drop (tmp);spin_unlock (&tmp->d_lock); -spin_unlock (&dcache_lock);simple_unlink (parent->d_inode, tmp); - }else { + }else spin_unlock (&tmp->d_lock); -spin_unlock (&dcache_lock); - } ret =0 ; bail:
drivers/usb/core/inode.c +0 -3
@@ -347,7 +347,6 @@ static int usbfs_empty (struct dentry *d {struct list_head *list; -spin_lock (&dcache_lock);spin_lock (&dentry->d_lock);list_for_each (list, &dentry->d_subdirs) { struct dentry *de = list_entry(list, struct dentry, d_u.d_child);
@@ -356,13 +355,11 @@ static int usbfs_empty (struct dentry *dif (usbfs_positive (de)) {spin_unlock (&de->d_lock);spin_unlock (&dentry->d_lock); -spin_unlock (&dcache_lock);return 0 ; }spin_unlock (&de->d_lock); }spin_unlock (&dentry->d_lock); -spin_unlock (&dcache_lock);return 1 ; }
fs/affs/amigaffs.c +0 -2
@@ -128,7 +128,6 @@ affs_fix_dcache(struct dentry *dentry, uvoid *data = dentry->d_fsdata;struct list_head *head, *next; -spin_lock (&dcache_lock);spin_lock (&dcache_inode_lock); head = &inode->i_dentry; next = head->next;
@@ -141,7 +140,6 @@ affs_fix_dcache(struct dentry *dentry, u next = next->next; }spin_unlock (&dcache_inode_lock); -spin_unlock (&dcache_lock); }
fs/coda/cache.c +0 -2
@@ -86,7 +86,6 @@ static void coda_flag_children(struct destruct list_head *child;struct dentry *de; -spin_lock (&dcache_lock);spin_lock (&parent->d_lock);list_for_each (child, &parent->d_subdirs) {
@@ -97,7 +96,6 @@ static void coda_flag_children(struct decoda_flag_inode (de->d_inode, flag); }spin_unlock (&parent->d_lock); -spin_unlock (&dcache_lock);return ; }
fs/configfs/configfs_internal.h +0 -2
@@ -120,7 +120,6 @@ static inline struct config_item *config {struct config_item * item =NULL ; -spin_lock (&dcache_lock);spin_lock (&dentry->d_lock);if (!d_unhashed (dentry)) {struct configfs_dirent * sd = dentry->d_fsdata;
@@ -131,7 +130,6 @@ static inline struct config_item *config item =config_item_get (sd->s_element); }spin_unlock (&dentry->d_lock); -spin_unlock (&dcache_lock);return item; }
fs/configfs/inode.c +1 -5
@@ -249,18 +249,14 @@ void configfs_drop_dentry(struct configfstruct dentry * dentry = sd->s_dentry;if (dentry) { -spin_lock (&dcache_lock);spin_lock (&dentry->d_lock);if (!(d_unhashed (dentry) && dentry->d_inode)) {dget_locked_dlock (dentry);__d_drop (dentry);spin_unlock (&dentry->d_lock); -spin_unlock (&dcache_lock);simple_unlink (parent->d_inode, dentry); - }else { + }else spin_unlock (&dentry->d_lock); -spin_unlock (&dcache_lock); - } } }
fs/ncpfs/dir.c +0 -3
@@ -364,7 +364,6 @@ ncp_dget_fpos(struct dentry *dentry, str }/* If a pointer is invalid, we search the dentry. */ -spin_lock (&dcache_lock);spin_lock (&parent->d_lock); next = parent->d_subdirs.next;while (next != &parent->d_subdirs) {
@@ -375,13 +374,11 @@ ncp_dget_fpos(struct dentry *dentry, strelse dent =NULL ;spin_unlock (&parent->d_lock); -spin_unlock (&dcache_lock); goto out; } next = next->next; }spin_unlock (&parent->d_lock); -spin_unlock (&dcache_lock);return NULL ; out:
fs/ncpfs/ncplib_kernel.h +0 -4
@@ -192,7 +192,6 @@ ncp_renew_dentries(struct dentry *parentstruct list_head *next;struct dentry *dentry; -spin_lock (&dcache_lock);spin_lock (&parent->d_lock); next = parent->d_subdirs.next;while (next != &parent->d_subdirs) {
@@ -206,7 +205,6 @@ ncp_renew_dentries(struct dentry *parent next = next->next; }spin_unlock (&parent->d_lock); -spin_unlock (&dcache_lock); } static inline void
@@ -216,7 +214,6 @@ ncp_invalidate_dircache_entries(struct dstruct list_head *next;struct dentry *dentry; -spin_lock (&dcache_lock);spin_lock (&parent->d_lock); next = parent->d_subdirs.next;while (next != &parent->d_subdirs) {
@@ -226,7 +223,6 @@ ncp_invalidate_dircache_entries(struct d next = next->next; }spin_unlock (&parent->d_lock); -spin_unlock (&dcache_lock); }struct ncp_cache_head {
fs/nfs/dir.c +0 -3
@@ -1464,11 +1464,9 @@ static int nfs_unlink(struct inode *dir,dfprintk (VFS ,"NFS: unlink(%s/%ld, %s)\n" , dir->i_sb->s_id, dir->i_ino, dentry->d_name.name); -spin_lock (&dcache_lock);spin_lock (&dentry->d_lock);if (dentry->d_count >1 ) {spin_unlock (&dentry->d_lock); -spin_unlock (&dcache_lock);/* Start asynchronous writeout of the inode */ write_inode_now (dentry->d_inode,0 ); error =nfs_sillyrename (dir, dentry);
@@ -1479,7 +1477,6 @@ static int nfs_unlink(struct inode *dir, need_rehash =1 ; }spin_unlock (&dentry->d_lock); -spin_unlock (&dcache_lock); error =nfs_safe_remove (dentry);if (!error || error == -ENOENT ) {nfs_set_verifier (dentry,nfs_save_change_attribute (dir));
fs/ocfs2/dcache.c +0 -2
@@ -151,7 +151,6 @@ struct dentry *ocfs2_find_local_alias(ststruct list_head *p;struct dentry *dentry =NULL ; -spin_lock (&dcache_lock);spin_lock (&dcache_inode_lock);list_for_each (p, &inode->i_dentry) { dentry = list_entry(p, struct dentry, d_alias);
@@ -171,7 +170,6 @@ struct dentry *ocfs2_find_local_alias(st }spin_unlock (&dcache_inode_lock); -spin_unlock (&dcache_lock);return dentry; }
fs/smbfs/cache.c +0 -4
@@ -62,7 +62,6 @@ smb_invalidate_dircache_entries(struct dstruct list_head *next;struct dentry *dentry; -spin_lock (&dcache_lock);spin_lock (&parent->d_lock); next = parent->d_subdirs.next;while (next != &parent->d_subdirs) {
@@ -72,7 +71,6 @@ smb_invalidate_dircache_entries(struct d next = next->next; }spin_unlock (&parent->d_lock); -spin_unlock (&dcache_lock); }/*
@@ -98,7 +96,6 @@ smb_dget_fpos(struct dentry *dentry, str }/* If a pointer is invalid, we search the dentry. */ -spin_lock (&dcache_lock);spin_lock (&parent->d_lock); next = parent->d_subdirs.next;while (next != &parent->d_subdirs) {
@@ -115,7 +112,6 @@ smb_dget_fpos(struct dentry *dentry, str dent =NULL ; out_unlock:spin_unlock (&parent->d_lock); -spin_unlock (&dcache_lock);return dent; }
kernel/cgroup.c +0 -6
@@ -869,7 +869,6 @@ static void cgroup_clear_directory(strucstruct list_head *node;BUG_ON (!mutex_is_locked (&dentry->d_inode->i_mutex)); -spin_lock (&dcache_lock);spin_lock (&dentry->d_lock); node = dentry->d_subdirs.next;while (node != &dentry->d_subdirs) {
@@ -884,18 +883,15 @@ static void cgroup_clear_directory(strucdget_locked_dlock (d);spin_unlock (&d->d_lock);spin_unlock (&dentry->d_lock); -spin_unlock (&dcache_lock);d_delete (d);simple_unlink (dentry->d_inode, d);dput (d); -spin_lock (&dcache_lock);spin_lock (&dentry->d_lock); }else spin_unlock (&d->d_lock); node = dentry->d_subdirs.next; }spin_unlock (&dentry->d_lock); -spin_unlock (&dcache_lock); }/*
@@ -907,14 +903,12 @@ static void cgroup_d_remove_dir(struct dcgroup_clear_directory (dentry); -spin_lock (&dcache_lock); parent = dentry->d_parent;spin_lock (&parent->d_lock);spin_lock (&dentry->d_lock);list_del_init (&dentry->d_u.d_child);spin_unlock (&dentry->d_lock);spin_unlock (&parent->d_lock); -spin_unlock (&dcache_lock);remove_dir (dentry); }
security/selinux/selinuxfs.c +0 -4
@@ -941,7 +941,6 @@ static void sel_remove_entries(struct de {struct list_head *node; -spin_lock (&dcache_lock);spin_lock (&de->d_lock); node = de->d_subdirs.next;while (node != &de->d_subdirs) {
@@ -954,11 +953,9 @@ static void sel_remove_entries(struct dedget_locked_dlock (d);spin_unlock (&de->d_lock);spin_unlock (&d->d_lock); -spin_unlock (&dcache_lock);d_delete (d);simple_unlink (de->d_inode, d);dput (d); -spin_lock (&dcache_lock);spin_lock (&de->d_lock); }else spin_unlock (&d->d_lock);
@@ -966,7 +963,6 @@ static void sel_remove_entries(struct de }spin_unlock (&de->d_lock); -spin_unlock (&dcache_lock); }#define BOOL_DIR_NAME "booleans"
security/tomoyo/realpath.c +0 -2
@@ -92,12 +92,10 @@ int tomoyo_realpath_from_path2(struct pa }else {struct path ns_root = {.mnt =NULL , .dentry =NULL }; -spin_lock (&dcache_lock);br_read_lock (vfsmount_lock);/* go to whatever namespace root we are under */ sp =__d_path (path, &ns_root, newname, newname_len);br_read_unlock (vfsmount_lock); -spin_unlock (&dcache_lock);/* Prepend "/proc" prefix if using internal proc vfs mount. */ if (!IS_ERR (sp) && (path->mnt->mnt_flags &MNT_INTERNAL ) && (path->mnt->mnt_sb->s_magic ==PROC_SUPER_MAGIC )) {
fs/nfs/getroot.c +0 -2
@@ -64,13 +64,11 @@ static int nfs_superblock_set_dummy_root* This again causes shrink_dcache_for_umount_subtree() to * Oops, since the test for IS_ROOT() will fail. */ -spin_lock (&dcache_lock);spin_lock (&dcache_inode_lock);spin_lock (&sb->s_root->d_lock);list_del_init (&sb->s_root->d_alias);spin_unlock (&sb->s_root->d_lock);spin_unlock (&dcache_inode_lock); -spin_unlock (&dcache_lock); }return 0 ; }
drivers/staging/pohmelfs/path_entry.c +0 -2
@@ -101,7 +101,6 @@ rename_retry: d = first; seq =read_seqbegin (&rename_lock);rcu_read_lock (); -spin_lock (&dcache_lock);if (!IS_ROOT (d) &&d_unhashed (d)) len +=UNHASHED_OBSCURE_STRING_SIZE ;/* Obscure " (deleted)" string */
@@ -110,7 +109,6 @@ rename_retry: len += d->d_name.len +1 ;/* Plus slash */ d = d->d_parent; } -spin_unlock (&dcache_lock);rcu_read_unlock ();if (read_seqretry (&rename_lock, seq)) goto rename_retry;
fs/nfs/namespace.c +0 -3
@@ -60,7 +60,6 @@ rename_retry: seq =read_seqbegin (&rename_lock);rcu_read_lock (); -spin_lock (&dcache_lock);while (!IS_ROOT (dentry) && dentry != droot) { namelen = dentry->d_name.len; buflen -= namelen +1 ;
@@ -71,7 +70,6 @@ rename_retry: *--end ='/' ; dentry = dentry->d_parent; } -spin_unlock (&dcache_lock);rcu_read_unlock ();if (read_seqretry (&rename_lock, seq)) goto rename_retry;
@@ -91,7 +89,6 @@ rename_retry:memcpy (end, base, namelen);return end; Elong_unlock: -spin_unlock (&dcache_lock);rcu_read_unlock ();if (read_seqretry (&rename_lock, seq)) goto rename_retry;
include/linux/fsnotify_backend.h +1 -4
@@ -276,10 +276,10 @@ static inline void __fsnotify_update_dca {struct dentry *parent; -assert_spin_locked (&dcache_lock);assert_spin_locked (&dentry->d_lock); parent = dentry->d_parent; +/* XXX: after dcache_lock removal, there is a race with parent->d_inode and fsnotify_inode_watches_children. must fix */ if (parent->d_inode &&fsnotify_inode_watches_children (parent->d_inode)) dentry->d_flags |=DCACHE_FSNOTIFY_PARENT_WATCHED ;else
@@ -288,15 +288,12 @@ static inline void __fsnotify_update_dca/* * fsnotify_d_instantiate - instantiate a dentry for inode -* Called with dcache_lock held. */ static inline void __fsnotify_d_instantiate (struct dentry *dentry,struct inode *inode) {if (!inode)return ; -assert_spin_locked (&dcache_lock); -spin_lock (&dentry->d_lock);__fsnotify_update_dcache_flags (dentry);spin_unlock (&dentry->d_lock);
fs/notify/fsnotify.c +0 -2
@@ -53,7 +53,6 @@ void __fsnotify_update_child_dentry_flag/* determine if the children should tell inode about their events */ watched =fsnotify_inode_watches_children (inode); -spin_lock (&dcache_lock);spin_lock (&dcache_inode_lock);/* run all of the dentries associated with this inode. Since this is a * directory, there damn well better only be one item on this list */
@@ -78,7 +77,6 @@ void __fsnotify_update_child_dentry_flagspin_unlock (&alias->d_lock); }spin_unlock (&dcache_inode_lock); -spin_unlock (&dcache_lock); }/* Notify this dentry's parent about a child's events. */
include/linux/fs.h +4 -0
@@ -2425,6 +2425,10 @@ static inline ino_t parent_ino(struct de {ino_t res; +/* +* Don't strictly need d_lock here? If the parent ino could change +* then surely we'd have a deeper race in the caller? +*/ spin_lock (&dentry->d_lock); res = dentry->d_parent->d_inode->i_ino;spin_unlock (&dentry->d_lock);
fs/autofs4/autofs_i.h +3 -0
@@ -16,6 +16,7 @@#include <linux/auto_fs4.h> #include <linux/auto_dev-ioctl.h> #include <linux/mutex.h> +#include <linux/spinlock.h> #include <linux/list.h> /* This is the range of ioctl() numbers we claim as ours */
@@ -60,6 +61,8 @@ do { \ current->pid, __func__, ##args); \ } while (0) +extern spinlock_t autofs4_lock; +/* Unified info structure. This is pointed to by both the dentry and inode structures. Each file in the filesystem has an instance of this structure. It holds a reference to the dentry, so dentries are never
fs/autofs4/expire.c +12 -12
@@ -94,9 +94,9 @@ done:* Calculate next entry in top down tree traversal. * From next_mnt in namespace.c - elegant. * -* How is this supposed to work if we drop dcache_lock between calls anyway? +* How is this supposed to work if we drop autofs4_lock between calls anyway? * How does it cope with renames? -* And also callers dput the returned dentry before taking dcache_lock again +* And also callers dput the returned dentry before taking autofs4_lock again * so what prevents it from being freed?? */ static struct dentry *get_next_positive_dentry(struct dentry *p,
@@ -105,7 +105,7 @@ static struct dentry *get_next_positive_struct list_head *next;struct dentry *ret; -spin_lock (&dcache_lock); +spin_lock (&autofs4_lock); again:spin_lock (&p->d_lock); next = p->d_subdirs.next;
@@ -115,7 +115,7 @@ again:if (p == root) {spin_unlock (&p->d_lock); -spin_unlock (&dcache_lock); +spin_unlock (&autofs4_lock);return NULL ; }
@@ -143,7 +143,7 @@ again:dget_dlock (ret);spin_unlock (&ret->d_lock);spin_unlock (&p->d_lock); -spin_unlock (&dcache_lock); +spin_unlock (&autofs4_lock);return ret; }
@@ -313,7 +313,7 @@ struct dentry *autofs4_expire_direct(str* A tree is eligible if :- * - it is unused by any user process * - it has been unused for exp_timeout time -* This seems to be racy dropping dcache_lock and asking for next->next after +* This seems to be racy dropping autofs4_lock and asking for next->next after * the lock has been dropped. */ struct dentry *autofs4_expire_indirect(struct super_block *sb,
@@ -336,7 +336,7 @@ struct dentry *autofs4_expire_indirect(s now = jiffies; timeout = sbi->exp_timeout; -spin_lock (&dcache_lock); +spin_lock (&autofs4_lock);spin_lock (&root->d_lock); next = root->d_subdirs.next;
@@ -355,7 +355,7 @@ struct dentry *autofs4_expire_indirect(s dentry =dget_dlock (dentry);spin_unlock (&dentry->d_lock);spin_unlock (&root->d_lock); -spin_unlock (&dcache_lock); +spin_unlock (&autofs4_lock);spin_lock (&sbi->fs_lock); ino =autofs4_dentry_ino (dentry);
@@ -420,12 +420,12 @@ struct dentry *autofs4_expire_indirect(s next:spin_unlock (&sbi->fs_lock);dput (dentry); -spin_lock (&dcache_lock); +spin_lock (&autofs4_lock);spin_lock (&root->d_lock); next = next->next; }spin_unlock (&root->d_lock); -spin_unlock (&dcache_lock); +spin_unlock (&autofs4_lock);return NULL ; found:
@@ -435,13 +435,13 @@ found: ino->flags |=AUTOFS_INF_EXPIRING ;init_completion (&ino->expire_complete);spin_unlock (&sbi->fs_lock); -spin_lock (&dcache_lock); +spin_lock (&autofs4_lock);spin_lock (&expired->d_parent->d_lock);spin_lock_nested (&expired->d_lock,DENTRY_D_LOCK_NESTED );list_move (&expired->d_parent->d_subdirs, &expired->d_u.d_child);spin_unlock (&expired->d_lock);spin_unlock (&expired->d_parent->d_lock); -spin_unlock (&dcache_lock); +spin_unlock (&autofs4_lock);return expired; }
fs/autofs4/root.c +7 -7
@@ -134,15 +134,15 @@ static int autofs4_dir_open(struct inode* autofs file system so just let the libfs routines handle * it. */ -spin_lock (&dcache_lock); +spin_lock (&autofs4_lock);spin_lock (&dentry->d_lock);if (!d_mountpoint (dentry) &&list_empty (&dentry->d_subdirs)) {spin_unlock (&dentry->d_lock); -spin_unlock (&dcache_lock); +spin_unlock (&autofs4_lock);return -ENOENT ; }spin_unlock (&dentry->d_lock); -spin_unlock (&dcache_lock); +spin_unlock (&autofs4_lock); out:return dcache_dir_open (inode, file);
@@ -248,11 +248,11 @@ static void *autofs4_follow_link(struct/* We trigger a mount for almost all flags */ lookup_type =autofs4_need_mount (nd->flags);spin_lock (&sbi->fs_lock); -spin_lock (&dcache_lock); +spin_lock (&autofs4_lock);spin_lock (&dentry->d_lock);if (!(lookup_type || ino->flags &AUTOFS_INF_PENDING )) {spin_unlock (&dentry->d_lock); -spin_unlock (&dcache_lock); +spin_unlock (&autofs4_lock);spin_unlock (&sbi->fs_lock); goto follow; }
@@ -274,7 +274,7 @@ static void *autofs4_follow_link(struct goto follow; }spin_unlock (&dentry->d_lock); -spin_unlock (&dcache_lock); +spin_unlock (&autofs4_lock);spin_unlock (&sbi->fs_lock); follow:/*
@@ -476,7 +476,7 @@ static struct dentry *autofs4_lookup_expconst unsigned char *str = name->name;struct list_head *p, *head; -spin_lock (&dcache_lock); +spin_lock (&autofs4_lock);spin_lock (&sbi->lookup_lock); head = &sbi->expiring_list;list_for_each (p, head) {
fs/autofs4/waitq.c +4 -3
@@ -194,14 +194,15 @@ static int autofs4_getpath(struct autofs rename_retry: buf = *name; len =0 ; + seq =read_seqbegin (&rename_lock);rcu_read_lock (); -spin_lock (&dcache_lock); +spin_lock (&autofs4_lock);for (tmp = dentry ; tmp != root ; tmp = tmp->d_parent) len += tmp->d_name.len +1 ;if (!len || --len >NAME_MAX ) { -spin_unlock (&dcache_lock); +spin_unlock (&autofs4_lock);rcu_read_unlock ();if (read_seqretry (&rename_lock, seq)) goto rename_retry;
@@ -217,7 +218,7 @@ rename_retry: p -= tmp->d_name.len;strncpy (p, tmp->d_name.name, tmp->d_name.len); } -spin_unlock (&dcache_lock); +spin_unlock (&autofs4_lock);rcu_read_unlock ();if (read_seqretry (&rename_lock, seq)) goto rename_retry;
drivers/infiniband/hw/qib/qib_fs.c +0 -3
@@ -451,17 +451,14 @@ static int remove_file(struct dentry *pa goto bail; } -spin_lock (&dcache_lock);spin_lock (&tmp->d_lock);if (!(d_unhashed (tmp) && tmp->d_inode)) {dget_locked_dlock (tmp);__d_drop (tmp);spin_unlock (&tmp->d_lock); -spin_unlock (&dcache_lock);simple_unlink (parent->d_inode, tmp); }else {spin_unlock (&tmp->d_lock); -spin_unlock (&dcache_lock); } ret =0 ;
fs/ceph/dir.c +1 -5
@@ -111,7 +111,6 @@ static int __dcache_readdir(struct filedout ("__dcache_readdir %p at %llu (last %p)\n" , dir, filp->f_pos, last); -spin_lock (&dcache_lock);spin_lock (&parent->d_lock);/* start at beginning? */
@@ -155,7 +154,6 @@ more:dget_dlock (dentry);spin_unlock (&dentry->d_lock);spin_unlock (&parent->d_lock); -spin_unlock (&dcache_lock);spin_unlock (&inode->i_lock); dout(" %llu (%llu) dentry %p %.*s %p\n", di->offset, filp->f_pos,
@@ -178,7 +176,6 @@ more: }spin_lock (&inode->i_lock); -spin_lock (&dcache_lock);spin_lock (&parent->d_lock); last = dentry;
@@ -189,7 +186,7 @@ more: p = p->prev; filp->f_pos++; -/* make sure a dentry wasn't dropped while we didn't have dcache_lock */ +/* make sure a dentry wasn't dropped while we didn't have parent->d_lock */ if ((ceph_inode (dir)->i_ceph_flags &CEPH_I_COMPLETE )) goto more;dout (" lost I_COMPLETE on %p; falling back to mds\n" , dir);
@@ -197,7 +194,6 @@ more: out_unlock:spin_unlock (&parent->d_lock); -spin_unlock (&dcache_lock);if (last) {spin_unlock (&inode->i_lock);
fs/ceph/inode.c +0 -4
@@ -825,7 +825,6 @@ static void ceph_set_dentry_offset(struc di->offset =ceph_inode (inode)->i_max_offset++;spin_unlock (&inode->i_lock); -spin_lock (&dcache_lock);spin_lock (&dir->d_lock);spin_lock_nested (&dn->d_lock,DENTRY_D_LOCK_NESTED );list_move (&dn->d_u.d_child, &dir->d_subdirs);
@@ -833,7 +832,6 @@ static void ceph_set_dentry_offset(struc dn->d_u.d_child.prev, dn->d_u.d_child.next);spin_unlock (&dn->d_lock);spin_unlock (&dir->d_lock); -spin_unlock (&dcache_lock); }/*
@@ -1213,13 +1211,11 @@ retry_lookup: goto retry_lookup; }else {/* reorder parent's d_subdirs */ -spin_lock (&dcache_lock);spin_lock (&parent->d_lock);spin_lock_nested (&dn->d_lock,DENTRY_D_LOCK_NESTED );list_move (&dn->d_u.d_child, &parent->d_subdirs);spin_unlock (&dn->d_lock);spin_unlock (&parent->d_lock); -spin_unlock (&dcache_lock); } di = dn->d_fsdata;
PATCH 18/52 fs: dcache reduce dput locking
In reply to [email protected]It is possible to run dput without taking locks up-front. In many cases where we don't kill the dentry anyway, these locks are not required.
(I think... need to think about it more). Further changes ->d_delete locking which is not all audited. d_delete must be idempotent, for one.
--- fs/dcache.c | 60 ++++++++++++++++++++++++++++++++---------------------------- 1 file changed, 32 insertions(+), 28 deletions(-)
fs/dcache.c +32 -28
@@ -278,7 +278,8 @@ static struct dentry *d_kill(struct dentvoid dput (struct dentry *dentry) { -struct dentry *parent =NULL ; +struct dentry *parent; +if (!dentry)return ;
@@ -286,25 +287,10 @@ repeat:if (dentry->d_count ==1 )might_sleep ();spin_lock (&dentry->d_lock); -if (dentry->d_count ==1 ) { -if (!spin_trylock (&dcache_inode_lock)) { -drop2: -spin_unlock (&dentry->d_lock); - goto repeat; - } - parent = dentry->d_parent; -if (parent && parent != dentry) { -if (!spin_trylock (&parent->d_lock)) { -spin_unlock (&dcache_inode_lock); - goto drop2; - } - } - } - dentry->d_count--; -if (dentry->d_count) { +BUG_ON (!dentry->d_count); +if (dentry->d_count >1 ) { + dentry->d_count--;spin_unlock (&dentry->d_lock); -if (parent && parent != dentry) -spin_unlock (&parent->d_lock);return ; }
@@ -312,8 +298,10 @@ drop2:* AV: ->d_delete() is _NOT_ allowed to block now. */ if (dentry->d_op && dentry->d_op->d_delete) { -if (dentry->d_op->d_delete (dentry)) - goto unhash_it; +if (dentry->d_op->d_delete (dentry)) { +__d_drop (dentry); + goto kill_it; + } }/* Unreachable? Get rid of it */ if (d_unhashed (dentry))
@@ -322,15 +310,31 @@ drop2: dentry->d_flags |=DCACHE_REFERENCED ;dentry_lru_add (dentry); } -spin_unlock (&dentry->d_lock); -if (parent && parent != dentry) -spin_unlock (&parent->d_lock); -spin_unlock (&dcache_inode_lock); -return ; + dentry->d_count--; +spin_unlock (&dentry->d_lock); +return ; -unhash_it: -__d_drop (dentry); kill_it: +spin_unlock (&dentry->d_lock); +spin_lock (&dcache_inode_lock); +relock: +spin_lock (&dentry->d_lock); + parent = dentry->d_parent; +if (parent && parent != dentry) { +if (!spin_trylock (&parent->d_lock)) { +spin_unlock (&dentry->d_lock); + goto relock; + } + } + dentry->d_count--; +if (dentry->d_count) { +/* This case should be fine */ +spin_unlock (&dentry->d_lock); +if (parent && parent != dentry) +spin_unlock (&parent->d_lock); +spin_unlock (&dcache_inode_lock); +return ; + }/* if dentry was on the d_lru list delete it from there */ dentry_lru_del (dentry); dentry =d_kill (dentry);
PATCH 19/52 fs: dcache per-bucket dcache hash locking
In reply to [email protected]We can turn the dcache hash locking from a global dcache_hash_lock into per-bucket locking.
--- fs/dcache.c | 234 +++++++++++++++++++++++++++---------------------- fs/super.c | 3 include/linux/dcache.h | 23 ---- include/linux/fs.h | 3 4 files changed, 141 insertions(+), 122 deletions(-)
fs/dcache.c +134 -101
@@ -33,13 +33,15 @@#include <linux/bootmem.h> #include <linux/fs_struct.h> #include <linux/hardirq.h> +#include <linux/bit_spinlock.h> +#include <linux/rculist_bl.h> #include "internal.h" /* * Usage: * dcache_inode_lock protects: * - i_dentry, d_alias, d_inode -* dcache_hash_lock protects: +* dcache_hash_bucket lock protects: * - the dcache hash table * dcache_lru_lock protects: * - the dcache lru lists and counters
@@ -57,7 +59,7 @@* dcache_inode_lock * dentry->d_lock * dcache_lru_lock -* dcache_hash_lock +* dcache_hash_bucket lock * * If there is an ancestor relationship: * dentry->d_parent->...->d_parent->d_lock
@@ -74,13 +76,11 @@ int sysctl_vfs_cache_pressure __read_mosEXPORT_SYMBOL_GPL (sysctl_vfs_cache_pressure);__cacheline_aligned_in_smp DEFINE_SPINLOCK (dcache_inode_lock ); -__cacheline_aligned_in_smp DEFINE_SPINLOCK (dcache_hash_lock );static __cacheline_aligned_in_smp DEFINE_SPINLOCK (dcache_lru_lock );__cacheline_aligned_in_smp DEFINE_SEQLOCK (rename_lock );EXPORT_SYMBOL (rename_lock);EXPORT_SYMBOL (dcache_inode_lock); -EXPORT_SYMBOL (dcache_hash_lock);static struct kmem_cache *dentry_cache__read_mostly ;
@@ -99,7 +99,11 @@ static struct kmem_cache *dentry_cache _static unsigned int d_hash_mask__read_mostly ;static unsigned int d_hash_shift__read_mostly ; -static struct hlist_head *dentry_hashtable__read_mostly ; + +struct dcache_hash_bucket { +struct hlist_bl_head head; +}; +static struct dcache_hash_bucket *dentry_hashtable__read_mostly ;/* Statistics gathering. */ struct dentry_stat_t dentry_stat = {
@@ -107,6 +111,24 @@ struct dentry_stat_t dentry_stat = { .age_limit = 45, }; +static inline struct dcache_hash_bucket *d_hash (struct dentry *parent, +unsigned long hash) +{ + hash += ((unsigned long ) parent ^GOLDEN_RATIO_PRIME ) /L1_CACHE_BYTES ; + hash = hash ^ ((hash ^GOLDEN_RATIO_PRIME ) >>D_HASHBITS ); +return dentry_hashtable + (hash &D_HASHMASK ); +} + +static inline void spin_lock_bucket (struct dcache_hash_bucket *b) +{ +bit_spin_lock (0 , (unsigned long *)b); +} + +static inline void spin_unlock_bucket (struct dcache_hash_bucket *b) +{ +__bit_spin_unlock (0 , (unsigned long *)b); +} +static void __d_free (struct dentry *dentry) {WARN_ON (!list_empty (&dentry->d_alias));
@@ -131,7 +153,7 @@ static void d_free(struct dentry *dentryif (dentry->d_op && dentry->d_op->d_release) dentry->d_op->d_release (dentry);/* if dentry was never inserted into hash, immediate free is OK */ -if (hlist_unhashed (&dentry->d_hash)) +if (hlist_bl_unhashed (&dentry->d_hash))__d_free (dentry);else call_rcu (&dentry->d_u.d_rcu, d_callback);
@@ -247,6 +269,81 @@ static struct dentry *d_kill(struct dentreturn parent; } +void __d_drop (struct dentry *dentry) +{ +if (!(dentry->d_flags &DCACHE_UNHASHED )) { +struct dcache_hash_bucket *b; + b =d_hash (dentry->d_parent, dentry->d_name.hash); +/* XXX: put unhashed inside hash lock? (don't need to actually) */ + dentry->d_flags |=DCACHE_UNHASHED ; +spin_lock_bucket (b); +hlist_bl_del_rcu (&dentry->d_hash); +spin_unlock_bucket (b); + } +} +EXPORT_SYMBOL (__d_drop); + +void d_drop (struct dentry *dentry) +{ +spin_lock (&dentry->d_lock); +__d_drop (dentry); +spin_unlock (&dentry->d_lock); +} +EXPORT_SYMBOL (d_drop); + +/* This must be called with d_lock held */ +static inline struct dentry *__dget_locked_dlock (struct dentry *dentry) +{ + dentry->d_count++; +dentry_lru_del_init (dentry); +return dentry; +} + +static inline struct dentry *__dget_locked (struct dentry *dentry) +{ +spin_lock (&dentry->d_lock); +__dget_locked_dlock (dentry); +spin_unlock (&dentry->d_lock); +return dentry; +} + +struct dentry *dget_locked_dlock (struct dentry *dentry) +{ +return __dget_locked_dlock (dentry); +} + +struct dentry *dget_locked (struct dentry *dentry) +{ +return __dget_locked (dentry); +} +EXPORT_SYMBOL (dget_locked); + +struct dentry *dget_parent (struct dentry *dentry) +{ +struct dentry *ret; + +repeat: +spin_lock (&dentry->d_lock); + ret = dentry->d_parent; +if (!ret) + goto out; +if (dentry == ret) { + ret->d_count++; + goto out; + } +if (!spin_trylock (&ret->d_lock)) { +spin_unlock (&dentry->d_lock); + goto repeat; + } +BUG_ON (!ret->d_count); + ret->d_count++; +spin_unlock (&ret->d_lock); +out: +spin_unlock (&dentry->d_lock); +return ret; +} +EXPORT_SYMBOL (dget_parent); +/* * This is dput *
@@ -398,60 +495,6 @@ int d_invalidate(struct dentry * dentry) }EXPORT_SYMBOL (d_invalidate); -/* This must be called with d_lock held */ -static inline struct dentry *__dget_locked_dlock (struct dentry *dentry) -{ - dentry->d_count++; -dentry_lru_del_init (dentry); -return dentry; -} - -/* This must be called with d_lock held */ -static inline struct dentry *__dget_locked (struct dentry *dentry) -{ -spin_lock (&dentry->d_lock); -__dget_locked_dlock (dentry); -spin_unlock (&dentry->d_lock); -return dentry; -} - -struct dentry *dget_locked_dlock (struct dentry *dentry) -{ -return __dget_locked_dlock (dentry); -} - -struct dentry *dget_locked (struct dentry *dentry) -{ -return __dget_locked (dentry); -} -EXPORT_SYMBOL (dget_locked); - -struct dentry *dget_parent (struct dentry *dentry) -{ -struct dentry *ret; - -repeat: -spin_lock (&dentry->d_lock); - ret = dentry->d_parent; -if (!ret) - goto out; -if (dentry == ret) { - ret->d_count++; - goto out; - } -if (!spin_trylock (&ret->d_lock)) { -spin_unlock (&dentry->d_lock); - goto repeat; - } -BUG_ON (!ret->d_count); - ret->d_count++; -spin_unlock (&ret->d_lock); -out: -spin_unlock (&dentry->d_lock); -return ret; -} -EXPORT_SYMBOL (dget_parent); -/** * d_find_alias - grab a hashed alias of inode * @inode: inode in question
@@ -900,8 +943,8 @@ void shrink_dcache_for_umount(struct supspin_unlock (&dentry->d_lock);shrink_dcache_for_umount_subtree (dentry); -while (!hlist_empty (&sb->s_anon)) { - dentry = hlist_entry(sb->s_anon.first, struct dentry, d_hash); +while (!hlist_bl_empty (&sb->s_anon)) { + dentry = hlist_bl_entry(hlist_bl_first(&sb->s_anon), struct dentry, d_hash);shrink_dcache_for_umount_subtree (dentry); } }
@@ -1183,7 +1226,7 @@ struct dentry *d_alloc(struct dentry * p dentry->d_op =NULL ; dentry->d_fsdata =NULL ; dentry->d_mounted =0 ; -INIT_HLIST_NODE (&dentry->d_hash); +INIT_HLIST_BL_NODE (&dentry->d_hash);INIT_LIST_HEAD (&dentry->d_lru);INIT_LIST_HEAD (&dentry->d_subdirs);INIT_LIST_HEAD (&dentry->d_alias);
@@ -1348,14 +1391,6 @@ struct dentry * d_alloc_root(struct inod }EXPORT_SYMBOL (d_alloc_root); -static inline struct hlist_head *d_hash (struct dentry *parent, -unsigned long hash) -{ - hash += ((unsigned long ) parent ^GOLDEN_RATIO_PRIME ) /L1_CACHE_BYTES ; - hash = hash ^ ((hash ^GOLDEN_RATIO_PRIME ) >>D_HASHBITS ); -return dentry_hashtable + (hash &D_HASHMASK ); -} -/** * d_obtain_alias - find or allocate a dentry for a given inode * @inode: inode to allocate the dentry for
@@ -1411,7 +1446,7 @@ struct dentry *d_obtain_alias(struct ino tmp->d_flags |=DCACHE_DISCONNECTED ; tmp->d_flags &= ~DCACHE_UNHASHED ;list_add (&tmp->d_alias, &inode->i_dentry); -hlist_add_head (&tmp->d_hash, &inode->i_sb->s_anon); +hlist_bl_add_head (&tmp->d_hash, &inode->i_sb->s_anon);/* XXX: make s_anon a bl list */ spin_unlock (&tmp->d_lock);spin_unlock (&dcache_inode_lock);
@@ -1604,14 +1639,14 @@ struct dentry * __d_lookup(struct dentryunsigned int len = name->len;unsigned int hash = name->hash;const unsigned char *str = name->name; -struct hlist_head *head =d_hash (parent,hash); +struct dcache_hash_bucket *b =d_hash (parent, hash); +struct hlist_bl_node *node;struct dentry *found =NULL ; -struct hlist_node *node;struct dentry *dentry;rcu_read_lock (); -hlist_for_each_entry_rcu (dentry, node, head, d_hash) { +hlist_bl_for_each_entry_rcu (dentry, node, &b->head, d_hash) {struct qstr *qstr;if (dentry->d_name.hash != hash)
@@ -1698,8 +1733,9 @@ out:int d_validate (struct dentry *dentry,struct dentry *dparent) { -struct hlist_head *base; -struct hlist_node *lhp; +struct dcache_hash_bucket *b; +struct dentry *lhp; +struct hlist_bl_node *node;/* Check whether the ptr might be valid at all.. */ if (!kmem_ptr_validate (dentry_cache, dentry))
@@ -1709,20 +1745,17 @@ int d_validate(struct dentry *dentry, st goto out;spin_lock (&dentry->d_lock); -spin_lock (&dcache_hash_lock); - base =d_hash (dparent, dentry->d_name.hash); -hlist_for_each (lhp,base) { -/* hlist_for_each_entry_rcu() not required for d_hash list -* as it is parsed under dcache_hash_lock -*/ - if (dentry == hlist_entry(lhp, struct dentry, d_hash)) { -spin_unlock (&dcache_hash_lock); + b =d_hash (dparent, dentry->d_name.hash); +rcu_read_lock (); +hlist_bl_for_each_entry_rcu (lhp, node, &b->head, d_hash) { +if (dentry == lhp) { +rcu_read_unlock ();__dget_locked_dlock (dentry);spin_unlock (&dentry->d_lock);return 1 ; } } -spin_unlock (&dcache_hash_lock); +rcu_read_unlock ();spin_unlock (&dentry->d_lock); out:return 0 ;
@@ -1776,11 +1809,12 @@ void d_delete(struct dentry * dentry) }EXPORT_SYMBOL (d_delete); -static void __d_rehash (struct dentry * entry,struct hlist_head *list) +static void __d_rehash (struct dentry * entry,struct dcache_hash_bucket *b) { - entry->d_flags &= ~DCACHE_UNHASHED ; -hlist_add_head_rcu (&entry->d_hash, list); +spin_lock_bucket (b); +hlist_bl_add_head_rcu (&entry->d_hash, &b->head); +spin_unlock_bucket (b); }static void _d_rehash (struct dentry * entry)
@@ -1798,9 +1832,7 @@ static void _d_rehash(struct dentry * envoid d_rehash (struct dentry * entry) {spin_lock (&entry->d_lock); -spin_lock (&dcache_hash_lock);_d_rehash (entry); -spin_unlock (&dcache_hash_lock);spin_unlock (&entry->d_lock); }EXPORT_SYMBOL (d_rehash);
@@ -1879,6 +1911,7 @@ static void switch_names(struct dentry **/ static void d_move_locked (struct dentry * dentry,struct dentry * target) { +struct dcache_hash_bucket *b;if (!dentry->d_inode)printk (KERN_WARNING "VFS: moving negative dcache entry\n" );
@@ -1909,11 +1942,13 @@ static void d_move_locked(struct dentry }/* Move the dentry to the target hash queue, if on different bucket */ -spin_lock (&dcache_hash_lock); -if (!d_unhashed (dentry)) -hlist_del_rcu (&dentry->d_hash); +if (!d_unhashed (dentry)) { + b =d_hash (dentry->d_parent, dentry->d_name.hash); +spin_lock_bucket (b); +hlist_bl_del_rcu (&dentry->d_hash); +spin_unlock_bucket (b); + }__d_rehash (dentry,d_hash (target->d_parent, target->d_name.hash)); -spin_unlock (&dcache_hash_lock);/* Unhash the target: dput() will then get rid of it */ __d_drop (target);
@@ -2121,9 +2156,7 @@ struct dentry *d_materialise_unique(stru found_lock:spin_lock (&actual->d_lock); found: -spin_lock (&dcache_hash_lock);_d_rehash (actual); -spin_unlock (&dcache_hash_lock);spin_unlock (&actual->d_lock);spin_unlock (&dcache_inode_lock); out_nolock:
@@ -2631,7 +2664,7 @@ static void __init dcache_init_early(voi dentry_hashtable = alloc_large_system_hash("Dentry cache", - sizeof(struct hlist_head), + sizeof(struct dcache_hash_bucket), dhash_entries, 13, HASH_EARLY,
@@ -2640,7 +2673,7 @@ static void __init dcache_init_early(voi 0);for (loop =0 ; loop < (1 << d_hash_shift); loop++) -INIT_HLIST_HEAD (&dentry_hashtable[loop]); +INIT_HLIST_BL_HEAD (&dentry_hashtable[loop].head); }static void __init dcache_init (void )
@@ -2663,7 +2696,7 @@ static void __init dcache_init(void) dentry_hashtable = alloc_large_system_hash("Dentry cache", - sizeof(struct hlist_head), + sizeof(struct dcache_hash_bucket), dhash_entries, 13, 0,
@@ -2672,7 +2705,7 @@ static void __init dcache_init(void) 0);for (loop =0 ; loop < (1 << d_hash_shift); loop++) -INIT_HLIST_HEAD (&dentry_hashtable[loop]); +INIT_HLIST_BL_HEAD (&dentry_hashtable[loop].head); }/* SLAB cache for __getname() consumers */
include/linux/dcache.h +4 -19
@@ -4,6 +4,7 @@#include <asm/atomic.h> #include <linux/list.h> #include <linux/rculist.h> +#include <linux/rculist_bl.h> #include <linux/spinlock.h> #include <linux/cache.h> #include <linux/rcupdate.h>
@@ -97,7 +98,7 @@ struct dentry {* The next three fields are touched by __d_lookup. Place them here * so they all fit in a cache line. */ -struct hlist_node d_hash;/* lookup hash list */ +struct hlist_bl_node d_hash;/* lookup hash list */ struct dentry *d_parent;/* parent directory */ struct qstr d_name;
@@ -190,7 +191,6 @@ d_iput: no no yes#define DCACHE_GENOCIDE 0x0200extern spinlock_t dcache_inode_lock; -extern spinlock_t dcache_hash_lock;extern seqlock_t rename_lock;/**
@@ -208,23 +208,8 @@ extern seqlock_t rename_lock;* * __d_drop requires dentry->d_lock. */ - -static inline void __d_drop (struct dentry *dentry) -{ -if (!(dentry->d_flags &DCACHE_UNHASHED )) { - dentry->d_flags |=DCACHE_UNHASHED ; -spin_lock (&dcache_hash_lock); -hlist_del_rcu (&dentry->d_hash); -spin_unlock (&dcache_hash_lock); - } -} - -static inline void d_drop (struct dentry *dentry) -{ -spin_lock (&dentry->d_lock); -__d_drop (dentry); -spin_unlock (&dentry->d_lock); -} +void d_drop (struct dentry *dentry); +void __d_drop (struct dentry *dentry);static inline int dname_external (struct dentry *dentry) {
fs/super.c +2 -1
@@ -30,6 +30,7 @@#include <linux/idr.h> #include <linux/mutex.h> #include <linux/backing-dev.h> +#include <linux/rculist_bl.h> #include "internal.h"
@@ -71,7 +72,7 @@ static struct super_block *alloc_super(sINIT_LIST_HEAD (&s->s_files);#endif INIT_LIST_HEAD (&s->s_instances); -INIT_HLIST_HEAD (&s->s_anon); +INIT_HLIST_BL_HEAD (&s->s_anon);INIT_LIST_HEAD (&s->s_inodes);INIT_LIST_HEAD (&s->s_dentry_lru);init_rwsem (&s->s_umount);
include/linux/fs.h +2 -1
@@ -382,6 +382,7 @@ struct inodes_stat_t {#include <linux/capability.h> #include <linux/semaphore.h> #include <linux/fiemap.h> +#include <linux/rculist_bl.h> #include <asm/atomic.h> #include <asm/byteorder.h>
@@ -1341,7 +1342,7 @@ struct super_block {const struct xattr_handler **s_xattr;struct list_head s_inodes;/* all inodes */ -struct hlist_head s_anon;/* anonymous dentries for (nfs) exporting */ +struct hlist_bl_head s_anon;/* anonymous dentries for (nfs) exporting */ #ifdef CONFIG_SMP struct list_head __percpu *s_files;#else
PATCH 20/52 fs: dcache reduce dcache_inode_lock
In reply to [email protected]dcache_inode_lock can be avoided in d_delete() and d_materialise_unique() in cases where it is not required.
--- fs/dcache.c | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-)
fs/dcache.c +12 -12
@@ -1789,10 +1789,15 @@ void d_delete(struct dentry * dentry)/* * Are we the only user? */ -spin_lock (&dcache_inode_lock); +again:spin_lock (&dentry->d_lock); isdir =S_ISDIR (dentry->d_inode->i_mode);if (dentry->d_count ==1 ) { +if (!spin_trylock (&dcache_inode_lock)) { +spin_unlock (&dentry->d_lock); +cpu_relax (); + goto again; + } dentry->d_flags &= ~DCACHE_CANT_MOUNT ;dentry_iput (dentry);fsnotify_nameremove (dentry, isdir);
@@ -1803,7 +1808,6 @@ void d_delete(struct dentry * dentry)__d_drop (dentry);spin_unlock (&dentry->d_lock); -spin_unlock (&dcache_inode_lock);fsnotify_nameremove (dentry, isdir); }
@@ -2116,14 +2120,15 @@ struct dentry *d_materialise_unique(struBUG_ON (!d_unhashed (dentry)); -spin_lock (&dcache_inode_lock); -if (!inode) { actual = dentry;__d_instantiate (dentry,NULL ); - goto found_lock; +d_rehash (actual); + goto out_nolock; } +spin_lock (&dcache_inode_lock); +if (S_ISDIR (inode->i_mode)) {struct dentry *alias;
@@ -2150,10 +2155,9 @@ struct dentry *d_materialise_unique(stru actual =__d_instantiate_unique (dentry, inode);if (!actual) actual = dentry; -else if (unlikely (!d_unhashed (actual))) - goto shouldnt_be_hashed; +else +BUG_ON (!d_unhashed (actual)); -found_lock:spin_lock (&actual->d_lock); found:_d_rehash (actual);
@@ -2167,10 +2171,6 @@ out_nolock:iput (inode);return actual; - -shouldnt_be_hashed: -spin_unlock (&dcache_inode_lock); -BUG (); }EXPORT_SYMBOL_GPL (d_materialise_unique);
PATCH 21/52 fs: dcache per-inode inode alias locking
In reply to [email protected]dcache_inode_lock can be replaced with per-inode locking. Use existing inode->i_lock for this. This is slightly non-trivial because we sometimes need to find the inode from the dentry, which requires d_inode to be stabilised (either with refcount or d_lock).
--- fs/affs/amigaffs.c | 4 - fs/dcache.c | 117 +++++++++++++++++++++++++------------------- fs/exportfs/expfs.c | 12 ++-- fs/nfs/getroot.c | 4 - fs/notify/fsnotify.c | 4 - fs/notify/inotify/inotify.c | 4 - fs/ocfs2/dcache.c | 4 - fs/sysfs/dir.c | 6 +- include/linux/dcache.h | 1 9 files changed, 89 insertions(+), 67 deletions(-)
fs/dcache.c +68 -47
@@ -39,8 +39,8 @@/* * Usage: -* dcache_inode_lock protects: -* - i_dentry, d_alias, d_inode +* dcache->d_inode->i_lock protects: +* - i_dentry, d_alias, d_inode of aliases * dcache_hash_bucket lock protects: * - the dcache hash table * dcache_lru_lock protects:
@@ -56,7 +56,7 @@* - d_alias, d_inode * * Ordering: -* dcache_inode_lock +* dentry->d_inode->i_lock * dentry->d_lock * dcache_lru_lock * dcache_hash_bucket lock
@@ -75,12 +75,10 @@int sysctl_vfs_cache_pressure__read_mostly =100 ;EXPORT_SYMBOL_GPL (sysctl_vfs_cache_pressure); -__cacheline_aligned_in_smp DEFINE_SPINLOCK (dcache_inode_lock );static __cacheline_aligned_in_smp DEFINE_SPINLOCK (dcache_lru_lock );__cacheline_aligned_in_smp DEFINE_SEQLOCK (rename_lock );EXPORT_SYMBOL (rename_lock); -EXPORT_SYMBOL (dcache_inode_lock);static struct kmem_cache *dentry_cache__read_mostly ;
@@ -165,14 +163,13 @@ static void d_free(struct dentry *dentry*/ static void dentry_iput (struct dentry * dentry) __releases(dentry->d_lock) -__releases (dcache_inode_lock) {struct inode *inode = dentry->d_inode;if (inode) { dentry->d_inode =NULL ;list_del_init (&dentry->d_alias);spin_unlock (&dentry->d_lock); -spin_unlock (&dcache_inode_lock); +spin_unlock (&inode->i_lock);if (!inode->i_nlink)fsnotify_inoderemove (inode);if (dentry->d_op && dentry->d_op->d_iput)
@@ -181,7 +178,6 @@ static void dentry_iput(struct dentry *iput (inode); }else {spin_unlock (&dentry->d_lock); -spin_unlock (&dcache_inode_lock); } }
@@ -252,7 +248,6 @@ static void dentry_lru_del_init(struct d*/ static struct dentry *d_kill (struct dentry *dentry) __releases(dentry->d_lock) -__releases (dcache_inode_lock) {struct dentry *parent;
@@ -376,6 +371,7 @@ EXPORT_SYMBOL(dget_parent);void dput (struct dentry *dentry) {struct dentry *parent; +struct inode *inode;if (!dentry)return ;
@@ -412,14 +408,21 @@ repeat:return ; kill_it: -spin_unlock (&dentry->d_lock); -spin_lock (&dcache_inode_lock); + inode = dentry->d_inode; +if (inode) { +if (!spin_trylock (&inode->i_lock)) { relock: -spin_lock (&dentry->d_lock); +spin_unlock (&dentry->d_lock); +cpu_relax (); +spin_lock (&dentry->d_lock); + goto kill_it; + } + } parent = dentry->d_parent;if (parent && parent != dentry) {if (!spin_trylock (&parent->d_lock)) { -spin_unlock (&dentry->d_lock); +if (inode) +spin_unlock (&inode->i_lock); goto relock; } }
@@ -429,7 +432,8 @@ relock:spin_unlock (&dentry->d_lock);if (parent && parent != dentry)spin_unlock (&parent->d_lock); -spin_unlock (&dcache_inode_lock); +if (inode) +spin_unlock (&inode->i_lock);return ; }/* if dentry was on the d_lru list delete it from there */
@@ -547,9 +551,9 @@ struct dentry * d_find_alias(struct inodstruct dentry *de =NULL ;if (!list_empty (&inode->i_dentry)) { -spin_lock (&dcache_inode_lock); +spin_lock (&inode->i_lock); de =__d_find_alias (inode,0 ); -spin_unlock (&dcache_inode_lock); +spin_unlock (&inode->i_lock); }return de; }
@@ -563,20 +567,20 @@ void d_prune_aliases(struct inode *inode {struct dentry *dentry; restart: -spin_lock (&dcache_inode_lock); +spin_lock (&inode->i_lock);list_for_each_entry (dentry, &inode->i_dentry, d_alias) {spin_lock (&dentry->d_lock);if (!dentry->d_count) {__dget_locked_dlock (dentry);__d_drop (dentry);spin_unlock (&dentry->d_lock); -spin_unlock (&dcache_inode_lock); +spin_unlock (&inode->i_lock);dput (dentry); goto restart; }spin_unlock (&dentry->d_lock); } -spin_unlock (&dcache_inode_lock); +spin_unlock (&inode->i_lock); }EXPORT_SYMBOL (d_prune_aliases);
@@ -599,8 +603,10 @@ static void prune_one_dentry(struct dent*/ while (dentry) {struct dentry *parent =NULL ; +struct inode *inode = dentry->d_inode; -spin_lock (&dcache_inode_lock); +if (inode) +spin_lock (&inode->i_lock); again:spin_lock (&dentry->d_lock);if (dentry->d_parent && dentry != dentry->d_parent) {
@@ -615,7 +621,8 @@ again:if (parent)spin_unlock (&parent->d_lock);spin_unlock (&dentry->d_lock); -spin_unlock (&dcache_inode_lock); +if (inode) +spin_unlock (&inode->i_lock);return ; }
@@ -684,10 +691,11 @@ restart: }spin_unlock (&dcache_lru_lock); -spin_lock (&dcache_inode_lock); again:spin_lock (&dcache_lru_lock);/* lru_lock also protects tmp list */ while (!list_empty (&tmp)) { +struct inode *inode; + dentry = list_entry(tmp.prev, struct dentry, d_lru);if (!spin_trylock (&dentry->d_lock)) {
@@ -705,10 +713,17 @@ again1:spin_unlock (&dentry->d_lock);continue ; } + inode = dentry->d_inode; +if (inode && !spin_trylock (&inode->i_lock)) { +again2: +spin_unlock (&dentry->d_lock); + goto again1; + }if (dentry->d_parent && dentry->d_parent != dentry) {if (!spin_trylock (&dentry->d_parent->d_lock)) { -spin_unlock (&dentry->d_lock); - goto again1; +if (inode) +spin_unlock (&inode->i_lock); + goto again2; } }__dentry_lru_del_init (dentry);
@@ -716,10 +731,8 @@ again1:prune_one_dentry (dentry);/* dentry->d_lock dropped */ -spin_lock (&dcache_inode_lock);spin_lock (&dcache_lru_lock); } -spin_unlock (&dcache_inode_lock);if (count ==NULL && !list_empty (&sb->s_dentry_lru)) goto restart;
@@ -1287,9 +1300,11 @@ static void __d_instantiate(struct dentrvoid d_instantiate (struct dentry *entry,struct inode * inode) {BUG_ON (!list_empty (&entry->d_alias)); -spin_lock (&dcache_inode_lock); +if (inode) +spin_lock (&inode->i_lock);__d_instantiate (entry, inode); -spin_unlock (&dcache_inode_lock); +if (inode) +spin_unlock (&inode->i_lock);security_d_instantiate (entry, inode); }EXPORT_SYMBOL (d_instantiate);
@@ -1348,9 +1363,11 @@ struct dentry *d_instantiate_unique(struBUG_ON (!list_empty (&entry->d_alias)); -spin_lock (&dcache_inode_lock); +if (inode) +spin_lock (&inode->i_lock); result =__d_instantiate_unique (entry, inode); -spin_unlock (&dcache_inode_lock); +if (inode) +spin_unlock (&inode->i_lock);if (!result) {security_d_instantiate (entry, inode);
@@ -1431,10 +1448,10 @@ struct dentry *d_obtain_alias(struct ino } tmp->d_parent = tmp;/* make sure dput doesn't croak */ -spin_lock (&dcache_inode_lock); +spin_lock (&inode->i_lock); res =__d_find_alias (inode,0 );if (res) { -spin_unlock (&dcache_inode_lock); +spin_unlock (&inode->i_lock);dput (tmp); goto out_iput; }
@@ -1448,7 +1465,7 @@ struct dentry *d_obtain_alias(struct inolist_add (&tmp->d_alias, &inode->i_dentry);hlist_bl_add_head (&tmp->d_hash, &inode->i_sb->s_anon);/* XXX: make s_anon a bl list */ spin_unlock (&tmp->d_lock); -spin_unlock (&dcache_inode_lock); +spin_unlock (&inode->i_lock);return tmp;
@@ -1479,18 +1496,18 @@ struct dentry *d_splice_alias(struct inostruct dentry *new =NULL ;if (inode &&S_ISDIR (inode->i_mode)) { -spin_lock (&dcache_inode_lock); +spin_lock (&inode->i_lock); new =__d_find_alias (inode,1 );if (new) {BUG_ON (!(new->d_flags &DCACHE_DISCONNECTED )); -spin_unlock (&dcache_inode_lock); +spin_unlock (&inode->i_lock);security_d_instantiate (new, inode);d_move (new, dentry);iput (inode); }else { -/* already got dcache_inode_lock, so d_add() by hand */ +/* already got inode->i_lock, so d_add() by hand */ __d_instantiate (dentry, inode); -spin_unlock (&dcache_inode_lock); +spin_unlock (&inode->i_lock);security_d_instantiate (dentry, inode);d_rehash (dentry); }
@@ -1563,10 +1580,10 @@ struct dentry *d_add_ci(struct dentry *d* Negative dentry: instantiate it unless the inode is a directory and * already has a dentry. */ -spin_lock (&dcache_inode_lock); +spin_lock (&inode->i_lock);if (!S_ISDIR (inode->i_mode) ||list_empty (&inode->i_dentry)) {__d_instantiate (found, inode); -spin_unlock (&dcache_inode_lock); +spin_unlock (&inode->i_lock);security_d_instantiate (found, inode);return found; }
@@ -1577,7 +1594,7 @@ struct dentry *d_add_ci(struct dentry *d*/ new = list_entry(inode->i_dentry.next, struct dentry, d_alias);dget_locked (new); -spin_unlock (&dcache_inode_lock); +spin_unlock (&inode->i_lock);security_d_instantiate (found, inode);d_move (new, found);iput (inode);
@@ -1785,15 +1802,17 @@ EXPORT_SYMBOL(d_validate);void d_delete (struct dentry * dentry) { +struct inode *inode;int isdir =0 ;/* * Are we the only user? */ again:spin_lock (&dentry->d_lock); - isdir =S_ISDIR (dentry->d_inode->i_mode); + inode = dentry->d_inode; + isdir =S_ISDIR (inode->i_mode);if (dentry->d_count ==1 ) { -if (!spin_trylock (&dcache_inode_lock)) { +if (inode && !spin_trylock (&inode->i_lock)) {spin_unlock (&dentry->d_lock);cpu_relax (); goto again;
@@ -2034,6 +2053,7 @@ static struct dentry *__d_unalias(struct {struct mutex *m1 =NULL , *m2 =NULL ;struct dentry *ret; +struct inode *inode;/* If alias and dentry share a parent, then no extra locks required */ if (alias->d_parent == dentry->d_parent)
@@ -2049,14 +2069,15 @@ static struct dentry *__d_unalias(structif (!mutex_trylock (&dentry->d_sb->s_vfs_rename_mutex)) goto out_err; m1 = &dentry->d_sb->s_vfs_rename_mutex; -if (!mutex_trylock (&alias->d_parent->d_inode->i_mutex)) + inode = alias->d_parent->d_inode; +if (!mutex_trylock (&inode->i_mutex)) goto out_err; - m2 = &alias->d_parent->d_inode->i_mutex; + m2 = &inode->i_mutex; out_unalias:d_move_locked (alias, dentry); ret = alias; out_err: -spin_unlock (&dcache_inode_lock); +spin_unlock (&inode->i_lock);if (m2)mutex_unlock (m2);if (m1)
@@ -2127,7 +2148,7 @@ struct dentry *d_materialise_unique(stru goto out_nolock; } -spin_lock (&dcache_inode_lock); +spin_lock (&inode->i_lock);if (S_ISDIR (inode->i_mode)) {struct dentry *alias;
@@ -2162,7 +2183,7 @@ struct dentry *d_materialise_unique(stru found:_d_rehash (actual);spin_unlock (&actual->d_lock); -spin_unlock (&dcache_inode_lock); +spin_unlock (&inode->i_lock); out_nolock:if (actual == dentry) {security_d_instantiate (dentry, inode);
include/linux/dcache.h +0 -1
@@ -190,7 +190,6 @@ d_iput: no no yes#define DCACHE_CANT_MOUNT 0x0100#define DCACHE_GENOCIDE 0x0200 -extern spinlock_t dcache_inode_lock;extern seqlock_t rename_lock;/**
fs/notify/inotify/inotify.c +2 -2
@@ -181,7 +181,7 @@ static void set_dentry_child_flags(struc {struct dentry *alias; -spin_lock (&dcache_inode_lock); +spin_lock (&inode->i_lock);list_for_each_entry (alias, &inode->i_dentry, d_alias) {struct dentry *child;
@@ -199,7 +199,7 @@ static void set_dentry_child_flags(struc }spin_unlock (&alias->d_lock); } -spin_unlock (&dcache_inode_lock); +spin_unlock (&inode->i_lock); }/*
fs/exportfs/expfs.c +7 -5
@@ -43,24 +43,26 @@ find_acceptable_alias(struct dentry *res void *context) {struct dentry *dentry, *toput =NULL ; +struct inode *inode;if (acceptable (context, result))return result; -spin_lock (&dcache_inode_lock); -list_for_each_entry (dentry, &result->d_inode->i_dentry, d_alias) { + inode = result->d_inode; +spin_lock (&inode->i_lock); +list_for_each_entry (dentry, &inode->i_dentry, d_alias) {dget_locked (dentry); -spin_unlock (&dcache_inode_lock); +spin_unlock (&inode->i_lock);if (toput)dput (toput);if (dentry != result &&acceptable (context, dentry)) {dput (result);return dentry; } -spin_lock (&dcache_inode_lock); +spin_lock (&inode->i_lock); toput = dentry; } -spin_unlock (&dcache_inode_lock); +spin_unlock (&inode->i_lock);if (toput)dput (toput);
fs/affs/amigaffs.c +2 -2
@@ -128,7 +128,7 @@ affs_fix_dcache(struct dentry *dentry, uvoid *data = dentry->d_fsdata;struct list_head *head, *next; -spin_lock (&dcache_inode_lock); +spin_lock (&inode->i_lock); head = &inode->i_dentry; next = head->next;while (next != head) {
@@ -139,7 +139,7 @@ affs_fix_dcache(struct dentry *dentry, u } next = next->next; } -spin_unlock (&dcache_inode_lock); +spin_unlock (&inode->i_lock); }
fs/nfs/getroot.c +2 -2
@@ -64,11 +64,11 @@ static int nfs_superblock_set_dummy_root* This again causes shrink_dcache_for_umount_subtree() to * Oops, since the test for IS_ROOT() will fail. */ -spin_lock (&dcache_inode_lock); +spin_lock (&sb->s_root->d_inode->i_lock);spin_lock (&sb->s_root->d_lock);list_del_init (&sb->s_root->d_alias);spin_unlock (&sb->s_root->d_lock); -spin_unlock (&dcache_inode_lock); +spin_unlock (&sb->s_root->d_inode->i_lock); }return 0 ; }
fs/ocfs2/dcache.c +2 -2
@@ -151,7 +151,7 @@ struct dentry *ocfs2_find_local_alias(ststruct list_head *p;struct dentry *dentry =NULL ; -spin_lock (&dcache_inode_lock); +spin_lock (&inode->i_lock);list_for_each (p, &inode->i_dentry) { dentry = list_entry(p, struct dentry, d_alias);
@@ -169,7 +169,7 @@ struct dentry *ocfs2_find_local_alias(st dentry =NULL ; } -spin_unlock (&dcache_inode_lock); +spin_unlock (&inode->i_lock);return dentry; }
fs/notify/fsnotify.c +2 -2
@@ -53,7 +53,7 @@ void __fsnotify_update_child_dentry_flag/* determine if the children should tell inode about their events */ watched =fsnotify_inode_watches_children (inode); -spin_lock (&dcache_inode_lock); +spin_lock (&inode->i_lock);/* run all of the dentries associated with this inode. Since this is a * directory, there damn well better only be one item on this list */ list_for_each_entry (alias, &inode->i_dentry, d_alias) {
@@ -76,7 +76,7 @@ void __fsnotify_update_child_dentry_flag }spin_unlock (&alias->d_lock); } -spin_unlock (&dcache_inode_lock); +spin_unlock (&inode->i_lock); }/* Notify this dentry's parent about a child's events. */
PATCH 22/52 fs: dcache rationalise dget variants
In reply to [email protected]dget_locked was a shortcut to avoid the lazy lru manipulation when we already held dcache_lock (lru manipulation was relatively cheap at that point). However, how that the lru lock is an innermost one, we never hold it at any caller, so the lock cost can now be avoided. We already have well working lazy dcache LRU, so it should be fine to defer LRU manipulations to scan time.
--- arch/powerpc/platforms/cell/spufs/inode.c | 2 - drivers/infiniband/hw/ipath/ipath_fs.c | 2 - fs/autofs4/root.c | 4 +-- fs/configfs/inode.c | 2 - fs/dcache.c | 34 ++++++++---------------------- fs/exportfs/expfs.c | 2 - fs/ncpfs/dir.c | 2 - fs/ocfs2/dcache.c | 2 - fs/smbfs/cache.c | 2 - fs/sysfs/dir.c | 2 - include/linux/dcache.h | 15 ++----------- kernel/cgroup.c | 2 - security/selinux/selinuxfs.c | 2 - 13 files changed, 25 insertions(+), 48 deletions(-)
arch/powerpc/platforms/cell/spufs/inode.c +1 -1
@@ -160,7 +160,7 @@ static void spufs_prune_dir(struct dentrlist_for_each_entry_safe (dentry, tmp, &dir->d_subdirs, d_u.d_child) {spin_lock (&dentry->d_lock);if (!(d_unhashed (dentry)) && dentry->d_inode) { -dget_locked_dlock (dentry); +dget_dlock (dentry);__d_drop (dentry);spin_unlock (&dentry->d_lock);simple_unlink (dir->d_inode, dentry);
drivers/infiniband/hw/ipath/ipath_fs.c +1 -1
@@ -275,7 +275,7 @@ static int remove_file(struct dentry *paspin_lock (&tmp->d_lock);if (!(d_unhashed (tmp) && tmp->d_inode)) { -dget_locked_dlock (tmp); +dget_dlock (tmp);__d_drop (tmp);spin_unlock (&tmp->d_lock);simple_unlink (parent->d_inode, tmp);
fs/configfs/inode.c +1 -1
@@ -251,7 +251,7 @@ void configfs_drop_dentry(struct configfif (dentry) {spin_lock (&dentry->d_lock);if (!(d_unhashed (dentry) && dentry->d_inode)) { -dget_locked_dlock (dentry); +dget_dlock (dentry);__d_drop (dentry);spin_unlock (&dentry->d_lock);simple_unlink (parent->d_inode, dentry);
fs/dcache.c +11 -18
@@ -287,29 +287,23 @@ void d_drop(struct dentry *dentry)EXPORT_SYMBOL (d_drop);/* This must be called with d_lock held */ -static inline struct dentry *__dget_locked_dlock (struct dentry *dentry) +static inline struct dentry *__dget_dlock (struct dentry *dentry) { dentry->d_count++; -dentry_lru_del_init (dentry);return dentry; } -static inline struct dentry *__dget_locked (struct dentry *dentry) +static inline struct dentry *__dget (struct dentry *dentry) {spin_lock (&dentry->d_lock); -__dget_locked_dlock (dentry); +__dget_dlock (dentry);spin_unlock (&dentry->d_lock);return dentry; } -struct dentry *dget_locked_dlock (struct dentry *dentry) -{ -return __dget_locked_dlock (dentry); -} -struct dentry *dget_locked (struct dentry *dentry) { -return __dget_locked (dentry); +return __dget (dentry); }EXPORT_SYMBOL (dget_locked);
@@ -534,7 +528,7 @@ static struct dentry * __d_find_alias(st (alias->d_flags & DCACHE_DISCONNECTED)) discon_alias = alias; else if (!want_discon) { -__dget_locked_dlock (alias); +__dget_dlock (alias);spin_unlock (&alias->d_lock);return alias; }
@@ -542,7 +536,7 @@ static struct dentry * __d_find_alias(stspin_unlock (&alias->d_lock); }if (discon_alias) -__dget_locked (discon_alias); +__dget (discon_alias);return discon_alias; }
@@ -571,7 +565,7 @@ restart:list_for_each_entry (dentry, &inode->i_dentry, d_alias) {spin_lock (&dentry->d_lock);if (!dentry->d_count) { -__dget_locked_dlock (dentry); +__dget_dlock (dentry);__d_drop (dentry);spin_unlock (&dentry->d_lock);spin_unlock (&inode->i_lock);
@@ -1349,7 +1343,7 @@ static struct dentry *__d_instantiate_uncontinue ;if (memcmp (qstr->name, name, len))continue ; -dget_locked (alias); +dget (alias);return alias; }
@@ -1593,7 +1587,7 @@ struct dentry *d_add_ci(struct dentry *d* reference to it, move it in place and use it. */ new = list_entry(inode->i_dentry.next, struct dentry, d_alias); -dget_locked (new); +dget (new);spin_unlock (&inode->i_lock);security_d_instantiate (found, inode);d_move (new, found);
@@ -1629,8 +1623,7 @@ EXPORT_SYMBOL(d_add_ci);* * The dentry unused LRU is not updated even if lookup finds the required dentry * in there. It is updated in places such as prune_dcache, shrink_dcache_sb, -* select_parent and __dget_locked. This laziness saves lookup from LRU lock -* acquisition. +* select_parent. This laziness saves lookup from LRU lock acquisition. * * d_lookup() is protected against the concurrent renames in some unrelated * directory using the seqlockt_t rename_lock.
@@ -1767,7 +1760,7 @@ int d_validate(struct dentry *dentry, sthlist_bl_for_each_entry_rcu (lhp, node, &b->head, d_hash) {if (dentry == lhp) {rcu_read_unlock (); -__dget_locked_dlock (dentry); +__dget_dlock (dentry);spin_unlock (&dentry->d_lock);return 1 ; }
fs/exportfs/expfs.c +1 -1
@@ -51,7 +51,7 @@ find_acceptable_alias(struct dentry *res inode = result->d_inode;spin_lock (&inode->i_lock);list_for_each_entry (dentry, &inode->i_dentry, d_alias) { -dget_locked (dentry); +dget (dentry);spin_unlock (&inode->i_lock);if (toput)dput (toput);
fs/ncpfs/dir.c +1 -1
@@ -370,7 +370,7 @@ ncp_dget_fpos(struct dentry *dentry, str dent = list_entry(next, struct dentry, d_u.d_child);if ((unsigned long )dent->d_fsdata == fpos) {if (dent->d_inode) -dget_locked (dent); +dget (dent);else dent =NULL ;spin_unlock (&parent->d_lock);
fs/ocfs2/dcache.c +1 -1
@@ -160,7 +160,7 @@ struct dentry *ocfs2_find_local_alias(stmlog (0 ,"dentry found: %.*s\n" , dentry->d_name.len, dentry->d_name.name); -dget_locked_dlock (dentry); +dget_dlock (dentry);spin_unlock (&dentry->d_lock);break ; }
fs/smbfs/cache.c +1 -1
@@ -102,7 +102,7 @@ smb_dget_fpos(struct dentry *dentry, str dent = list_entry(next, struct dentry, d_u.d_child);if ((unsigned long )dent->d_fsdata == fpos) {if (dent->d_inode) -dget_locked (dent); +dget (dent);else dent =NULL ; goto out_unlock;
include/linux/dcache.h +3 -12
@@ -306,23 +306,17 @@ extern char *dentry_path(struct dentry */* Allocation counts.. */ /** -* dget, dget_locked - get a reference to a dentry +* dget, dget_dlock - get a reference to a dentry * @dentry: dentry to get a reference to * * Given a dentry or %NULL pointer increment the reference count * if appropriate and return the dentry. A dentry will not be -* destroyed when it has references. dget() should never be -* called for dentries with zero reference counter. For these cases -* (preferably none, functions in dcache.c are sufficient for normal -* needs and they take necessary precautions) you should hold dcache_lock -* and call dget_locked() instead of dget(). +* destroyed when it has references. */ static inline struct dentry *dget_dlock (struct dentry *dentry) { -if (dentry) { -BUG_ON (!dentry->d_count); +if (dentry) dentry->d_count++; - }return dentry; }
@@ -336,9 +330,6 @@ static inline struct dentry *dget(structreturn dentry; } -extern struct dentry *dget_locked (struct dentry *); -extern struct dentry *dget_locked_dlock (struct dentry *); -extern struct dentry *dget_parent (struct dentry *dentry);/**
kernel/cgroup.c +1 -1
@@ -880,7 +880,7 @@ static void cgroup_clear_directory(struc/* This should never be called on a cgroup * directory with child cgroups */ BUG_ON (d->d_inode->i_mode &S_IFDIR ); -dget_locked_dlock (d); +dget_dlock (d);spin_unlock (&d->d_lock);spin_unlock (&dentry->d_lock);d_delete (d);
security/selinux/selinuxfs.c +1 -1
@@ -950,7 +950,7 @@ static void sel_remove_entries(struct delist_del_init (node);if (d->d_inode) { -dget_locked_dlock (d); +dget_dlock (d);spin_unlock (&de->d_lock);spin_unlock (&d->d_lock);d_delete (d);
drivers/infiniband/hw/qib/qib_fs.c +1 -1
@@ -453,7 +453,7 @@ static int remove_file(struct dentry *paspin_lock (&tmp->d_lock);if (!(d_unhashed (tmp) && tmp->d_inode)) { -dget_locked_dlock (tmp); +dget_dlock (tmp);__d_drop (tmp);spin_unlock (&tmp->d_lock);simple_unlink (parent->d_inode, tmp);
PATCH 23/52 fs: dcache percpu nr_dentry
In reply to [email protected]The nr_dentry stat is a globally touched cacheline and atomic operation twice over the lifetime of a dentry. It is used for the benfit of userspace only. We could make a per-cpu counter or something for it, but it is only accessed via proc, so we could use slab stats.
XXX: must implement slab routines to return stats for a single cache, and implement the proc handler.
--- fs/dcache.c | 5 +---- include/linux/dcache.h | 2 +- 2 files changed, 2 insertions(+), 5 deletions(-)
fs/dcache.c +1 -4
@@ -105,7 +105,7 @@ static struct dcache_hash_bucket *dentry/* Statistics gathering. */ struct dentry_stat_t dentry_stat = { - .nr_dentry =ATOMIC_INIT (0 ), + .nr_dentry =0 , .age_limit =45 , };
@@ -146,7 +146,6 @@ static void d_callback(struct rcu_head **/ static void d_free (struct dentry *dentry) { -atomic_dec (&dentry_stat.nr_dentry);BUG_ON (dentry->d_count);if (dentry->d_op && dentry->d_op->d_release) dentry->d_op->d_release (dentry);
@@ -1249,8 +1248,6 @@ struct dentry *d_alloc(struct dentry * pspin_unlock (&parent->d_lock); } -atomic_inc (&dentry_stat.nr_dentry); -return dentry; }EXPORT_SYMBOL (d_alloc);
include/linux/dcache.h +1 -1
@@ -38,7 +38,7 @@ struct qstr { };struct dentry_stat_t { -atomic_t nr_dentry; +int nr_dentry;/* unused */ int nr_unused;/* protected by dcache_lru_lock */ int age_limit;/* age in seconds */ int want_pages;/* pages requested by system */
PATCH 24/52 fs: dcache reduce d_parent locking
In reply to [email protected]Use RCU property of dcache to simplify locking in some places where we take d_parent and d_lock.
Comment: don't need rcu_deref because we take the spinlock and recheck it.
--
fs/dcache.c +20 -17
@@ -311,23 +311,18 @@ struct dentry *dget_parent(struct dentrystruct dentry *ret; repeat: -spin_lock (&dentry->d_lock); +rcu_read_lock (); ret = dentry->d_parent; -if (!ret) - goto out; -if (dentry == ret) { - ret->d_count++; - goto out; - } -if (!spin_trylock (&ret->d_lock)) { -spin_unlock (&dentry->d_lock); +spin_lock (&ret->d_lock); +if (unlikely (ret != dentry->d_parent)) { +spin_unlock (&ret->d_lock); +rcu_read_unlock (); goto repeat; } +rcu_read_unlock ();BUG_ON (!ret->d_count); ret->d_count++;spin_unlock (&ret->d_lock); -out: -spin_unlock (&dentry->d_lock);return ret; }EXPORT_SYMBOL (dget_parent);
@@ -601,14 +596,22 @@ static void prune_one_dentry(struct dentif (inode)spin_lock (&inode->i_lock); again: -spin_lock (&dentry->d_lock); -if (dentry->d_parent && dentry != dentry->d_parent) { -if (!spin_trylock (&dentry->d_parent->d_lock)) { -spin_unlock (&dentry->d_lock); +rcu_read_lock (); + parent = dentry->d_parent; +if (parent) { +spin_lock (&parent->d_lock); +if (unlikely (parent != dentry->d_parent)) { +spin_unlock (&parent->d_lock); +rcu_read_unlock (); goto again; } - parent = dentry->d_parent; - } +if (parent != dentry) +spin_lock_nested (&dentry->d_lock,DENTRY_D_LOCK_NESTED ); +else + parent =NULL ; + }else +spin_lock (&dentry->d_lock); +rcu_read_unlock (); dentry->d_count--;if (dentry->d_count) {if (parent)
Peter Zijlstra
maintainerRe: [patch 24/52] fs: dcache reduce d_parent locking
Quoted 4 lines · On Thu, 2010-06-24 at 13:02 +1000, [email protected] wrote:
Use RCU property of dcache to simplify locking in some places where we take
d_parentandd_lock.Comment: don't need
rcu_derefbecause we take the spinlock and recheck it.
But does the LOCK barrier imply a DATA DEPENDENCY barrier? (It does on x86, and the compiler barrier implied by spin_lock() suffices to replace ACCESS_ONCE()).
Re: [patch 24/52] fs: dcache reduce d_parent locking
Quoted 9 lines · Peter Zijlstra
Quoted 4 lines · On Thu, 2010-06-24 at 13:02 +1000, [email protected] wrote:
Use RCU property of dcache to simplify locking in some places where we take
d_parentandd_lock.Comment: don't need
rcu_derefbecause we take the spinlock and recheck it.But does the LOCK barrier imply a DATA DEPENDENCY barrier? (It does on x86, and the compiler barrier implied by
spin_lock()suffices to replaceACCESS_ONCE()).
Well the dependency we care about is from loading the parent pointer to acquiring its spinlock. But we can't possibly have stale data given to the spin lock operation itself because it is a RMW.
Re: [patch 24/52] fs: dcache reduce d_parent locking
Quoted 7 lines · Nick Piggin
Quoted 6 lines · Peter Zijlstra
Quoted 4 lines · On Thu, 2010-06-24 at 13:02 +1000, [email protected] wrote:
Use RCU property of dcache to simplify locking in some places where we take
d_parentandd_lock.Comment: don't need
rcu_derefbecause we take the spinlock and recheck it.
Quoted 7 lines
Quoted 3 lines
But does the LOCK barrier imply a DATA DEPENDENCY barrier? (It does on x86, and the compiler barrier implied by
spin_lock()suffices to replaceACCESS_ONCE()).Well the dependency we care about is from loading the parent pointer to acquiring its spinlock. But we can't possibly have stale data given to the spin lock operation itself because it is a RMW.
As long as you check for the structure being valid after acquiring the lock, I agree. Otherwise, I would be concerned about the following sequence of events:
1. CPU 0 picks up a pointer to a given data element.
2. CPU 1 removes this element from the list, drops any locks that
it might have, and starts waiting for a grace period to elapse.
3. CPU 0 acquires the lock, does some operation that would
be appropriate had the element not been removed, then releases the lock.
4. After the grace period, CPU 1 frees the element, negating
CPU 0's hard work.
The usual approach is to have a "deleted" flag or some such in the element that CPU 0 would set when removing the element and that CPU 1 would check after acquiring the lock. Which you might well already be doing! ;-)
Thanx, Paul
Re: [patch 24/52] fs: dcache reduce d_parent locking
Quoted 30 lines · Paul E. McKenney
Quoted 7 lines · Nick Piggin
Quoted 6 lines · Peter Zijlstra
Quoted 4 lines · On Thu, 2010-06-24 at 13:02 +1000, [email protected] wrote:
Use RCU property of dcache to simplify locking in some places where we take
d_parentandd_lock.Comment: don't need
rcu_derefbecause we take the spinlock and recheck it.Quoted 7 lines
Quoted 3 lines
But does the LOCK barrier imply a DATA DEPENDENCY barrier? (It does on x86, and the compiler barrier implied by
spin_lock()suffices to replaceACCESS_ONCE()).Well the dependency we care about is from loading the parent pointer to acquiring its spinlock. But we can't possibly have stale data given to the spin lock operation itself because it is a RMW.
As long as you check for the structure being valid after acquiring the lock, I agree. Otherwise, I would be concerned about the following sequence of events:
1. CPU 0 picks up a pointer to a given data element.
2. CPU 1 removes this element from the list, drops any locks that
it might have, and starts waiting for a grace period to elapse.3. CPU 0 acquires the lock, does some operation that would
be appropriate had the element not been removed, then releases the lock.
Quoted 7 lines
4. After the grace period, CPU 1 frees the element, negating
CPU 0's hard work.The usual approach is to have a "deleted" flag or some such in the element that CPU 0 would set when removing the element and that CPU 1 would check after acquiring the lock. Which you might well already be doing! ;-)
Thanks, yep it's done under RCU, and after taking the lock it rechecks to see that it is still reachable by the same pointer (and if not, unlocks and retries) so it should be fine.
Thanks,
Nick
Re: [patch 24/52] fs: dcache reduce d_parent locking
Quoted 34 lines · Nick Piggin
Quoted 33 lines · Paul E. McKenney
Quoted 7 lines · Nick Piggin
Quoted 6 lines · Peter Zijlstra
Quoted 4 lines · On Thu, 2010-06-24 at 13:02 +1000, [email protected] wrote:
Use RCU property of dcache to simplify locking in some places where we take
d_parentandd_lock.Comment: don't need
rcu_derefbecause we take the spinlock and recheck it.Quoted 7 lines
Quoted 3 lines
But does the LOCK barrier imply a DATA DEPENDENCY barrier? (It does on x86, and the compiler barrier implied by
spin_lock()suffices to replaceACCESS_ONCE()).Well the dependency we care about is from loading the parent pointer to acquiring its spinlock. But we can't possibly have stale data given to the spin lock operation itself because it is a RMW.
As long as you check for the structure being valid after acquiring the lock, I agree. Otherwise, I would be concerned about the following sequence of events:
1. CPU 0 picks up a pointer to a given data element.
2. CPU 1 removes this element from the list, drops any locks that
it might have, and starts waiting for a grace period to elapse.3. CPU 0 acquires the lock, does some operation that would
be appropriate had the element not been removed, then releases the lock.4. After the grace period, CPU 1 frees the element, negating
CPU 0's hard work.
Quoted 8 lines
Quoted 4 lines
The usual approach is to have a "deleted" flag or some such in the element that CPU 0 would set when removing the element and that CPU 1 would check after acquiring the lock. Which you might well already be doing! ;-)
Thanks, yep it's done under RCU, and after taking the lock it rechecks to see that it is still reachable by the same pointer (and if not, unlocks and retries) so it should be fine.
Very good!!! ;-)
Thanx, Paul
Re: [patch 24/52] fs: dcache reduce d_parent locking
In reply to [email protected]Quoted 4 lines · On Thu, Jun 24, 2010 at 01:02:36PM +1000, [email protected] wrote:
Use RCU property of dcache to simplify locking in some places where we take
d_parentandd_lock.Comment: don't need
rcu_derefbecause we take the spinlock and recheck it.
Looks good other than one question below.
Thanx, Paul
Quoted 8 lines
Signed-off-by Nick Piggin <[email protected]>--
Index:
linux-2.6/fs/dcache.c
===================================================================
fs/dcache.c struct dentry *dget_parent(struct dentry · line 315
Quoted 6 lines
fs/dcache.c +1
-1@@ -311,23 +311,18 @@ struct dentry *dget_parent(struct dentrystruct dentry *ret; repeat: -spin_lock (&dentry->d_lock); +rcu_read_lock (); ret = dentry->d_parent;
Doesn't this need to be as follows?
ret = rcu_dereference(dentry)->d_parent;
Otherwise, couldn't we end up seeing pre-initialization value for ->d_parent for a newly inserted dentry?
fs/dcache.c struct dentry *dget_parent(struct dentry · line 316
Quoted 9 lines
fs/dcache.c +1
-8-if (!ret) - goto out; -if (dentry == ret) { - ret->d_count++; - goto out; - } -if (!spin_trylock (&ret->d_lock)) { -spin_unlock (&dentry->d_lock); +spin_lock (&ret->d_lock);
Once we do this, however, we are golden, at least for all dentry fields protected by ->lock. This does assume that the compiler does not speculate the fetch that initialized the argument dentry into the critical section, which I would sure hope would be a reasonable assumption.
Quoted 50 lines
fs/dcache.c +18
-9+if (unlikely (ret != dentry->d_parent)) { +spin_unlock (&ret->d_lock); +rcu_read_unlock (); goto repeat; } +rcu_read_unlock ();BUG_ON (!ret->d_count); ret->d_count++;spin_unlock (&ret->d_lock); -out: -spin_unlock (&dentry->d_lock);return ret; }EXPORT_SYMBOL (dget_parent);@@ -601,14 +596,22 @@ static void prune_one_dentry(struct dentif (inode)spin_lock (&inode->i_lock); again: -spin_lock (&dentry->d_lock); -if (dentry->d_parent && dentry != dentry->d_parent) { -if (!spin_trylock (&dentry->d_parent->d_lock)) { -spin_unlock (&dentry->d_lock); +rcu_read_lock (); + parent = dentry->d_parent; +if (parent) { +spin_lock (&parent->d_lock); +if (unlikely (parent != dentry->d_parent)) { +spin_unlock (&parent->d_lock); +rcu_read_unlock (); goto again; } - parent = dentry->d_parent; - } +if (parent != dentry) +spin_lock_nested (&dentry->d_lock,DENTRY_D_LOCK_NESTED ); +else + parent =NULL ; + }else +spin_lock (&dentry->d_lock); +rcu_read_unlock (); dentry->d_count--;if (dentry->d_count) {if (parent) --To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/
Re: [patch 24/52] fs: dcache reduce d_parent locking
Hi Paul,
Sorry I had left this in my postponed folder while rechecking your questions and forgot about it :P
Quoted 27 lines · Paul E. McKenney
Quoted 4 lines · On Thu, Jun 24, 2010 at 01:02:36PM +1000, [email protected] wrote:
Use RCU property of dcache to simplify locking in some places where we take
d_parentandd_lock.Comment: don't need
rcu_derefbecause we take the spinlock and recheck it.Looks good other than one question below.
Thanx, Paul
Quoted 8 lines
Signed-off-by Nick Piggin <[email protected]>--
Index:
linux-2.6/fs/dcache.c
===================================================================fs/dcache.c struct dentry *dget_parent(struct dentry · line 315
Quoted 6 lines
fs/dcache.c +1
-1@@ -311,23 +311,18 @@ struct dentry *dget_parent(struct dentrystruct dentry *ret; repeat: -spin_lock (&dentry->d_lock); +rcu_read_lock (); ret = dentry->d_parent;Doesn't this need to be as follows?
Quoted 4 lines
ret = rcu_dereference(dentry)->d_parent;Otherwise, couldn't we end up seeing pre-initialization value for ->d_parent for a newly inserted dentry?
I don't think so. The child's dentry memory should be guaranteed to be post-initialized at the point it is passed to dget_parent, becase we've to have a stable refcount on it at that point. Ie. if it was pulled from an RCU list, it should already have been rcu dereferenced by now.
So ->d_parent should be a valid pointer with lifetime guarantee provided by RCU -- so enough to take the spinlock and recheck.
Quoted 15 lines
fs/dcache.c struct dentry *dget_parent(struct dentry · line 316
Quoted 10 lines
fs/dcache.c +1
-8-if (!ret) - goto out; -if (dentry == ret) { - ret->d_count++; - goto out; - } -if (!spin_trylock (&ret->d_lock)) { -spin_unlock (&dentry->d_lock); +spin_lock (&ret->d_lock);Once we do this, however, we are golden, at least for all dentry fields protected by ->lock. This does assume that the compiler does not speculate the fetch that initialized the argument dentry into the critical section, which I would sure hope would be a reasonable assumption.
Yes I think the above fact that we have a "good" ref on the dentry should prevent this.
Thanks,
Nick
PATCH 25/52 fs: dcache DCACHE_REFERENCED improve
In reply to [email protected]dentry referenced bit is only set when installing the dentry back onto the LRU. However with lazy LRU, the dentry can already be on the LRU list at dput time, thus missing out on setting the referenced bit. Fix this.
fs/dcache.c +3 -3
@@ -387,10 +387,10 @@ repeat:/* Unreachable? Get rid of it */ if (d_unhashed (dentry)) goto kill_it; -if (list_empty (&dentry->d_lru)) { - dentry->d_flags |=DCACHE_REFERENCED ; +/* Otherwise leave it cached and ensure it's on the LRU */ +if (list_empty (&dentry->d_lru))dentry_lru_add (dentry); - } + dentry->d_flags |=DCACHE_REFERENCED ; dentry->d_count--;spin_unlock (&dentry->d_lock);return ;
PATCH 26/52 fs: icache lock s_inodes list
In reply to [email protected]Protect sb->s_inodes with a new lock, sb_inode_list_lock.
--- fs/drop_caches.c | 4 ++++ fs/fs-writeback.c | 4 ++++ fs/inode.c | 12 ++++++++++++ fs/notify/inode_mark.c | 2 ++ fs/notify/inotify/inotify.c | 2 ++ fs/quota/dquot.c | 6 ++++++ include/linux/writeback.h | 1 + 7 files changed, 31 insertions(+)
fs/drop_caches.c +4 -0
@@ -17,18 +17,22 @@ static void drop_pagecache_sb(struct supstruct inode *inode, *toput_inode =NULL ;spin_lock (&inode_lock); +spin_lock (&sb_inode_list_lock);list_for_each_entry (inode, &sb->s_inodes, i_sb_list) {if (inode->i_state & (I_FREEING |I_CLEAR |I_WILL_FREE |I_NEW ))continue ;if (inode->i_mapping->nrpages ==0 )continue ;__iget (inode); +spin_unlock (&sb_inode_list_lock);spin_unlock (&inode_lock);invalidate_mapping_pages (inode->i_mapping,0 ,-1 );iput (toput_inode); toput_inode = inode;spin_lock (&inode_lock); +spin_lock (&sb_inode_list_lock); } +spin_unlock (&sb_inode_list_lock);spin_unlock (&inode_lock);iput (toput_inode); }
fs/fs-writeback.c +4 -0
@@ -1166,6 +1166,7 @@ static void wait_sb_inodes(struct super_WARN_ON (!rwsem_is_locked (&sb->s_umount));spin_lock (&inode_lock); +spin_lock (&sb_inode_list_lock);/* * Data integrity sync. Must wait for all pages under writeback,
@@ -1183,6 +1184,7 @@ static void wait_sb_inodes(struct super_if (mapping->nrpages ==0 )continue ;__iget (inode); +spin_unlock (&sb_inode_list_lock);spin_unlock (&inode_lock);/* * We hold a reference to 'inode' so it couldn't have
@@ -1200,7 +1202,9 @@ static void wait_sb_inodes(struct super_cond_resched ();spin_lock (&inode_lock); +spin_lock (&sb_inode_list_lock); } +spin_unlock (&sb_inode_list_lock);spin_unlock (&inode_lock);iput (old_inode); }
fs/inode.c +21 -0
@@ -27,6 +27,15 @@#include <linux/posix_acl.h> /* +* Usage: +* sb_inode_list_lock protects: +* s_inodes, i_sb_list +* +* Ordering: +* inode_lock +* sb_inode_list_lock +*/ +/* * This is needed for the following functions: * - inode_has_buffers * - invalidate_inode_buffers
@@ -84,6 +93,7 @@ static struct hlist_head *inode_hashtabl* the i_state of an inode while it is in use.. */ DEFINE_SPINLOCK (inode_lock); +DEFINE_SPINLOCK (sb_inode_list_lock);/* * iprune_sem provides exclusion between the kswapd or try_to_free_pages
@@ -344,7 +354,9 @@ static void dispose_list(struct list_heaspin_lock (&inode_lock);hlist_del_init (&inode->i_hash); +spin_lock (&sb_inode_list_lock);list_del_init (&inode->i_sb_list); +spin_unlock (&sb_inode_list_lock);spin_unlock (&inode_lock);wake_up_inode (inode);
@@ -376,6 +388,7 @@ static int invalidate_list(struct list_h* shrink_icache_memory() away. */ cond_resched_lock (&inode_lock); +cond_resched_lock (&sb_inode_list_lock); next = next->next;if (tmp == head)
@@ -413,9 +426,11 @@ int invalidate_inodes(struct super_blockdown_write (&iprune_sem);spin_lock (&inode_lock); +spin_lock (&sb_inode_list_lock);inotify_unmount_inodes (&sb->s_inodes);fsnotify_unmount_inodes (&sb->s_inodes); busy =invalidate_list (&sb->s_inodes, &throw_away); +spin_unlock (&sb_inode_list_lock);spin_unlock (&inode_lock);dispose_list (&throw_away);
@@ -603,7 +618,9 @@ __inode_add_to_lists(struct super_block { inodes_stat.nr_inodes++;list_add (&inode->i_list, &inode_in_use); +spin_lock (&sb_inode_list_lock);list_add (&inode->i_sb_list, &sb->s_inodes); +spin_unlock (&sb_inode_list_lock);if (head)hlist_add_head (&inode->i_hash, head); }
@@ -1197,7 +1214,9 @@ void generic_delete_inode(struct inode *const struct super_operations *op = inode->i_sb->s_op;list_del_init (&inode->i_list); +spin_lock (&sb_inode_list_lock);list_del_init (&inode->i_sb_list); +spin_unlock (&sb_inode_list_lock);WARN_ON (inode->i_state &I_NEW ); inode->i_state |=I_FREEING ; inodes_stat.nr_inodes--;
@@ -1255,7 +1274,9 @@ int generic_detach_inode(struct inode *ihlist_del_init (&inode->i_hash); }list_del_init (&inode->i_list); +spin_lock (&sb_inode_list_lock);list_del_init (&inode->i_sb_list); +spin_unlock (&sb_inode_list_lock);WARN_ON (inode->i_state &I_NEW ); inode->i_state |=I_FREEING ; inodes_stat.nr_inodes--;
fs/notify/inotify/inotify.c +2 -0
@@ -429,6 +429,7 @@ void inotify_unmount_inodes(struct list_* will be added since the umount has begun. Finally, * iprune_mutex keeps shrink_icache_memory() away. */ +spin_unlock (&sb_inode_list_lock);spin_unlock (&inode_lock);if (need_iput_tmp)
@@ -451,6 +452,7 @@ void inotify_unmount_inodes(struct list_iput (inode);spin_lock (&inode_lock); +spin_lock (&sb_inode_list_lock); } }EXPORT_SYMBOL_GPL (inotify_unmount_inodes);
fs/quota/dquot.c +6 -0
@@ -884,6 +884,7 @@ static void add_dquot_ref(struct super_b#endif spin_lock (&inode_lock); +spin_lock (&sb_inode_list_lock);list_for_each_entry (inode, &sb->s_inodes, i_sb_list) {if (inode->i_state & (I_FREEING |I_CLEAR |I_WILL_FREE |I_NEW ))continue ;
@@ -897,6 +898,7 @@ static void add_dquot_ref(struct super_bcontinue ;__iget (inode); +spin_unlock (&sb_inode_list_lock);spin_unlock (&inode_lock);iput (old_inode);
@@ -908,7 +910,9 @@ static void add_dquot_ref(struct super_b* keep the reference and iput it later. */ old_inode = inode;spin_lock (&inode_lock); +spin_lock (&sb_inode_list_lock); } +spin_unlock (&sb_inode_list_lock);spin_unlock (&inode_lock);iput (old_inode);
@@ -988,6 +992,7 @@ static void remove_dquot_ref(struct supestruct inode *inode;spin_lock (&inode_lock); +spin_lock (&sb_inode_list_lock);list_for_each_entry (inode, &sb->s_inodes, i_sb_list) {/* * We have to scan also I_NEW inodes because they can already
@@ -998,6 +1003,7 @@ static void remove_dquot_ref(struct supeif (!IS_NOQUOTA (inode))remove_inode_dquot_ref (inode, type, tofree_head); } +spin_unlock (&sb_inode_list_lock);spin_unlock (&inode_lock); }
include/linux/writeback.h +1 -0
@@ -10,6 +10,7 @@struct backing_dev_info ;extern spinlock_t inode_lock; +extern spinlock_t sb_inode_list_lock;extern struct list_head inode_in_use;extern struct list_head inode_unused;
fs/notify/inode_mark.c +2 -0
@@ -408,6 +408,7 @@ void fsnotify_unmount_inodes(struct list* will be added since the umount has begun. Finally, * iprune_mutex keeps shrink_icache_memory() away. */ +spin_unlock (&sb_inode_list_lock);spin_unlock (&inode_lock);if (need_iput_tmp)
@@ -421,5 +422,6 @@ void fsnotify_unmount_inodes(struct listiput (inode);spin_lock (&inode_lock); +spin_lock (&sb_inode_list_lock); } }
PATCH 27/52 fs: icache lock inode hash
In reply to [email protected]Add a new lock, inode_hash_lock, to protect the inode hash table lists.
--- fs/inode.c | 29 ++++++++++++++++++++++++++++- include/linux/writeback.h | 1 + 2 files changed, 29 insertions(+), 1 deletion(-)
fs/inode.c +32 -1
@@ -30,10 +30,14 @@* Usage: * sb_inode_list_lock protects: * s_inodes, i_sb_list +* inode_hash_lock protects: +* inode hash table, i_hash * * Ordering: * inode_lock * sb_inode_list_lock +* inode_lock +* inode_hash_lock */ /* * This is needed for the following functions:
@@ -94,6 +98,7 @@ static struct hlist_head *inode_hashtabl*/ DEFINE_SPINLOCK (inode_lock);DEFINE_SPINLOCK (sb_inode_list_lock); +DEFINE_SPINLOCK (inode_hash_lock);/* * iprune_sem provides exclusion between the kswapd or try_to_free_pages
@@ -353,7 +358,9 @@ static void dispose_list(struct list_heaclear_inode (inode);spin_lock (&inode_lock); +spin_lock (&inode_hash_lock);hlist_del_init (&inode->i_hash); +spin_unlock (&inode_hash_lock);spin_lock (&sb_inode_list_lock);list_del_init (&inode->i_sb_list);spin_unlock (&sb_inode_list_lock);
@@ -563,17 +570,20 @@ static struct inode *find_inode(struct sstruct inode *inode =NULL ; repeat: +spin_lock (&inode_hash_lock);hlist_for_each_entry (inode, node, head, i_hash) {if (inode->i_sb != sb)continue ;if (!test (inode, data))continue ;if (inode->i_state & (I_FREEING |I_CLEAR |I_WILL_FREE )) { +spin_unlock (&inode_hash_lock);__wait_on_freeing_inode (inode); goto repeat; }break ; } +spin_unlock (&inode_hash_lock);return node ? inode :NULL ; }
@@ -588,17 +598,20 @@ static struct inode *find_inode_fast(strstruct inode *inode =NULL ; repeat: +spin_lock (&inode_hash_lock);hlist_for_each_entry (inode, node, head, i_hash) {if (inode->i_ino != ino)continue ;if (inode->i_sb != sb)continue ;if (inode->i_state & (I_FREEING |I_CLEAR |I_WILL_FREE )) { +spin_unlock (&inode_hash_lock);__wait_on_freeing_inode (inode); goto repeat; }break ; } +spin_unlock (&inode_hash_lock);return node ? inode :NULL ; }
@@ -621,8 +634,11 @@ __inode_add_to_lists(struct super_blockspin_lock (&sb_inode_list_lock);list_add (&inode->i_sb_list, &sb->s_inodes);spin_unlock (&sb_inode_list_lock); -if (head) +if (head) { +spin_lock (&inode_hash_lock);hlist_add_head (&inode->i_hash, head); +spin_unlock (&inode_hash_lock); + } }/**
@@ -1100,7 +1116,9 @@ int insert_inode_locked(struct inode *inwhile (1 ) {struct hlist_node *node;struct inode *old =NULL ; +spin_lock (&inode_lock); +spin_lock (&inode_hash_lock);hlist_for_each_entry (old, node, head, i_hash) {if (old->i_ino != ino)continue ;
@@ -1112,9 +1130,11 @@ int insert_inode_locked(struct inode *in }if (likely (!node)) {hlist_add_head (&inode->i_hash, head); +spin_unlock (&inode_hash_lock);spin_unlock (&inode_lock);return 0 ; } +spin_unlock (&inode_hash_lock);__iget (old);spin_unlock (&inode_lock);wait_on_inode (old);
@@ -1140,6 +1160,7 @@ int insert_inode_locked4(struct inode *istruct inode *old =NULL ;spin_lock (&inode_lock); +spin_lock (&inode_hash_lock);hlist_for_each_entry (old, node, head, i_hash) {if (old->i_sb != sb)continue ;
@@ -1151,9 +1172,11 @@ int insert_inode_locked4(struct inode *i }if (likely (!node)) {hlist_add_head (&inode->i_hash, head); +spin_unlock (&inode_hash_lock);spin_unlock (&inode_lock);return 0 ; } +spin_unlock (&inode_hash_lock);__iget (old);spin_unlock (&inode_lock);wait_on_inode (old);
@@ -1178,7 +1201,9 @@ void __insert_inode_hash(struct inode *i {struct hlist_head *head = inode_hashtable +hash (inode->i_sb, hashval);spin_lock (&inode_lock); +spin_lock (&inode_hash_lock);hlist_add_head (&inode->i_hash, head); +spin_unlock (&inode_hash_lock);spin_unlock (&inode_lock); }EXPORT_SYMBOL (__insert_inode_hash);
@@ -1192,7 +1217,9 @@ EXPORT_SYMBOL(__insert_inode_hash);void remove_inode_hash (struct inode *inode) {spin_lock (&inode_lock); +spin_lock (&inode_hash_lock);hlist_del_init (&inode->i_hash); +spin_unlock (&inode_hash_lock);spin_unlock (&inode_lock); }EXPORT_SYMBOL (remove_inode_hash);
@@ -1234,7 +1261,9 @@ void generic_delete_inode(struct inode *clear_inode (inode); }spin_lock (&inode_lock); +spin_lock (&inode_hash_lock);hlist_del_init (&inode->i_hash); +spin_unlock (&inode_hash_lock);spin_unlock (&inode_lock);wake_up_inode (inode);BUG_ON (inode->i_state !=I_CLEAR );
@@ -1271,7 +1300,9 @@ int generic_detach_inode(struct inode *iWARN_ON (inode->i_state &I_NEW ); inode->i_state &= ~I_WILL_FREE ; inodes_stat.nr_unused--; +spin_lock (&inode_hash_lock);hlist_del_init (&inode->i_hash); +spin_unlock (&inode_hash_lock); }list_del_init (&inode->i_list);spin_lock (&sb_inode_list_lock);
include/linux/writeback.h +1 -0
@@ -11,6 +11,7 @@ struct backing_dev_info;extern spinlock_t inode_lock;extern spinlock_t sb_inode_list_lock; +extern spinlock_t inode_hash_lock;extern struct list_head inode_in_use;extern struct list_head inode_unused;
PATCH 28/52 fs: icache lock i_state
In reply to [email protected]Protect i_state updates with i_lock
--- fs/drop_caches.c | 9 ++++-- fs/fs-writeback.c | 37 +++++++++++++++++++----- fs/inode.c | 81 ++++++++++++++++++++++++++++++++++++++++++++++-------- fs/nilfs2/gcdat.c | 1 fs/quota/dquot.c | 14 +++++++-- 5 files changed, 117 insertions(+), 25 deletions(-)
fs/drop_caches.c +6 -3
@@ -19,11 +19,14 @@ static void drop_pagecache_sb(struct supspin_lock (&inode_lock);spin_lock (&sb_inode_list_lock);list_for_each_entry (inode, &sb->s_inodes, i_sb_list) { -if (inode->i_state & (I_FREEING |I_CLEAR |I_WILL_FREE |I_NEW )) -continue ; -if (inode->i_mapping->nrpages ==0 ) +spin_lock (&inode->i_lock); +if (inode->i_state & (I_FREEING |I_CLEAR |I_WILL_FREE |I_NEW ) + || inode->i_mapping->nrpages ==0 ) { +spin_unlock (&inode->i_lock);continue ; + }__iget (inode); +spin_unlock (&inode->i_lock);spin_unlock (&sb_inode_list_lock);spin_unlock (&inode_lock);invalidate_mapping_pages (inode->i_mapping,0 ,-1 );
fs/fs-writeback.c +26 -4
@@ -398,10 +398,12 @@ static void inode_wait_for_writeback(strwait_queue_head_t *wqh; wqh =bit_waitqueue (&inode->i_state, __I_SYNC); -while (inode->i_state &I_SYNC ) { +while (inode->i_state &I_SYNC ) { +spin_unlock (&inode->i_lock);spin_unlock (&inode_lock);__wait_on_bit (wqh, &wq, inode_wait,TASK_UNINTERRUPTIBLE );spin_lock (&inode_lock); +spin_lock (&inode->i_lock); } }
@@ -455,6 +457,7 @@ writeback_single_inode(struct inode *ino/* Set I_SYNC, reset I_DIRTY_PAGES */ inode->i_state |=I_SYNC ; inode->i_state &= ~I_DIRTY_PAGES ; +spin_unlock (&inode->i_lock);spin_unlock (&inode_lock); ret =do_writepages (mapping, wbc);
@@ -476,8 +479,10 @@ writeback_single_inode(struct inode *ino* write_inode() */ spin_lock (&inode_lock); +spin_lock (&inode->i_lock); dirty = inode->i_state &I_DIRTY ; inode->i_state &= ~(I_DIRTY_SYNC |I_DIRTY_DATASYNC ); +spin_unlock (&inode->i_lock);spin_unlock (&inode_lock);/* Don't write the inode if only I_DIRTY_PAGES was set */ if (dirty & (I_DIRTY_SYNC |I_DIRTY_DATASYNC )) {
@@ -487,6 +492,7 @@ writeback_single_inode(struct inode *ino }spin_lock (&inode_lock); +spin_lock (&inode->i_lock); inode->i_state &= ~I_SYNC ;if (!(inode->i_state & (I_FREEING |I_CLEAR ))) {if ((inode->i_state &I_DIRTY_PAGES ) && wbc->for_kupdate) {
@@ -630,7 +636,9 @@ static int writeback_sb_inodes(struct suif (sb != inode->i_sb)/* finish with this superblock */ return 0 ; +spin_lock (&inode->i_lock);if (inode->i_state & (I_NEW |I_WILL_FREE )) { +spin_unlock (&inode->i_lock);requeue_io (inode);continue ; }
@@ -638,8 +646,10 @@ static int writeback_sb_inodes(struct su* Was this inode dirtied after sync_sb_inodes was called? * This keeps sync from extra jobs and livelock. */ -if (inode_dirtied_after (inode, wbc->wb_start)) +if (inode_dirtied_after (inode, wbc->wb_start)) { +spin_unlock (&inode->i_lock);return 1 ; + }BUG_ON (inode->i_state & (I_FREEING |I_CLEAR ));__iget (inode);
@@ -652,6 +662,7 @@ static int writeback_sb_inodes(struct su*/ redirty_tail (inode); } +spin_unlock (&inode->i_lock);spin_unlock (&inode_lock);iput (inode);cond_resched ();
@@ -1090,6 +1101,7 @@ void __mark_inode_dirty(struct inode *inblock_dump___mark_inode_dirty (inode);spin_lock (&inode_lock); +spin_lock (&inode->i_lock);if ((inode->i_state & flags) != flags) {const int was_dirty = inode->i_state &I_DIRTY ;
@@ -1134,6 +1146,7 @@ void __mark_inode_dirty(struct inode *in } } out: +spin_unlock (&inode->i_lock);spin_unlock (&inode_lock); }EXPORT_SYMBOL (__mark_inode_dirty);
@@ -1178,12 +1191,17 @@ static void wait_sb_inodes(struct super_list_for_each_entry (inode, &sb->s_inodes, i_sb_list) {struct address_space *mapping; -if (inode->i_state & (I_FREEING |I_CLEAR |I_WILL_FREE |I_NEW )) -continue ; mapping = inode->i_mapping;if (mapping->nrpages ==0 )continue ; + +spin_lock (&inode->i_lock); +if (inode->i_state & (I_FREEING |I_CLEAR |I_WILL_FREE |I_NEW )) { +spin_unlock (&inode->i_lock); +continue ; + }__iget (inode); +spin_unlock (&inode->i_lock);spin_unlock (&sb_inode_list_lock);spin_unlock (&inode_lock);/*
@@ -1287,7 +1305,9 @@ int write_inode_now(struct inode *inode,might_sleep ();spin_lock (&inode_lock); +spin_lock (&inode->i_lock); ret =writeback_single_inode (inode, &wbc); +spin_unlock (&inode->i_lock);spin_unlock (&inode_lock);if (sync)inode_sync_wait (inode);
@@ -1311,7 +1331,9 @@ int sync_inode(struct inode *inode, struint ret;spin_lock (&inode_lock); +spin_lock (&inode->i_lock); ret =writeback_single_inode (inode, wbc); +spin_unlock (&inode->i_lock);spin_unlock (&inode_lock);return ret; }
fs/inode.c +74 -11
322 lines not on this page · raw message
fs/nilfs2/gcdat.c +1 -0
@@ -27,6 +27,7 @@#include "page.h" #include "mdt.h" +/* XXX: what protects i_state? */ int nilfs_init_gcdat_inode (struct the_nilfs *nilfs) {struct inode *dat = nilfs->ns_dat, *gcdat = nilfs->ns_gc_dat;
fs/quota/dquot.c +11 -3
@@ -886,18 +886,26 @@ static void add_dquot_ref(struct super_bspin_lock (&inode_lock);spin_lock (&sb_inode_list_lock);list_for_each_entry (inode, &sb->s_inodes, i_sb_list) { -if (inode->i_state & (I_FREEING |I_CLEAR |I_WILL_FREE |I_NEW )) +spin_lock (&inode->i_lock); +if (inode->i_state & (I_FREEING |I_CLEAR |I_WILL_FREE |I_NEW )) { +spin_unlock (&inode->i_lock);continue ; + }#ifdef CONFIG_QUOTA_DEBUG if (unlikely (inode_get_rsv_space (inode) >0 )) reserved =1 ;#endif -if (!atomic_read (&inode->i_writecount)) +if (!atomic_read (&inode->i_writecount)) { +spin_unlock (&inode->i_lock);continue ; -if (!dqinit_needed (inode, type)) + } +if (!dqinit_needed (inode, type)) { +spin_unlock (&inode->i_lock);continue ; + }__iget (inode); +spin_unlock (&inode->i_lock);spin_unlock (&sb_inode_list_lock);spin_unlock (&inode_lock);
PATCH 29/52 fs: icache lock i_count
In reply to [email protected]Protect inode->i_count with i_lock, rather than having it atomic. Next step should also be to move things together (eg. the refcount increment into d_instantiate, which will remove a lock/unlock cycle on i_lock).
--- arch/powerpc/platforms/cell/spufs/file.c | 2 - fs/affs/inode.c | 4 ++- fs/afs/dir.c | 4 ++- fs/anon_inodes.c | 4 ++- fs/bfs/dir.c | 4 ++- fs/block_dev.c | 15 ++++++++++-- fs/btrfs/inode.c | 4 ++- fs/cifs/inode.c | 2 - fs/coda/dir.c | 4 ++- fs/exofs/inode.c | 12 +++++++--- fs/exofs/namei.c | 4 ++- fs/ext2/namei.c | 4 ++- fs/ext3/ialloc.c | 4 +-- fs/ext3/namei.c | 4 ++- fs/ext4/ialloc.c | 4 +-- fs/ext4/namei.c | 4 ++- fs/fs-writeback.c | 4 +-- fs/gfs2/ops_inode.c | 4 ++- fs/hfsplus/dir.c | 4 ++- fs/hpfs/inode.c | 2 - fs/inode.c | 37 ++++++++++++++++++++----------- fs/jffs2/dir.c | 8 +++++- fs/jfs/jfs_txnmgr.c | 4 ++- fs/jfs/namei.c | 4 ++- fs/libfs.c | 4 ++- fs/locks.c | 3 -- fs/minix/namei.c | 4 ++- fs/namei.c | 7 ++++- fs/nfs/dir.c | 4 ++- fs/nfs/getroot.c | 4 ++- fs/nfs/inode.c | 4 +-- fs/nilfs2/mdt.c | 2 - fs/nilfs2/namei.c | 4 ++- fs/notify/inode_mark.c | 22 +++++++++++------- fs/notify/inotify/inotify.c | 28 +++++++++++++---------- fs/ntfs/super.c | 4 ++- fs/ocfs2/namei.c | 4 ++- fs/reiserfs/file.c | 4 +-- fs/reiserfs/namei.c | 4 ++- fs/reiserfs/stree.c | 2 - fs/sysv/namei.c | 4 ++- fs/ubifs/dir.c | 4 ++- fs/ubifs/super.c | 2 - fs/udf/namei.c | 4 ++- fs/ufs/namei.c | 4 ++- fs/xfs/linux-2.6/xfs_iops.c | 4 ++- fs/xfs/xfs_iget.c | 2 - fs/xfs/xfs_inode.h | 6 +++-- include/linux/fs.h | 2 - ipc/mqueue.c | 7 ++++- kernel/futex.c | 4 ++- mm/shmem.c | 4 ++- 52 files changed, 201 insertions(+), 96 deletions(-)
arch/powerpc/platforms/cell/spufs/file.c +1 -1
@@ -1549,7 +1549,7 @@ static int spufs_mfc_open(struct inode *if (ctx->owner != current->mm)return -EINVAL ; -if (atomic_read (&inode->i_count) !=1 ) +if (inode->i_count !=1 )return -EBUSY ;mutex_lock (&ctx->mapping_lock);
fs/affs/inode.c +3 -1
@@ -380,7 +380,9 @@ affs_add_entry(struct inode *dir, structaffs_adjust_checksum (inode_bh, block -be32_to_cpu (chain));mark_buffer_dirty_inode (inode_bh, inode); inode->i_nlink =2 ; -atomic_inc (&inode->i_count); +spin_lock (&inode->i_lock); + inode->i_count++; +spin_unlock (&inode->i_lock); }affs_fix_checksum (sb, bh);mark_buffer_dirty_inode (bh, inode);
fs/afs/dir.c +3 -1
@@ -1002,7 +1002,9 @@ static int afs_link(struct dentry *from,if (ret <0 ) goto link_error; -atomic_inc (&vnode->vfs_inode.i_count); +spin_lock (&vnode->vfs_inode.i_lock); + vnode->vfs_inode.i_count++; +spin_unlock (&vnode->vfs_inode.i_lock);d_instantiate (dentry, &vnode->vfs_inode);key_put (key);_leave (" = 0" );
fs/anon_inodes.c +3 -1
@@ -114,7 +114,9 @@ struct file *anon_inode_getfile(const ch* so we can avoid doing an igrab() and we can use an open-coded * atomic_inc(). */ -atomic_inc (&anon_inode_inode->i_count); +spin_lock (&anon_inode_inode->i_lock); + anon_inode_inode->i_count++; +spin_unlock (&anon_inode_inode->i_lock); path.dentry->d_op = &anon_inodefs_dentry_operations;d_instantiate (path.dentry, anon_inode_inode);
fs/block_dev.c +12 -3
@@ -549,7 +549,12 @@ EXPORT_SYMBOL(bdget);*/ struct block_device *bdgrab (struct block_device *bdev) { -atomic_inc (&bdev->bd_inode->i_count); +struct inode *inode = bdev->bd_inode; + +spin_lock (&inode->i_lock); + inode->i_count++; +spin_unlock (&inode->i_lock); +return bdev; }
@@ -579,7 +584,9 @@ static struct block_device *bd_acquire(sspin_lock (&bdev_lock); bdev = inode->i_bdev;if (bdev) { -atomic_inc (&bdev->bd_inode->i_count); +spin_lock (&inode->i_lock); + bdev->bd_inode->i_count++; +spin_unlock (&inode->i_lock);spin_unlock (&bdev_lock);return bdev; }
@@ -595,7 +602,9 @@ static struct block_device *bd_acquire(s* So, we can access it via ->i_mapping always * without igrab(). */ -atomic_inc (&bdev->bd_inode->i_count); +spin_lock (&inode->i_lock); + bdev->bd_inode->i_count++; +spin_unlock (&inode->i_lock); inode->i_bdev = bdev; inode->i_mapping = bdev->bd_inode->i_mapping;list_add (&inode->i_devices, &bdev->bd_inodes);
fs/ext2/namei.c +3 -1
@@ -206,7 +206,9 @@ static int ext2_link (struct dentry * ol inode->i_ctime =CURRENT_TIME_SEC ;inode_inc_link_count (inode); -atomic_inc (&inode->i_count); +spin_lock (&inode->i_lock); + inode->i_count++; +spin_unlock (&inode->i_lock); err =ext2_add_link (dentry, inode);if (!err) {
fs/ext3/ialloc.c +2 -2
@@ -100,9 +100,9 @@ void ext3_free_inode (handle_t *handle,struct ext3_sb_info *sbi;int fatal =0 , err; -if (atomic_read (&inode->i_count) >1 ) { +if (inode->i_count >1 ) {printk ("ext3_free_inode: inode has count=%d\n" , -atomic_read (&inode->i_count)); + inode->i_count);return ; }if (inode->i_nlink) {
fs/ext3/namei.c +3 -1
@@ -2261,7 +2261,9 @@ retry: inode->i_ctime =CURRENT_TIME_SEC ;inc_nlink (inode); -atomic_inc (&inode->i_count); +spin_lock (&inode->i_lock); + inode->i_count++; +spin_unlock (&inode->i_lock); err =ext3_add_entry (handle, dentry, inode);if (!err) {
fs/fs-writeback.c +2 -2
@@ -427,7 +427,7 @@ writeback_single_inode(struct inode *inounsigned dirty;int ret; -if (!atomic_read (&inode->i_count)) +if (!inode->i_count)WARN_ON (!(inode->i_state & (I_WILL_FREE |I_FREEING )));else WARN_ON (inode->i_state &I_WILL_FREE );
@@ -551,7 +551,7 @@ select_queue: inode->i_state |=I_DIRTY_PAGES ;redirty_tail (inode); } - }else if (atomic_read (&inode->i_count)) { + }else if (inode->i_count) {/* * The inode is clean, inuse */
fs/inode.c +25 -13
@@ -33,14 +33,13 @@* inode_hash_lock protects: * inode hash table, i_hash * inode->i_lock protects: -* i_state +* i_state, i_count * * Ordering: * inode_lock * sb_inode_list_lock * inode->i_lock -* inode_lock -* inode_hash_lock +* inode_hash_lock */ /* * This is needed for the following functions:
@@ -151,7 +150,7 @@ int inode_init_always(struct super_block inode->i_sb = sb; inode->i_blkbits = sb->s_blocksize_bits; inode->i_flags =0 ; -atomic_set (&inode->i_count,1 ); + inode->i_count =1 ; inode->i_op = &empty_iops; inode->i_fop = &empty_fops; inode->i_nlink =1 ;
@@ -306,7 +305,8 @@ void __iget(struct inode *inode) {assert_spin_locked (&inode->i_lock); -if (atomic_inc_return (&inode->i_count) !=1 ) + inode->i_count++; +if (inode->i_count >1 )return ;if (!(inode->i_state & (I_DIRTY |I_SYNC )))
@@ -412,7 +412,7 @@ static int invalidate_list(struct list_hcontinue ; }invalidate_inode_buffers (inode); -if (!atomic_read (&inode->i_count)) { +if (!inode->i_count) {list_move (&inode->i_list, dispose);WARN_ON (inode->i_state &I_NEW ); inode->i_state |=I_FREEING ;
@@ -463,7 +463,7 @@ static int can_unuse(struct inode *inodereturn 0 ;if (inode_has_buffers (inode))return 0 ; -if (atomic_read (&inode->i_count)) +if (inode->i_count)return 0 ;if (inode->i_data.nrpages)return 0 ;
@@ -501,7 +501,7 @@ static void prune_icache(int nr_to_scan) inode = list_entry(inode_unused.prev, struct inode, i_list);spin_lock (&inode->i_lock); -if (inode->i_state ||atomic_read (&inode->i_count)) { +if (inode->i_state || inode->i_count) {list_move (&inode->i_list, &inode_unused);spin_unlock (&inode->i_lock);continue ;
@@ -1290,8 +1290,6 @@ void generic_delete_inode(struct inode * {const struct super_operations *op = inode->i_sb->s_op; -spin_lock (&sb_inode_list_lock); -spin_lock (&inode->i_lock);list_del_init (&inode->i_list);list_del_init (&inode->i_sb_list);spin_unlock (&sb_inode_list_lock);
@@ -1336,8 +1334,6 @@ int generic_detach_inode(struct inode *i {struct super_block *sb = inode->i_sb; -spin_lock (&sb_inode_list_lock); -spin_lock (&inode->i_lock);if (!hlist_unhashed (&inode->i_hash)) {if (!(inode->i_state & (I_DIRTY |I_SYNC )))list_move (&inode->i_list, &inode_unused);
@@ -1436,8 +1432,24 @@ void iput(struct inode *inode)if (inode) {BUG_ON (inode->i_state ==I_CLEAR ); -if (atomic_dec_and_lock (&inode->i_count, &inode_lock)) +retry: +spin_lock (&inode->i_lock); +if (inode->i_count ==1 ) { +if (!spin_trylock (&inode_lock)) { +spin_unlock (&inode->i_lock); + goto retry; + } +if (!spin_trylock (&sb_inode_list_lock)) { +spin_unlock (&inode_lock); +spin_unlock (&inode->i_lock); + goto retry; + } + inode->i_count--;iput_final (inode); + }else { + inode->i_count--; +spin_unlock (&inode->i_lock); + } } }EXPORT_SYMBOL (iput);
fs/libfs.c +3 -1
@@ -277,7 +277,9 @@ int simple_link(struct dentry *old_dentr inode->i_ctime = dir->i_ctime = dir->i_mtime =CURRENT_TIME ;inc_nlink (inode); -atomic_inc (&inode->i_count); +spin_lock (&inode->i_lock); + inode->i_count++; +spin_unlock (&inode->i_lock);dget (dentry);d_instantiate (dentry, inode);return 0 ;
fs/locks.c +1 -2
@@ -1375,8 +1375,7 @@ int generic_setlease(struct file *filp,if ((arg ==F_RDLCK ) && (atomic_read (&inode->i_writecount) >0 )) goto out;if ((arg ==F_WRLCK ) - && (dentry->d_count >1 - || (atomic_read (&inode->i_count) >1 ))) + && (dentry->d_count >1 || inode->i_count >1 )) goto out; }
fs/namei.c +5 -2
@@ -2306,8 +2306,11 @@ static long do_unlinkat(int dfd, const cif (nd.last.name[nd.last.len]) goto slashes; inode = dentry->d_inode; -if (inode) -atomic_inc (&inode->i_count); +if (inode) { +spin_lock (&inode->i_lock); + inode->i_count++; +spin_unlock (&inode->i_lock); + } error =mnt_want_write (nd.path.mnt);if (error) goto exit2;
fs/nfs/dir.c +3 -1
@@ -1570,7 +1570,9 @@ nfs_link(struct dentry *old_dentry, strud_drop (dentry); error =NFS_PROTO (dir)->link (inode, dir, &dentry->d_name);if (error ==0 ) { -atomic_inc (&inode->i_count); +spin_lock (&inode->i_lock); + inode->i_count++; +spin_unlock (&inode->i_lock);d_add (dentry, inode); }return error;
fs/nfs/getroot.c +3 -1
@@ -55,7 +55,9 @@ static int nfs_superblock_set_dummy_rootreturn -ENOMEM ; }/* Circumvent igrab(): we know the inode is not being freed */ -atomic_inc (&inode->i_count); +spin_lock (&inode->i_lock); + inode->i_count++; +spin_unlock (&inode->i_lock);/* * Ensure that this dentry is invisible to d_find_alias(). * Otherwise, it may be spliced into the tree by
fs/notify/inotify/inotify.c +16 -12
@@ -404,23 +404,28 @@ void inotify_unmount_inodes(struct list_* evict all inodes with zero i_count from icache which is * unnecessarily violent and may in fact be illegal to do. */ -if (!atomic_read (&inode->i_count)) +if (!inode->i_count)continue ; need_iput_tmp = need_iput; need_iput =NULL ;/* In case inotify_remove_watch_locked() drops a reference. */ -if (inode != need_iput_tmp) +if (inode != need_iput_tmp) { +spin_lock (&inode->i_lock);__iget (inode); -else +spin_unlock (&inode->i_lock); + }else need_iput_tmp =NULL ;/* In case the dropping of a reference would nuke next_i. */ -if ((&next_i->i_sb_list != list) && -atomic_read (&next_i->i_count) && - !(next_i->i_state & (I_CLEAR |I_FREEING | -I_WILL_FREE ))) { -__iget (next_i); - need_iput = next_i; +if (&next_i->i_sb_list != list) { +spin_lock (&next_i->i_lock); +if (next_i->i_count && + !(next_i->i_state & + (I_CLEAR |I_FREEING |I_WILL_FREE ))) { +__iget (next_i); + need_iput = next_i; + } +spin_unlock (&next_i->i_lock); }/*
@@ -439,11 +444,10 @@ void inotify_unmount_inodes(struct list_mutex_lock (&inode->inotify_mutex); watches = &inode->inotify_watches;list_for_each_entry_safe (watch, next_w, watches, i_list) { -struct inotify_handle *ih= watch->ih; +struct inotify_handle *ih = watch->ih;get_inotify_watch (watch);mutex_lock (&ih->mutex); - ih->in_ops->handle_event (watch, watch->wd,IN_UNMOUNT ,0 , -NULL ,NULL ); + ih->in_ops->handle_event (watch, watch->wd,IN_UNMOUNT ,0 ,NULL ,NULL );inotify_remove_watch_locked (ih, watch);mutex_unlock (&ih->mutex);put_inotify_watch (watch);
fs/xfs/linux-2.6/xfs_iops.c +3 -1
@@ -360,7 +360,9 @@ xfs_vn_link(if (unlikely (error))return -error; -atomic_inc (&inode->i_count); +spin_lock (&inode->i_lock); + inode->i_count++; +spin_unlock (&inode->i_lock);d_instantiate (dentry, inode);return 0 ; }
fs/xfs/xfs_inode.h +4 -2
@@ -483,8 +483,10 @@ void xfs_mark_inode_dirty_sync(xfs_inod#define IHOLD (ip) \ do { \ - ASSERT(atomic_read(&VFS_I(ip)->i_count) > 0) ; \ - atomic_inc(&(VFS_I(ip)->i_count)); \ + spin_lock(&VFS_I(ip)->i_lock); \ + ASSERT(VFS_I(ip)->i_count > 0) ; \ + VFS_I(ip)->i_count++; \ + spin_unlock(&VFS_I(ip)->i_lock); \ trace_xfs_ihold(ip, _THIS_IP_); \ } while (0)
include/linux/fs.h +1 -1
@@ -729,7 +729,7 @@ struct inode {struct list_head i_sb_list;struct list_head i_dentry;unsigned long i_ino; -atomic_t i_count; +unsigned int i_count;unsigned int i_nlink;uid_t i_uid;gid_t i_gid;
ipc/mqueue.c +5 -2
@@ -769,8 +769,11 @@ SYSCALL_DEFINE1(mq_unlink, const char __ } inode = dentry->d_inode; -if (inode) -atomic_inc (&inode->i_count); +if (inode) { +spin_lock (&inode->i_lock); + inode->i_count++; +spin_unlock (&inode->i_lock); + } err =mnt_want_write (ipc_ns->mq_mnt);if (err) goto out_err;
kernel/futex.c +3 -1
@@ -168,7 +168,9 @@ static void get_futex_key_refs(union futswitch (key->both.offset & (FUT_OFF_INODE |FUT_OFF_MMSHARED )) {case FUT_OFF_INODE : -atomic_inc (&key->shared.inode->i_count); +spin_lock (&key->shared.inode->i_lock); + key->shared.inode->i_count++; +spin_unlock (&key->shared.inode->i_lock);break ;case FUT_OFF_MMSHARED :atomic_inc (&key->private.mm->mm_count);
mm/shmem.c +3 -1
@@ -1877,7 +1877,9 @@ static int shmem_link(struct dentry *old dir->i_size +=BOGO_DIRENT_SIZE ; inode->i_ctime = dir->i_ctime = dir->i_mtime =CURRENT_TIME ;inc_nlink (inode); -atomic_inc (&inode->i_count);/* New dentry reference */ +spin_lock (&inode->i_lock); + inode->i_count++;/* New dentry reference */ +spin_unlock (&inode->i_lock);dget (dentry);/* Extra pinning count for the created dentry */ d_instantiate (dentry, inode); out:
fs/bfs/dir.c +3 -1
@@ -176,7 +176,9 @@ static int bfs_link(struct dentry *old,inc_nlink (inode); inode->i_ctime =CURRENT_TIME_SEC ;mark_inode_dirty (inode); -atomic_inc (&inode->i_count); +spin_lock (&inode->i_lock); + inode->i_count++; +spin_unlock (&inode->i_lock);d_instantiate (new, inode);mutex_unlock (&info->bfs_lock);return 0 ;
fs/btrfs/inode.c +3 -1
@@ -4753,7 +4753,9 @@ static int btrfs_link(struct dentry *old }btrfs_set_trans_block_group (trans, dir); -atomic_inc (&inode->i_count); +spin_lock (&inode->i_lock); + inode->i_count++; +spin_unlock (&inode->i_lock); err =btrfs_add_nondir (trans, dentry, inode,1 , index);
fs/coda/dir.c +3 -1
@@ -303,7 +303,9 @@ static int coda_link(struct dentry *sour }coda_dir_update_mtime (dir_inode); -atomic_inc (&inode->i_count); +spin_lock (&inode->i_lock); + inode->i_count++; +spin_unlock (&inode->i_lock);d_instantiate (de, inode);inc_nlink (inode);
fs/exofs/inode.c +9 -3
@@ -1124,7 +1124,9 @@ static void create_done(struct exofs_io_set_obj_created (oi); -atomic_dec (&inode->i_count); +spin_lock (&inode->i_lock); + inode->i_count--; +spin_unlock (&inode->i_lock);wake_up (&oi->i_wq); }
@@ -1177,14 +1179,18 @@ struct inode *exofs_new_inode(struct ino/* increment the refcount so that the inode will still be around when we * reach the callback */ -atomic_inc (&inode->i_count); +spin_lock (&inode->i_lock); + inode->i_count++; +spin_unlock (&inode->i_lock); ios->done = create_done; ios->private = inode; ios->cred = oi->i_cred; ret =exofs_sbi_create (ios);if (ret) { -atomic_dec (&inode->i_count); +spin_lock (&inode->i_lock); + inode->i_count--; +spin_unlock (&inode->i_lock);exofs_put_io_state (ios);return ERR_PTR (ret); }
fs/exofs/namei.c +3 -1
@@ -153,7 +153,9 @@ static int exofs_link(struct dentry *old inode->i_ctime =CURRENT_TIME ;inode_inc_link_count (inode); -atomic_inc (&inode->i_count); +spin_lock (&inode->i_lock); + inode->i_count++; +spin_unlock (&inode->i_lock);return exofs_add_nondir (dentry, inode); }
fs/ext4/ialloc.c +2 -2
@@ -189,9 +189,9 @@ void ext4_free_inode(handle_t *handle, sstruct ext4_sb_info *sbi;int fatal =0 , err, count, cleared; -if (atomic_read (&inode->i_count) >1 ) { +if (inode->i_count >1 ) {printk (KERN_ERR "ext4_free_inode: inode has count=%d\n" , -atomic_read (&inode->i_count)); + inode->i_count);return ; }if (inode->i_nlink) {
fs/ext4/namei.c +3 -1
@@ -2340,7 +2340,9 @@ retry: inode->i_ctime =ext4_current_time (inode);ext4_inc_count (handle, inode); -atomic_inc (&inode->i_count); +spin_lock (&inode->i_lock); + inode->i_count++; +spin_unlock (&inode->i_lock); err =ext4_add_entry (handle, dentry, inode);if (!err) {
fs/gfs2/ops_inode.c +3 -1
@@ -253,7 +253,9 @@ out_parent:gfs2_holder_uninit (ghs);gfs2_holder_uninit (ghs +1 );if (!error) { -atomic_inc (&inode->i_count); +spin_lock (&inode->i_lock); + inode->i_count++; +spin_unlock (&inode->i_lock);d_instantiate (dentry, inode);mark_inode_dirty (inode); }
fs/hfsplus/dir.c +3 -1
@@ -301,7 +301,9 @@ static int hfsplus_link(struct dentry *sinc_nlink (inode);hfsplus_instantiate (dst_dentry, inode, cnid); -atomic_inc (&inode->i_count); +spin_lock (&inode->i_lock); + inode->i_count++; +spin_unlock (&inode->i_lock); inode->i_ctime =CURRENT_TIME_SEC ;mark_inode_dirty (inode);HFSPLUS_SB (sb).file_count++;
fs/hpfs/inode.c +1 -1
@@ -183,7 +183,7 @@ void hpfs_write_inode(struct inode *i)struct hpfs_inode_info *hpfs_inode =hpfs_i (i);struct inode *parent;if (i->i_ino ==hpfs_sb (i->i_sb)->sb_root)return ; -if (hpfs_inode->i_rddir_off && !atomic_read (&i->i_count)) { +if (hpfs_inode->i_rddir_off && !i->i_count) {if (*hpfs_inode->i_rddir_off)printk ("HPFS: write_inode: some position still there\n" );kfree (hpfs_inode->i_rddir_off); hpfs_inode->i_rddir_off =NULL ;
fs/jffs2/dir.c +6 -2
@@ -290,7 +290,9 @@ static int jffs2_link (struct dentry *olmutex_unlock (&f->sem);d_instantiate (dentry, old_dentry->d_inode); dir_i->i_mtime = dir_i->i_ctime =ITIME (now); -atomic_inc (&old_dentry->d_inode->i_count); +spin_lock (&old_dentry->d_inode->i_lock); + old_dentry->d_inode->i_count++; +spin_unlock (&old_dentry->d_inode->i_lock); }return ret; }
@@ -871,7 +873,9 @@ static int jffs2_rename (struct inode *oprintk (KERN_NOTICE "jffs2_rename(): Link succeeded, unlink failed (err %d). You now have a hard link\n" , ret);/* Might as well let the VFS know */ d_instantiate (new_dentry, old_dentry->d_inode); -atomic_inc (&old_dentry->d_inode->i_count); +spin_lock (&old_dentry->d_inode->i_lock); + old_dentry->d_inode->i_count++; +spin_unlock (&old_dentry->d_inode->i_lock); new_dir_i->i_mtime = new_dir_i->i_ctime =ITIME (now);return ret; }
fs/jfs/jfs_txnmgr.c +3 -1
@@ -1279,7 +1279,9 @@ int txCommit(tid_t tid, /* transaction* lazy commit thread finishes processing */ if (tblk->xflag &COMMIT_DELETE ) { -atomic_inc (&tblk->u.ip->i_count); +spin_lock (&tblk->u.ip->i_lock); + tblk->u.ip->i_count++; +spin_unlock (&tblk->u.ip->i_lock);/* * Avoid a rare deadlock *
fs/jfs/namei.c +3 -1
@@ -839,7 +839,9 @@ static int jfs_link(struct dentry *old_d ip->i_ctime =CURRENT_TIME ; dir->i_ctime = dir->i_mtime =CURRENT_TIME ;mark_inode_dirty (dir); -atomic_inc (&ip->i_count); +spin_lock (&ip->i_lock); + ip->i_count++; +spin_unlock (&ip->i_lock); iplist[0 ] = ip; iplist[1 ] = dir;
fs/minix/namei.c +3 -1
@@ -101,7 +101,9 @@ static int minix_link(struct dentry * ol inode->i_ctime =CURRENT_TIME_SEC ;inode_inc_link_count (inode); -atomic_inc (&inode->i_count); +spin_lock (&inode->i_lock); + inode->i_count++; +spin_unlock (&inode->i_lock);return add_nondir (dentry, inode); }
fs/nfs/inode.c +2 -2
@@ -377,7 +377,7 @@ nfs_fhget(struct super_block *sb, structdprintk ("NFS: nfs_fhget(%s/%Ld ct=%d)\n" , inode->i_sb->s_id, (long long )NFS_FILEID (inode), -atomic_read (&inode->i_count)); + inode->i_count); out:return inode;
@@ -1123,7 +1123,7 @@ static int nfs_update_inode(struct inodedfprintk (VFS ,"NFS: %s(%s/%ld ct=%d info=0x%x)\n" , __func__, inode->i_sb->s_id, inode->i_ino, -atomic_read (&inode->i_count), fattr->valid); + inode->i_count, fattr->valid);if ((fattr->valid &NFS_ATTR_FATTR_FILEID ) && nfsi->fileid != fattr->fileid) goto out_fileid;
fs/nilfs2/mdt.c +1 -1
@@ -479,7 +479,7 @@ nilfs_mdt_new_common(struct the_nilfs *n inode->i_sb = sb;/* sb may be NULL for some meta data files */ inode->i_blkbits = nilfs->ns_blocksize_bits; inode->i_flags =0 ; -atomic_set (&inode->i_count,1 ); + inode->i_count =1 ; inode->i_nlink =1 ; inode->i_ino = ino; inode->i_mode =S_IFREG ;
fs/nilfs2/namei.c +3 -1
@@ -219,7 +219,9 @@ static int nilfs_link(struct dentry *old inode->i_ctime =CURRENT_TIME ;inode_inc_link_count (inode); -atomic_inc (&inode->i_count); +spin_lock (&inode->i_lock); + inode->i_count++; +spin_unlock (&inode->i_lock); err =nilfs_add_nondir (dentry, inode);if (!err)
fs/ocfs2/namei.c +3 -1
@@ -722,7 +722,9 @@ static int ocfs2_link(struct dentry *old goto out_commit; } -atomic_inc (&inode->i_count); +spin_lock (&inode->i_lock); + inode->i_count++; +spin_unlock (&inode->i_lock); dentry->d_op = &ocfs2_dentry_ops;d_instantiate (dentry, inode);
fs/reiserfs/file.c +2 -2
@@ -39,7 +39,7 @@ static int reiserfs_file_release(structBUG_ON (!S_ISREG (inode->i_mode));/* fast out for when nothing needs to be done */ -if ((atomic_read (&inode->i_count) >1 || +if ((inode->i_count >1 || !(REISERFS_I (inode)->i_flags & i_pack_on_close_mask) || !tail_has_to_be_packed (inode)) &&REISERFS_I (inode)->i_prealloc_count <=0 ) {
@@ -94,7 +94,7 @@ static int reiserfs_file_release(structif (!err) err = jbegin_failure; -if (!err &&atomic_read (&inode->i_count) <=1 && +if (!err && inode->i_count <=1 && (REISERFS_I (inode)->i_flags & i_pack_on_close_mask) &&tail_has_to_be_packed (inode)) {/* if regular file is released by last holder and it has been
fs/reiserfs/namei.c +3 -1
@@ -1156,7 +1156,9 @@ static int reiserfs_link(struct dentry * inode->i_ctime =CURRENT_TIME_SEC ;reiserfs_update_sd (&th, inode); -atomic_inc (&inode->i_count); +spin_lock (&inode->i_lock); + inode->i_count++; +spin_unlock (&inode->i_lock);d_instantiate (dentry, inode); retval =journal_end (&th, dir->i_sb, jbegin_count);reiserfs_write_unlock (dir->i_sb);
fs/reiserfs/stree.c +1 -1
@@ -1477,7 +1477,7 @@ static int maybe_indirect_to_direct(stru** reading in the last block. The user will hit problems trying to ** read the file, but for now we just skip the indirect2direct */ -if (atomic_read (&inode->i_count) >1 || +if (inode->i_count >1 || !tail_has_to_be_packed (inode) || !page || (REISERFS_I (inode)->i_flags & i_nopack_mask)) {/* leave tail in an unformatted node */
fs/sysv/namei.c +3 -1
@@ -126,7 +126,9 @@ static int sysv_link(struct dentry * old inode->i_ctime =CURRENT_TIME_SEC ;inode_inc_link_count (inode); -atomic_inc (&inode->i_count); +spin_lock (&inode->i_lock); + inode->i_count++; +spin_unlock (&inode->i_lock);return add_nondir (dentry, inode); }
fs/ubifs/dir.c +3 -1
@@ -550,7 +550,9 @@ static int ubifs_link(struct dentry *oldlock_2_inodes (dir, inode);inc_nlink (inode); -atomic_inc (&inode->i_count); +spin_lock (&inode->i_lock); + inode->i_count++; +spin_unlock (&inode->i_lock); inode->i_ctime =ubifs_current_time (inode); dir->i_size += sz_change; dir_ui->ui_size = dir->i_size;
fs/ubifs/super.c +1 -1
@@ -342,7 +342,7 @@ static void ubifs_delete_inode(struct in goto out;dbg_gen ("inode %lu, mode %#x" , inode->i_ino, (int )inode->i_mode); -ubifs_assert (!atomic_read (&inode->i_count)); +ubifs_assert (!inode->i_count);ubifs_assert (inode->i_nlink ==0 );truncate_inode_pages (&inode->i_data,0 );
fs/udf/namei.c +3 -1
@@ -1101,7 +1101,9 @@ static int udf_link(struct dentry *old_dinc_nlink (inode); inode->i_ctime =current_fs_time (inode->i_sb);mark_inode_dirty (inode); -atomic_inc (&inode->i_count); +spin_lock (&inode->i_lock); + inode->i_count++; +spin_unlock (&inode->i_lock);d_instantiate (dentry, inode);unlock_kernel ();
fs/ufs/namei.c +3 -1
@@ -180,7 +180,9 @@ static int ufs_link (struct dentry * old inode->i_ctime =CURRENT_TIME_SEC ;inode_inc_link_count (inode); -atomic_inc (&inode->i_count); +spin_lock (&inode->i_lock); + inode->i_count++; +spin_unlock (&inode->i_lock); error =ufs_add_nondir (dentry, inode);unlock_kernel ();
fs/notify/inode_mark.c +14 -8
@@ -382,24 +382,30 @@ void fsnotify_unmount_inodes(struct list* evict all inodes with zero i_count from icache which is * unnecessarily violent and may in fact be illegal to do. */ -if (!atomic_read (&inode->i_count)) +if (!inode->i_count)continue ; need_iput_tmp = need_iput; need_iput =NULL ;/* In case fsnotify_inode_delete() drops a reference. */ -if (inode != need_iput_tmp) +if (inode != need_iput_tmp) { +spin_lock (&inode->i_lock);__iget (inode); -else +spin_unlock (&inode->i_lock); + }else need_iput_tmp =NULL ;/* In case the dropping of a reference would nuke next_i. */ -if ((&next_i->i_sb_list != list) && -atomic_read (&next_i->i_count) && - !(next_i->i_state & (I_CLEAR |I_FREEING |I_WILL_FREE ))) { -__iget (next_i); - need_iput = next_i; +if (&next_i->i_sb_list != list) { +spin_lock (&next_i->i_lock); +if (next_i->i_count && + !(next_i->i_state & + (I_CLEAR |I_FREEING |I_WILL_FREE ))) { +__iget (next_i); + need_iput = next_i; + } +spin_unlock (&next_i->i_lock); }/*
fs/ntfs/super.c +3 -1
@@ -2930,7 +2930,9 @@ static int ntfs_fill_super(struct super_ }if ((sb->s_root =d_alloc_root (vol->root_ino))) {/* We increment i_count simulating an ntfs_iget(). */ -atomic_inc (&vol->root_ino->i_count); +spin_lock (&vol->root_ino->i_lock); + vol->root_ino->i_count++; +spin_unlock (&vol->root_ino->i_lock);ntfs_debug ("Exiting, status successful." );/* Release the default upcase if it has no users. */ mutex_lock (&ntfs_lock);
fs/cifs/inode.c +1 -1
@@ -1612,7 +1612,7 @@ int cifs_revalidate_dentry(struct dentry }cFYI (1 ,"Revalidate: %s inode 0x%p count %d dentry: 0x%p d_time %ld " -"jiffies %ld" , full_path, inode, inode->i_count.counter, +"jiffies %ld" , full_path, inode, inode->i_count, dentry, dentry->d_time, jiffies);if (CIFS_SB (sb)->tcon->unix_ext)
fs/xfs/linux-2.6/xfs_trace.h +1 -1
@@ -576,7 +576,7 @@ DECLARE_EVENT_CLASS(xfs_inode_class,TP_fast_assign ( __entry->dev = VFS_I(ip)->i_sb->s_dev; __entry->ino = ip->i_ino; - __entry->count =atomic_read (&VFS_I (ip)->i_count); + __entry->count =VFS_I (ip)->i_count; __entry->pincount =atomic_read (&ip->i_pincount); __entry->caller_ip = caller_ip; ),
net/socket.c +3 -1
@@ -386,7 +386,9 @@ static int sock_alloc_file(struct socket &socket_file_ops);if (unlikely (!file)) {/* drop dentry, keep inode */ -atomic_inc (&path.dentry->d_inode->i_count); +spin_lock (&path.dentry->d_inode->i_lock); + path.dentry->d_inode->i_count++; +spin_unlock (&path.dentry->d_inode->i_lock);path_put (&path);put_unused_fd (fd);return -ENFILE ;
Re: [patch 29/52] fs: icache lock i_count
Quoted 3 lines · On Thu, Jun 24, 2010 at 01:02:41PM +1000, [email protected] wrote:
Protect
inode->i_countwithi_lock, rather than having it atomic. Next step should also be to move things together (eg. the refcount increment intod_instantiate, which will remove a lock/unlock cycle oni_lock).
.....
Quoted 9 lines
Index:
linux-2.6/fs/inode.c
===================================================================fs/inode.c +0
-1@@ -33,14 +33,13 @@* inode_hash_lock protects: * inode hash table, i_hash * inode->i_lock protects: -* i_state
fs/inode.c line 43
Quoted 10 lines
fs/inode.c +2
-2+* i_state, i_count * * Ordering: * inode_lock * sb_inode_list_lock * inode->i_lock -* inode_lock -* inode_hash_lock +* inode_hash_lock */
I thought that the rule governing the use of inode->i_lock was that it can be used anywhere as long as it is the innermost lock.
Hmmm, no references in the code or documentation. Google gives a pretty good reference:
http://www.mail-archive.com/[email protected]/msg02584.html
Perhaps a different/new lock needs to be used here?
Cheers,
Dave.
Signature · 2 lines
Dave Chinner [email protected]
Re: [patch 29/52] fs: icache lock i_count
Quoted 28 lines · Dave Chinner
Quoted 3 lines · On Thu, Jun 24, 2010 at 01:02:41PM +1000, [email protected] wrote:
Protect
inode->i_countwithi_lock, rather than having it atomic. Next step should also be to move things together (eg. the refcount increment intod_instantiate, which will remove a lock/unlock cycle oni_lock)......
Quoted 9 lines
Index:
linux-2.6/fs/inode.c
===================================================================fs/inode.c +0
-1@@ -33,14 +33,13 @@* inode_hash_lock protects: * inode hash table, i_hash * inode->i_lock protects: -* i_state fs/inode.c line 43
Quoted 10 lines
fs/inode.c +2
-2+* i_state, i_count * * Ordering: * inode_lock * sb_inode_list_lock * inode->i_lock -* inode_lock -* inode_hash_lock +* inode_hash_lock */ I thought that the rule governing the use of
inode->i_lockwas that it can be used anywhere as long as it is the innermost lock.
Quoted 6 lines
Hmmm, no references in the code or documentation. Google gives a pretty good reference:
http://www.mail-archive.com/[email protected]/msg02584.html
Perhaps a different/new lock needs to be used here?
Well I just changed the order (and documented it to boot :)). It's pretty easy to verify that LOR is no problem. inode hash is only taken in a very few places so other code outside inode.c is fine to use i_lock as an innermost lock.
Re: [patch 29/52] fs: icache lock i_count
Quoted 32 lines · Nick Piggin
Quoted 31 lines · Dave Chinner
Quoted 3 lines · On Thu, Jun 24, 2010 at 01:02:41PM +1000, [email protected] wrote:
Protect
inode->i_countwithi_lock, rather than having it atomic. Next step should also be to move things together (eg. the refcount increment intod_instantiate, which will remove a lock/unlock cycle oni_lock)......
Quoted 9 lines
Index:
linux-2.6/fs/inode.c
===================================================================fs/inode.c +0
-1@@ -33,14 +33,13 @@* inode_hash_lock protects: * inode hash table, i_hash * inode->i_lock protects: -* i_state fs/inode.c line 43
Quoted 10 lines
fs/inode.c +2
-2+* i_state, i_count * * Ordering: * inode_lock * sb_inode_list_lock * inode->i_lock -* inode_lock -* inode_hash_lock +* inode_hash_lock */ I thought that the rule governing the use of
inode->i_lockwas that it can be used anywhere as long as it is the innermost lock.Hmmm, no references in the code or documentation. Google gives a pretty good reference:
Quoted 8 lines
Quoted 3 lines
http://www.mail-archive.com/[email protected]/msg02584.html
Perhaps a different/new lock needs to be used here?
Well I just changed the order (and documented it to boot :)). It's pretty easy to verify that LOR is no problem. inode hash is only taken in a very few places so other code outside
inode.cis fine to usei_lockas an innermost lock.
It's not just the inode_hash_lock - you move four or five other locks under inode->i_lock as the series progresses. IOWs, there's now many paths and locking orders where the i_lock is not innermost. If we go forward with this, it's only going to get more complex and eventually somewhere we'll need a new lock for an innermost operation because inode->i_lock is no longer safe to use....
Seriously: use a new lock for high level inode operations you are optimising - don't repurpose an existing lock with different usage rules just because it's convenient.
Cheers,
Dave.
Signature · 2 lines
Dave Chinner [email protected]
Re: [patch 29/52] fs: icache lock i_count
Quoted 42 lines · Dave Chinner
Quoted 32 lines · Nick Piggin
Quoted 31 lines · Dave Chinner
Quoted 3 lines · On Thu, Jun 24, 2010 at 01:02:41PM +1000, [email protected] wrote:
Protect
inode->i_countwithi_lock, rather than having it atomic. Next step should also be to move things together (eg. the refcount increment intod_instantiate, which will remove a lock/unlock cycle oni_lock)......
Quoted 9 lines
Index:
linux-2.6/fs/inode.c
===================================================================fs/inode.c +0
-1@@ -33,14 +33,13 @@* inode_hash_lock protects: * inode hash table, i_hash * inode->i_lock protects: -* i_state fs/inode.c line 43
Quoted 10 lines
fs/inode.c +2
-2+* i_state, i_count * * Ordering: * inode_lock * sb_inode_list_lock * inode->i_lock -* inode_lock -* inode_hash_lock +* inode_hash_lock */ I thought that the rule governing the use of
inode->i_lockwas that it can be used anywhere as long as it is the innermost lock.Hmmm, no references in the code or documentation. Google gives a pretty good reference:
Quoted 8 lines
Quoted 3 lines
http://www.mail-archive.com/[email protected]/msg02584.html
Perhaps a different/new lock needs to be used here?
Well I just changed the order (and documented it to boot :)). It's pretty easy to verify that LOR is no problem. inode hash is only taken in a very few places so other code outside
inode.cis fine to usei_lockas an innermost lock.
Quoted 6 lines
It's not just the
inode_hash_lock- you move four or five other locks underinode->i_lockas the series progresses. IOWs, there's now many paths and locking orders where thei_lockis not innermost. If we go forward with this, it's only going to get more complex and eventually somewhere we'll need a new lock for an innermost operation becauseinode->i_lockis no longer safe to use....
OK yes it's more than one lock, but I don't quite see the problem. The locks are mostly confined to inode.c and fs-writeback.c, and filesystems can basically use i_lock as inner most for their purposes. If they get it wrong, lockdep will tell them pretty quick. And it's documented to boot.
Quoted 3 lines
Seriously: use a new lock for high level inode operations you are optimising - don't repurpose an existing lock with different usage rules just because it's convenient.
That's what scalability development is all about, I'm afraid. Just adding more and more locks is what makes things more complex, so you have to juggle around or change locks when possible. If there is a difficulty with locking pops up in future, I'd prefer to look at it then.
I don't think any filesystems cared at all when I converted them.
Re: [patch 29/52] fs: icache lock i_count
Quoted 6 lines · Nick Piggin
On Thu, Jul 01, 2010 at 12:36:18PM +1000, Dave Chinner wrote:
If there is a
difficulty with locking pops up in future, I'd prefer to look at it then.I don't think any filesystems cared at all when I converted them.
What I mean by this is that _today_ no filesystems seemed to have any problems with how I did it. I did touch quota and notify code, which iterates inode sb lists, but it was pretty trivial. Not many others are about inode locking details enough to care about any of the locks in fs/inode.c.
And so instead of adding another lock now when I already have a (IMO) nice and working code, I will prefer to wait until some fs development runs into problem with locking.
There are several things that can be done. Using RCU for more of the inode lists is a possibility, and can improve lock order problems while actually reducing the amount of locking rather than adding locks.
Re: [patch 29/52] fs: icache lock i_count
In reply to Nick PigginQuoted 10 lines · Nick Piggin
Quoted 3 lines · Dave Chinner
Seriously: use a new lock for high level inode operations you are optimising - don't repurpose an existing lock with different usage rules just because it's convenient.
That's what scalability development is all about, I'm afraid. Just adding more and more locks is what makes things more complex, so you have to juggle around or change locks when possible. If there is a difficulty with locking pops up in future, I'd prefer to look at it then.
I must agree strongly with Nick here. Lock proliferation is a Bad Thing; adding a new lock here just adds complexity without really improving anything, since you still have to deal with lock ordering _and_ add a new one to the mix. It also makes struct inode bigger for no real gain. Since i_lock is already well understood and its use is pretty isolated, it seems ideal to extend it to more general use while keeping the isolation intact as much as possible. Hell, if the code were designed with this kind of scalability in mind, i_lock would be doing all the locking directly related to struct inode. Much as it is after Nick's work.
My argument here is that it's not just convenient, it makes the implementation simpler since instead of dealing with two locks there's just one.
Signature · 2 lines
Frank Mayhar <[email protected]> Google, Inc.
Andrew Morton
maintainerRe: [patch 29/52] fs: icache lock i_count
In reply to Nick PigginQuoted 32 lines · On Wed, 30 Jun 2010 22:05:02 +1000 Nick Piggin <[email protected]> wrote:
Quoted 31 lines · Dave Chinner
Quoted 3 lines · On Thu, Jun 24, 2010 at 01:02:41PM +1000, [email protected] wrote:
Protect
inode->i_countwithi_lock, rather than having it atomic. Next step should also be to move things together (eg. the refcount increment intod_instantiate, which will remove a lock/unlock cycle oni_lock)......
Quoted 9 lines
Index:
linux-2.6/fs/inode.c
===================================================================fs/inode.c +0
-1@@ -33,14 +33,13 @@* inode_hash_lock protects: * inode hash table, i_hash * inode->i_lock protects: -* i_state fs/inode.c line 43
Quoted 10 lines
fs/inode.c +2
-2+* i_state, i_count * * Ordering: * inode_lock * sb_inode_list_lock * inode->i_lock -* inode_lock -* inode_hash_lock +* inode_hash_lock */ I thought that the rule governing the use of
inode->i_lockwas that it can be used anywhere as long as it is the innermost lock.Hmmm, no references in the code or documentation. Google gives a pretty good reference:
Quoted 8 lines
Quoted 3 lines
http://www.mail-archive.com/[email protected]/msg02584.html
Perhaps a different/new lock needs to be used here?
Well I just changed the order (and documented it to boot :)). It's pretty easy to verify that LOR is no problem. inode hash is only taken in a very few places so other code outside
inode.cis fine to usei_lockas an innermost lock.
um, nesting a kernel-wide singleton lock inside a per-inode lock is plain nutty.
Re: [patch 29/52] fs: icache lock i_count
Quoted 38 lines · Andrew Morton
Quoted 36 lines · On Wed, 30 Jun 2010 22:05:02 +1000 Nick Piggin <[email protected]> wrote:
Quoted 34 lines · Dave Chinner
Quoted 3 lines · On Thu, Jun 24, 2010 at 01:02:41PM +1000, [email protected] wrote:
Protect
inode->i_countwithi_lock, rather than having it atomic. Next step should also be to move things together (eg. the refcount increment intod_instantiate, which will remove a lock/unlock cycle oni_lock)......
Quoted 9 lines
Index:
linux-2.6/fs/inode.c
===================================================================fs/inode.c +0
-1@@ -33,14 +33,13 @@* inode_hash_lock protects: * inode hash table, i_hash * inode->i_lock protects: -* i_state fs/inode.c line 43
Quoted 10 lines
fs/inode.c +2
-2+* i_state, i_count * * Ordering: * inode_lock * sb_inode_list_lock * inode->i_lock -* inode_lock -* inode_hash_lock +* inode_hash_lock */ I thought that the rule governing the use of
inode->i_lockwas that it can be used anywhere as long as it is the innermost lock.Hmmm, no references in the code or documentation. Google gives a pretty good reference:
http://www.mail-archive.com/[email protected]/msg02584.html
Perhaps a different/new lock needs to be used here?
Quoted 7 lines
Quoted 4 lines
Well I just changed the order (and documented it to boot :)). It's pretty easy to verify that LOR is no problem. inode hash is only taken in a very few places so other code outside
inode.cis fine to usei_lockas an innermost lock.um, nesting a kernel-wide singleton lock inside a per-inode lock is plain nutty.
I think it worked out OK. Because the kernel-wide locks are really restricted in where they are to be used (ie. not in filesystems). So they're really pretty constrained to the inode management subsystem. So filesystems still get to really use i_lock as an inner most lock for their purposes.
And filesystems get to take i_lock and stop all manipulation of inode now. No changing of flags, moving it on/off lists etc behind its back. It really is about locking the data rather than the code.
The final ordering outcome looks like this:
* inode->i_lock
* inode_list_lglock * zone->inode_lru_lock * wb->b_lock * inode_hash_bucket lock
And it works like that because when you want to add or remove an inode from various data structures, you take the i_lock and then take each of these locks in turn, inside it. The alternative is to build a bigger lock ordering graph, and take all the locks up-front before taking i_lock. I did actaully try that and it ended up being worse, so I went this route.
I think taking a global lock in mark_inode_dirty is nutty (especially when that global lock is shared with hash management, LRU scanning, writeback, i_flags access... :) It's just a question of which is less nutty.
Andrew Morton
maintainerRe: [patch 29/52] fs: icache lock i_count
Quoted 57 lines · On Sat, 3 Jul 2010 13:41:23 +1000 Nick Piggin <[email protected]> wrote:
Quoted 38 lines · Andrew Morton
Quoted 36 lines · On Wed, 30 Jun 2010 22:05:02 +1000 Nick Piggin <[email protected]> wrote:
Quoted 34 lines · Dave Chinner
Quoted 3 lines · On Thu, Jun 24, 2010 at 01:02:41PM +1000, [email protected] wrote:
Protect
inode->i_countwithi_lock, rather than having it atomic. Next step should also be to move things together (eg. the refcount increment intod_instantiate, which will remove a lock/unlock cycle oni_lock)......
Quoted 9 lines
Index:
linux-2.6/fs/inode.c
===================================================================fs/inode.c +0
-1@@ -33,14 +33,13 @@* inode_hash_lock protects: * inode hash table, i_hash * inode->i_lock protects: -* i_state fs/inode.c line 43
Quoted 10 lines
fs/inode.c +2
-2+* i_state, i_count * * Ordering: * inode_lock * sb_inode_list_lock * inode->i_lock -* inode_lock -* inode_hash_lock +* inode_hash_lock */ I thought that the rule governing the use of
inode->i_lockwas that it can be used anywhere as long as it is the innermost lock.Hmmm, no references in the code or documentation. Google gives a pretty good reference:
http://www.mail-archive.com/[email protected]/msg02584.html
Perhaps a different/new lock needs to be used here?
Quoted 7 lines
Quoted 4 lines
Well I just changed the order (and documented it to boot :)). It's pretty easy to verify that LOR is no problem. inode hash is only taken in a very few places so other code outside
inode.cis fine to usei_lockas an innermost lock.um, nesting a kernel-wide singleton lock inside a per-inode lock is plain nutty.
I think it worked out OK. Because the kernel-wide locks are really restricted in where they are to be used (ie. not in filesystems). So they're really pretty constrained to the inode management subsystem. So filesystems still get to really use
i_lockas an inner most lock for their purposes.And filesystems get to take
i_lockand stop all manipulation of inode now. No changing of flags, moving it on/off lists etc behind its back. It really is about locking the data rather than the code.
Quoted 7 lines
The final ordering outcome looks like this:
*
inode->i_lock* inode_list_lglock * zone->inode_lru_lock * wb->b_lock * inode_hash_bucket lock
Apart from the conceptual vandalism, it means that any contention times and cache transfer times on those singleton locks will increase worst-case hold times of our nice, fine-grained i_lock.
Quoted 2 lines
And it works like that because when you want to add or remove an inode from various data structures, you take the
i_lock
Well that would be wrong. i_lock protects things *within* its inode. It's nonsensical to take i_lock when the inode is being added to or removed from external containers because i_lock doesn't protect those containers!
Quoted 10 lines
and then take each
of these locks in turn, inside it. The alternative is to build a bigger lock ordering graph, and take all the locks up-front before takingi_lock. I did actaully try that and it ended up being worse, so I went this route.I think taking a global lock in
mark_inode_dirtyis nutty (especially when that global lock is shared with hash management, LRU scanning, writeback,i_flagsaccess... :) It's just a question of which is less nutty.
Yes, inode_lock is bad news.
Re: [patch 29/52] fs: icache lock i_count
Quoted 23 lines · Andrew Morton
Quoted 14 lines · On Sat, 3 Jul 2010 13:41:23 +1000 Nick Piggin <[email protected]> wrote:
Quoted 2 lines · Andrew Morton
um, nesting a kernel-wide singleton lock inside a per-inode lock is plain nutty.
I think it worked out OK. Because the kernel-wide locks are really restricted in where they are to be used (ie. not in filesystems). So they're really pretty constrained to the inode management subsystem. So filesystems still get to really use
i_lockas an inner most lock for their purposes.And filesystems get to take
i_lockand stop all manipulation of inode now. No changing of flags, moving it on/off lists etc behind its back. It really is about locking the data rather than the code.Quoted 7 lines
The final ordering outcome looks like this:
*
inode->i_lock* inode_list_lglock * zone->inode_lru_lock * wb->b_lock * inode_hash_bucket lock
Quoted 3 lines
Apart from the conceptual vandalism, it means that any contention times and cache transfer times on those singleton locks will increase worst-case hold times of our nice, fine-grained
i_lock.
Yes you are quite right about contention times. I'll answer in two parts. First, why I don't think it should prove to be a big problem; second, what we can do to improve it if it does.
So a lot of things that previously required the much worse inode_lock now can use the i_lock (or other fine grained locks above). Also, the the contention only comes into play if we actually happen to hit the same i_lock at the same time, so the throughput oriented mainline should generally be OK, and -rt has priority inheretance that improves that situation.
IF this proves to be a problem -- I doubt it will, in fact I think that worst case contention experienced by filesystems and vfs will go down, significantly -- but if it is a problem, we can easily fix it up.
Because all of those above data structures can be traversed using RCU (hash list already is). That would make all the locks really only taken to put an inode on or off a list.
The other thing that can be trivially done is to introduce different locks. I don't have a problem with that, but like any other data structure, I just would like to wait and see where we have problems. The same argument applies to a lot of places that we use a single lock to lock different properties of an object. We use page_lock to protect page membership on or off LRU and pagecache lists for example, as well as various state transitions (eg. to writeback).
In summary, if there is a lock hold problem, it is easy to use RCU to reduce lock widths, or introduce a new per-inode lock to protect different parts of the inode structure.
Quoted 7 lines
Quoted 2 lines
And it works like that because when you want to add or remove an inode from various data structures, you take the
i_lockWell that would be wrong.
i_lockprotects things *within* its inode. It's nonsensical to takei_lockwhen the inode is being added to or removed from external containers becausei_lockdoesn't protect those containers!
Membership in a data structure is a property of the item, conceptually and literally when you're messing with list entries and such. There is no conceptual vandalism that I can tell.
And it just works better this way when lifting inode_lock (and dcache_lock). Currently, we do things like:
spin_lock(&inode_lock);add_to_list1add_to_list2inode->blah = somethingspin_unlock(&inode_lock);
If you lock list1, list2, and blah seperately, it is no longer an equivalent transformation: other code could find an inode on list1 that is now not on list2 and blah is not set.
The real data object of interest in all cases is the inode object. Other structures are just in aid of finding inode objects that have a particular property.
So it makes a lot of sense to have a lock to rule the inode (as opposed to now we have a lock to rule *all* inodes).
It is possible that locking can be reduced if some things are verified and carefully shown not to matter. I just don't see the need yet and it would make things overly complicated I think. Introducing any more complexity will sink this patchset.
Quoted 6 lines
Quoted 6 lines
and then take each
of these locks in turn, inside it. The alternative is to build a bigger lock ordering graph, and take all the locks up-front before takingi_lock. I did actaully try that and it ended up being worse, so I went this route.
Quoted 6 lines
Quoted 4 lines
I think taking a global lock in
mark_inode_dirtyis nutty (especially when that global lock is shared with hash management, LRU scanning, writeback,i_flagsaccess... :) It's just a question of which is less nutty.Yes,
inode_lockis bad news.
Hopefully not for long :)
Re: [patch 29/52] fs: icache lock i_count
Quoted 4 lines · Nick Piggin
It is possible that locking can be reduced if some things are verified and carefully shown not to matter. I just don't see the need yet and it would make things overly complicated I think. Introducing any more complexity will sink this patchset.
By overly complicated, I mean, for this patchset where locking is already been rewritten. It would then be no more complicated (actually far less) than equivalently trying to lift inode_lock from parts of the code where it is causing contention times.
Re: [patch 29/52] fs: icache lock i_count
In reply to Nick PigginQuoted 2 lines · Nick Piggin
So it makes a lot of sense to have a lock to rule the inode (as opposed to now we have a lock to rule *all* inodes).
I don't disagree with this approach - I object to the fact that you repurpose an existing lock and change it's locking rules to "rule the inode". We don't have any one lock that "rules the inode", anyway, so adding a new "i_list_lock" for the new VFS level locking strategies makes it a lot more self-contained. Fundamentally I'm less concerned about the additional memory usage than I am about having landmines planted around i_lock...
Cheers,
Dave.
Signature · 2 lines
Dave Chinner [email protected]
Re: [patch 29/52] fs: icache lock i_count
Quoted 7 lines · Dave Chinner
Quoted 2 lines · Nick Piggin
So it makes a lot of sense to have a lock to rule the inode (as opposed to now we have a lock to rule *all* inodes).
I don't disagree with this approach - I object to the fact that you repurpose an existing lock and change it's locking rules to "rule the inode". We don't have any one lock that "rules the inode",
"rule the inode" was in regard to the inode cache / dirty / writeout handling; ie. what inode_lock is currently for. The idea is that in these code paths, we tend to want to take an inode, lock it, and then manipulate it (including putting it on or taking it off data structures).
Quoted 4 lines
anyway, so adding a new "
i_list_lock" for the new VFS level locking strategies makes it a lot more self-contained. Fundamentally I'm less concerned about the additional memory usage than I am about having landmines planted aroundi_lock...
(it's not just lists, its refcount and i_state too)
I just don't see a problem. _No_ filesystem takes any of the locks that nest inside i_lock. They may call some inode.c functions, but shouldn't do so while holding i_lock if they are treating i_lock as an innermost lock. So they can keep using i_lock. I did go through and attempt to look at all filesystems.
Within inode.c, lock ordering is 2 levels deep, same as it was when we had inode_lock and i_lock.
If some filesystem introduces a lock ordering problem from not reading the newly added documentation, lockdep should catch it pretty quick.
So I don't see a landmine they don't have (in much larger doses) with i_mutex, reentrant reclaim, page lock, buffer lock, mmap_sem etc already.
Theodore Tso
maintainerRe: [patch 29/52] fs: icache lock i_count
Quoted 11 lines · Nick Piggin
Quoted 9 lines · Dave Chinner
I don't disagree with this approach - I object to the fact that you repurpose an existing lock and change it's locking rules to "rule the inode". We don't have any one lock that "rules the inode", anyway, so adding a new "
i_list_lock" for the new VFS level locking strategies makes it a lot more self-contained. Fundamentally I'm less concerned about the additional memory usage than I am about having landmines planted aroundi_lock...
Quoted 3 lines
If some filesystem introduces a lock ordering problem from not reading the newly added documentation, lockdep should catch it pretty quick.
I assume you mean inline documentation in the source, because I quickly scanned the source and couldn't find any significant changes to any files in Documentation.
It would be nice if the new state of affairs is documented in a single file, so that people who want to understand this new locking system don't have to go crawling through the code, or searching mailing list archives to figure out what's going on.
A lot of the text in this mail thread, including your discussion of the new locking hierarchy, and why things are the way they are, would be good
fodder for a new documentation file. And if you don't want to renamei_lock, because no better name can be found, we should at least document that starting as of 2.6.35/36 the meaning of i_lock changed.
-- Ted
Re: [patch 29/52] fs: icache lock i_count
Quoted 17 lines · Theodore Tso
Quoted 11 lines · Nick Piggin
Quoted 9 lines · Dave Chinner
I don't disagree with this approach - I object to the fact that you repurpose an existing lock and change it's locking rules to "rule the inode". We don't have any one lock that "rules the inode", anyway, so adding a new "
i_list_lock" for the new VFS level locking strategies makes it a lot more self-contained. Fundamentally I'm less concerned about the additional memory usage than I am about having landmines planted aroundi_lock...Quoted 3 lines
If some filesystem introduces a lock ordering problem from not reading the newly added documentation, lockdep should catch it pretty quick.
Quoted 8 lines
I assume you mean inline documentation in the source, because I quickly scanned the source and couldn't find any significant changes to any files in Documentation.
It would be nice if the new state of affairs is documented in a single file, so that people who want to understand this new locking system don't have to go crawling through the code, or searching mailing list archives to figure out what's going on.
These type of internal details of lock ordering I think work best in source files (see rmap.c and filemap.c) so it's a little closer to the source code. That's in inode.c and dcache.c.
Locking for filesystem callback APIs I agree is just as good to be in Documentation/filesystems/Locking (which I need to update a bit), but it's never been used for this stuff before. I'm always open to suggestions of how to document it better though.
Quoted 5 lines
A lot of the text in this mail thread, including your discussion of the new locking hierarchy, and why things are the way they are, would be good
fodder for a new documentation file. And if you don't want to renamei_lock, because no better name can be found, we should at least document that starting as of 2.6.35/36 the meaning ofi_lockchanged.
Well there is not much definition of what i_lock is. It is really not an "innermost" lock anyway (whatever that exactly means). CEPH even takes dcache_lock inside i_lock. NFS uses it pretty extensively too.
So it really is *the* non sleeping lock to protect inode fields that I can see.
As far as filesystems go, inode changes matter very little really, but the best I can do is just to comment and document the locking and try to audit each filesystem.
Re: [patch 29/52] fs: icache lock i_count
In reply to Theodore TsoQuoted 5 lines · Theodore Tso
A lot of the text in this mail thread, including your discussion of the new locking hierarchy, and why things are the way they are, would be good
fodder for a new documentation file. And if you don't want to renamei_lock, because no better name can be found, we should at least document that starting as of 2.6.35/36 the meaning ofi_lockchanged.
Actually I like the idea of renaming i_lock. It adds a bit of code churn but renaming it to more clearly reflect its function makes sense to me and might catch uses of it that might have been missed earlier.
Signature · 2 lines
Frank Mayhar <[email protected]> Google, Inc.
PATCH 30/52 fs: icache lock lru/writeback lists
In reply to [email protected]Add a new lock, wb_inode_list_lock, to protect i_list and various lists which the inode can be put onto.
--- fs/fs-writeback.c | 40 ++++++++++++++++++++++++++++++++++++++-- fs/inode.c | 43 +++++++++++++++++++++++++++++++++++-------- include/linux/writeback.h | 1 + mm/backing-dev.c | 4 ++++ 4 files changed, 78 insertions(+), 10 deletions(-)
fs/fs-writeback.c +49 -3
@@ -287,6 +287,7 @@ static void redirty_tail(struct inode *i {struct bdi_writeback *wb = &inode_to_bdi (inode)->wb; +assert_spin_locked (&wb_inode_list_lock);if (!list_empty (&wb->b_dirty)) {struct inode *tail;
@@ -304,6 +305,7 @@ static void requeue_io(struct inode *ino {struct bdi_writeback *wb = &inode_to_bdi (inode)->wb; +assert_spin_locked (&wb_inode_list_lock);list_move (&inode->i_list, &wb->b_more_io); }
@@ -344,6 +346,7 @@ static void move_expired_inodes(struct lstruct inode *inode;int do_sb_sort =0 ; +assert_spin_locked (&wb_inode_list_lock);while (!list_empty (delaying_queue)) { inode = list_entry(delaying_queue->prev, struct inode, i_list); if (older_than_this &&
@@ -399,11 +402,13 @@ static void inode_wait_for_writeback(str wqh =bit_waitqueue (&inode->i_state, __I_SYNC);while (inode->i_state &I_SYNC ) { +spin_unlock (&wb_inode_list_lock);spin_unlock (&inode->i_lock);spin_unlock (&inode_lock);__wait_on_bit (wqh, &wq, inode_wait,TASK_UNINTERRUPTIBLE );spin_lock (&inode_lock);spin_lock (&inode->i_lock); +spin_lock (&wb_inode_list_lock); } }
@@ -457,6 +462,7 @@ writeback_single_inode(struct inode *ino/* Set I_SYNC, reset I_DIRTY_PAGES */ inode->i_state |=I_SYNC ; inode->i_state &= ~I_DIRTY_PAGES ; +spin_unlock (&wb_inode_list_lock);spin_unlock (&inode->i_lock);spin_unlock (&inode_lock);
@@ -493,6 +499,7 @@ writeback_single_inode(struct inode *inospin_lock (&inode_lock);spin_lock (&inode->i_lock); +spin_lock (&wb_inode_list_lock); inode->i_state &= ~I_SYNC ;if (!(inode->i_state & (I_FREEING |I_CLEAR ))) {if ((inode->i_state &I_DIRTY_PAGES ) && wbc->for_kupdate) {
@@ -623,23 +630,31 @@ static int writeback_sb_inodes(struct sustruct bdi_writeback *wb, struct writeback_control *wbc) { +again: while (!list_empty(&wb->b_io)) { long pages_skipped;struct inode *inode =list_entry (wb->b_io.prev, struct inode, i_list); +if (!spin_trylock (&inode->i_lock)) { +spin_unlock (&wb_inode_list_lock); +spin_lock (&wb_inode_list_lock); + goto again; + }if (wbc->sb && sb != inode->i_sb) {/* super block given and doesn't match, skip this inode */ redirty_tail (inode); +spin_unlock (&inode->i_lock);continue ; } -if (sb != inode->i_sb) +if (sb != inode->i_sb) {/* finish with this superblock */ +spin_unlock (&inode->i_lock);return 0 ; -spin_lock (&inode->i_lock); + }if (inode->i_state & (I_NEW |I_WILL_FREE )) { -spin_unlock (&inode->i_lock);requeue_io (inode); +spin_unlock (&inode->i_lock);continue ; }/*
@@ -662,11 +677,13 @@ static int writeback_sb_inodes(struct su*/ redirty_tail (inode); } +spin_unlock (&wb_inode_list_lock);spin_unlock (&inode->i_lock);spin_unlock (&inode_lock);iput (inode);cond_resched ();spin_lock (&inode_lock); +spin_lock (&wb_inode_list_lock);if (wbc->nr_to_write <=0 ) { wbc->more_io =1 ;return 1 ;
@@ -685,6 +702,9 @@ static void writeback_inodes_wb(struct b wbc->wb_start = jiffies;/* livelock avoidance */ spin_lock (&inode_lock); +again: +spin_lock (&wb_inode_list_lock); +if (!wbc->for_kupdate ||list_empty (&wb->b_io))queue_io (wb, wbc->older_than_this);
@@ -697,13 +717,23 @@ static void writeback_inodes_wb(struct bif (wbc->sb && sb != wbc->sb) {/* super block given and doesn't match, skip this inode */ +if (!spin_trylock (&inode->i_lock)) { +spin_unlock (&wb_inode_list_lock); + goto again; + }redirty_tail (inode); +spin_unlock (&inode->i_lock);continue ; } state =pin_sb_for_writeback (wbc, sb);if (state ==SB_PIN_FAILED ) { +if (!spin_trylock (&inode->i_lock)) { +spin_unlock (&wb_inode_list_lock); + goto again; + }requeue_io (inode); +spin_unlock (&inode->i_lock);continue ; } ret =writeback_sb_inodes (sb, wb, wbc);
@@ -713,6 +743,7 @@ static void writeback_inodes_wb(struct bif (ret)break ; } +spin_unlock (&wb_inode_list_lock);spin_unlock (&inode_lock);/* Leave any unwritten inodes on b_io */ }
@@ -825,12 +856,21 @@ static long wb_writeback(struct bdi_writ* become available for writeback. Otherwise * we'll just busyloop. */ +retry:spin_lock (&inode_lock); +spin_lock (&wb_inode_list_lock);if (!list_empty (&wb->b_more_io)) { inode =list_entry (wb->b_more_io.prev, struct inode, i_list); +if (!spin_trylock (&inode->i_lock)) { +spin_unlock (&wb_inode_list_lock); +spin_unlock (&inode_lock); + goto retry; + }inode_wait_for_writeback (inode); +spin_unlock (&inode->i_lock); } +spin_unlock (&wb_inode_list_lock);spin_unlock (&inode_lock); }
@@ -1142,7 +1182,9 @@ void __mark_inode_dirty(struct inode *in } inode->dirtied_when = jiffies; +spin_lock (&wb_inode_list_lock);list_move (&inode->i_list, &wb->b_dirty); +spin_unlock (&wb_inode_list_lock); } } out:
@@ -1306,7 +1348,9 @@ int write_inode_now(struct inode *inode,might_sleep ();spin_lock (&inode_lock);spin_lock (&inode->i_lock); +spin_lock (&wb_inode_list_lock); ret =writeback_single_inode (inode, &wbc); +spin_unlock (&wb_inode_list_lock);spin_unlock (&inode->i_lock);spin_unlock (&inode_lock);if (sync)
@@ -1332,7 +1376,9 @@ int sync_inode(struct inode *inode, struspin_lock (&inode_lock);spin_lock (&inode->i_lock); +spin_lock (&wb_inode_list_lock); ret =writeback_single_inode (inode, wbc); +spin_unlock (&wb_inode_list_lock);spin_unlock (&inode->i_lock);spin_unlock (&inode_lock);return ret;
fs/inode.c +38 -8
153 lines not on this page · raw message
include/linux/writeback.h +1 -0
@@ -11,6 +11,7 @@ struct backing_dev_info;extern spinlock_t inode_lock;extern spinlock_t sb_inode_list_lock; +extern spinlock_t wb_inode_list_lock;extern spinlock_t inode_hash_lock;extern struct list_head inode_in_use;extern struct list_head inode_unused;
mm/backing-dev.c +4 -0
@@ -78,6 +78,7 @@ static int bdi_debug_stats_show(struct s*/ nr_wb = nr_dirty = nr_io = nr_more_io =0 ;spin_lock (&inode_lock); +spin_lock (&wb_inode_list_lock);list_for_each_entry (wb, &bdi->wb_list, list) { nr_wb++;list_for_each_entry (inode, &wb->b_dirty, i_list)
@@ -87,6 +88,7 @@ static int bdi_debug_stats_show(struct slist_for_each_entry (inode, &wb->b_more_io, i_list) nr_more_io++; } +spin_unlock (&wb_inode_list_lock);spin_unlock (&inode_lock);get_dirty_limits (&background_thresh, &dirty_thresh, &bdi_thresh, bdi);
@@ -712,9 +714,11 @@ void bdi_destroy(struct backing_dev_infostruct bdi_writeback *dst = &default_backing_dev_info.wb;spin_lock (&inode_lock); +spin_lock (&wb_inode_list_lock);list_splice (&bdi->wb.b_dirty, &dst->b_dirty);list_splice (&bdi->wb.b_io, &dst->b_io);list_splice (&bdi->wb.b_more_io, &dst->b_more_io); +spin_unlock (&wb_inode_list_lock);spin_unlock (&inode_lock); }
Peter Zijlstra
maintainerRe: [patch 30/52] fs: icache lock lru/writeback lists
Quoted 1 line · On Thu, 2010-06-24 at 13:02 +1000, [email protected] wrote:
+
assert_spin_locked(&wb_inode_list_lock);
There's also lockdep_assert_held() which also validates we're the owner.
Re: [patch 30/52] fs: icache lock lru/writeback lists
Quoted 4 lines · Peter Zijlstra
Quoted 1 line · On Thu, 2010-06-24 at 13:02 +1000, [email protected] wrote:
+
assert_spin_locked(&wb_inode_list_lock);There's also
lockdep_assert_held()which also validates we're the owner.
These locks should have such miniscule contention now that they effectively mean the same thing :) But no that's a good suggestion thanks. I guess _most_ assert_spin_locked could be changed over.