aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNick Piggin <npiggin@kernel.dk>2011-01-07 01:50:11 -0500
committerNick Piggin <npiggin@kernel.dk>2011-01-07 01:50:33 -0500
commitb3e19d924b6eaf2ca7d22cba99a517c5171007b6 (patch)
tree8c1fa4074114a883a4e2de2f7d12eb29ed91bdf1
parentc6653a838b1b2738561aff0b8c0f62a9b714bdd9 (diff)
fs: scale mntget/mntput
The problem that this patch aims to fix is vfsmount refcounting scalability. We need to take a reference on the vfsmount for every successful path lookup, which often go to the same mount point. The fundamental difficulty is that a "simple" reference count can never be made scalable, because any time a reference is dropped, we must check whether that was the last reference. To do that requires communication with all other CPUs that may have taken a reference count. We can make refcounts more scalable in a couple of ways, involving keeping distributed counters, and checking for the global-zero condition less frequently. - check the global sum once every interval (this will delay zero detection for some interval, so it's probably a showstopper for vfsmounts). - keep a local count and only taking the global sum when local reaches 0 (this is difficult for vfsmounts, because we can't hold preempt off for the life of a reference, so a counter would need to be per-thread or tied strongly to a particular CPU which requires more locking). - keep a local difference of increments and decrements, which allows us to sum the total difference and hence find the refcount when summing all CPUs. Then, keep a single integer "long" refcount for slow and long lasting references, and only take the global sum of local counters when the long refcount is 0. This last scheme is what I implemented here. Attached mounts and process root and working directory references are "long" references, and everything else is a short reference. This allows scalable vfsmount references during path walking over mounted subtrees and unattached (lazy umounted) mounts with processes still running in them. This results in one fewer atomic op in the fastpath: mntget is now just a per-CPU inc, rather than an atomic inc; and mntput just requires a spinlock and non-atomic decrement in the common case. However code is otherwise bigger and heavier, so single threaded performance is basically a wash. Signed-off-by: Nick Piggin <npiggin@kernel.dk>
-rw-r--r--arch/ia64/kernel/perfmon.c2
-rw-r--r--drivers/mtd/mtdchar.c2
-rw-r--r--fs/anon_inodes.c2
-rw-r--r--fs/fs_struct.c26
-rw-r--r--fs/internal.h1
-rw-r--r--fs/namei.c24
-rw-r--r--fs/namespace.c242
-rw-r--r--fs/pipe.c2
-rw-r--r--fs/pnode.c4
-rw-r--r--fs/super.c2
-rw-r--r--include/linux/mount.h53
-rw-r--r--include/linux/path.h2
-rw-r--r--net/socket.c19
13 files changed, 283 insertions, 98 deletions
diff --git a/arch/ia64/kernel/perfmon.c b/arch/ia64/kernel/perfmon.c
index 5a24f40bb48e..f099b82703d8 100644
--- a/arch/ia64/kernel/perfmon.c
+++ b/arch/ia64/kernel/perfmon.c
@@ -1542,7 +1542,7 @@ pfm_exit_smpl_buffer(pfm_buffer_fmt_t *fmt)
1542 * any operations on the root directory. However, we need a non-trivial 1542 * any operations on the root directory. However, we need a non-trivial
1543 * d_name - pfm: will go nicely and kill the special-casing in procfs. 1543 * d_name - pfm: will go nicely and kill the special-casing in procfs.
1544 */ 1544 */
1545static struct vfsmount *pfmfs_mnt; 1545static struct vfsmount *pfmfs_mnt __read_mostly;
1546 1546
1547static int __init 1547static int __init
1548init_pfm_fs(void) 1548init_pfm_fs(void)
diff --git a/drivers/mtd/mtdchar.c b/drivers/mtd/mtdchar.c
index 4759d827e8c7..f511dd15fd31 100644
--- a/drivers/mtd/mtdchar.c
+++ b/drivers/mtd/mtdchar.c
@@ -1201,7 +1201,7 @@ err_unregister_chdev:
1201static void __exit cleanup_mtdchar(void) 1201static void __exit cleanup_mtdchar(void)
1202{ 1202{
1203 unregister_mtd_user(&mtdchar_notifier); 1203 unregister_mtd_user(&mtdchar_notifier);
1204 mntput(mtd_inode_mnt); 1204 mntput_long(mtd_inode_mnt);
1205 unregister_filesystem(&mtd_inodefs_type); 1205 unregister_filesystem(&mtd_inodefs_type);
1206 __unregister_chrdev(MTD_CHAR_MAJOR, 0, 1 << MINORBITS, "mtd"); 1206 __unregister_chrdev(MTD_CHAR_MAJOR, 0, 1 << MINORBITS, "mtd");
1207} 1207}
diff --git a/fs/anon_inodes.c b/fs/anon_inodes.c
index 9d92b33da8a0..5fd38112a6ca 100644
--- a/fs/anon_inodes.c
+++ b/fs/anon_inodes.c
@@ -232,7 +232,7 @@ static int __init anon_inode_init(void)
232 return 0; 232 return 0;
233 233
234err_mntput: 234err_mntput:
235 mntput(anon_inode_mnt); 235 mntput_long(anon_inode_mnt);
236err_unregister_filesystem: 236err_unregister_filesystem:
237 unregister_filesystem(&anon_inode_fs_type); 237 unregister_filesystem(&anon_inode_fs_type);
238err_exit: 238err_exit:
diff --git a/fs/fs_struct.c b/fs/fs_struct.c
index 60b8531f41c5..68ca487bedb1 100644
--- a/fs/fs_struct.c
+++ b/fs/fs_struct.c
@@ -17,11 +17,11 @@ void set_fs_root(struct fs_struct *fs, struct path *path)
17 write_seqcount_begin(&fs->seq); 17 write_seqcount_begin(&fs->seq);
18 old_root = fs->root; 18 old_root = fs->root;
19 fs->root = *path; 19 fs->root = *path;
20 path_get(path); 20 path_get_long(path);
21 write_seqcount_end(&fs->seq); 21 write_seqcount_end(&fs->seq);
22 spin_unlock(&fs->lock); 22 spin_unlock(&fs->lock);
23 if (old_root.dentry) 23 if (old_root.dentry)
24 path_put(&old_root); 24 path_put_long(&old_root);
25} 25}
26 26
27/* 27/*
@@ -36,12 +36,12 @@ void set_fs_pwd(struct fs_struct *fs, struct path *path)
36 write_seqcount_begin(&fs->seq); 36 write_seqcount_begin(&fs->seq);
37 old_pwd = fs->pwd; 37 old_pwd = fs->pwd;
38 fs->pwd = *path; 38 fs->pwd = *path;
39 path_get(path); 39 path_get_long(path);
40 write_seqcount_end(&fs->seq); 40 write_seqcount_end(&fs->seq);
41 spin_unlock(&fs->lock); 41 spin_unlock(&fs->lock);
42 42
43 if (old_pwd.dentry) 43 if (old_pwd.dentry)
44 path_put(&old_pwd); 44 path_put_long(&old_pwd);
45} 45}
46 46
47void chroot_fs_refs(struct path *old_root, struct path *new_root) 47void chroot_fs_refs(struct path *old_root, struct path *new_root)
@@ -59,13 +59,13 @@ void chroot_fs_refs(struct path *old_root, struct path *new_root)
59 write_seqcount_begin(&fs->seq); 59 write_seqcount_begin(&fs->seq);
60 if (fs->root.dentry == old_root->dentry 60 if (fs->root.dentry == old_root->dentry
61 && fs->root.mnt == old_root->mnt) { 61 && fs->root.mnt == old_root->mnt) {
62 path_get(new_root); 62 path_get_long(new_root);
63 fs->root = *new_root; 63 fs->root = *new_root;
64 count++; 64 count++;
65 } 65 }
66 if (fs->pwd.dentry == old_root->dentry 66 if (fs->pwd.dentry == old_root->dentry
67 && fs->pwd.mnt == old_root->mnt) { 67 && fs->pwd.mnt == old_root->mnt) {
68 path_get(new_root); 68 path_get_long(new_root);
69 fs->pwd = *new_root; 69 fs->pwd = *new_root;
70 count++; 70 count++;
71 } 71 }
@@ -76,13 +76,13 @@ void chroot_fs_refs(struct path *old_root, struct path *new_root)
76 } while_each_thread(g, p); 76 } while_each_thread(g, p);
77 read_unlock(&tasklist_lock); 77 read_unlock(&tasklist_lock);
78 while (count--) 78 while (count--)
79 path_put(old_root); 79 path_put_long(old_root);
80} 80}
81 81
82void free_fs_struct(struct fs_struct *fs) 82void free_fs_struct(struct fs_struct *fs)
83{ 83{
84 path_put(&fs->root); 84 path_put_long(&fs->root);
85 path_put(&fs->pwd); 85 path_put_long(&fs->pwd);
86 kmem_cache_free(fs_cachep, fs); 86 kmem_cache_free(fs_cachep, fs);
87} 87}
88 88
@@ -115,7 +115,13 @@ struct fs_struct *copy_fs_struct(struct fs_struct *old)
115 spin_lock_init(&fs->lock); 115 spin_lock_init(&fs->lock);
116 seqcount_init(&fs->seq); 116 seqcount_init(&fs->seq);
117 fs->umask = old->umask; 117 fs->umask = old->umask;
118 get_fs_root_and_pwd(old, &fs->root, &fs->pwd); 118
119 spin_lock(&old->lock);
120 fs->root = old->root;
121 path_get_long(&fs->root);
122 fs->pwd = old->pwd;
123 path_get_long(&fs->pwd);
124 spin_unlock(&old->lock);
119 } 125 }
120 return fs; 126 return fs;
121} 127}
diff --git a/fs/internal.h b/fs/internal.h
index e43b9a4dbf4e..9687c2ee2735 100644
--- a/fs/internal.h
+++ b/fs/internal.h
@@ -63,6 +63,7 @@ extern int copy_mount_string(const void __user *, char **);
63 63
64extern void free_vfsmnt(struct vfsmount *); 64extern void free_vfsmnt(struct vfsmount *);
65extern struct vfsmount *alloc_vfsmnt(const char *); 65extern struct vfsmount *alloc_vfsmnt(const char *);
66extern unsigned int mnt_get_count(struct vfsmount *mnt);
66extern struct vfsmount *__lookup_mnt(struct vfsmount *, struct dentry *, int); 67extern struct vfsmount *__lookup_mnt(struct vfsmount *, struct dentry *, int);
67extern void mnt_set_mountpoint(struct vfsmount *, struct dentry *, 68extern void mnt_set_mountpoint(struct vfsmount *, struct dentry *,
68 struct vfsmount *); 69 struct vfsmount *);
diff --git a/fs/namei.c b/fs/namei.c
index 4e957bf744ae..19433cdba011 100644
--- a/fs/namei.c
+++ b/fs/namei.c
@@ -368,6 +368,18 @@ void path_get(struct path *path)
368EXPORT_SYMBOL(path_get); 368EXPORT_SYMBOL(path_get);
369 369
370/** 370/**
371 * path_get_long - get a long reference to a path
372 * @path: path to get the reference to
373 *
374 * Given a path increment the reference count to the dentry and the vfsmount.
375 */
376void path_get_long(struct path *path)
377{
378 mntget_long(path->mnt);
379 dget(path->dentry);
380}
381
382/**
371 * path_put - put a reference to a path 383 * path_put - put a reference to a path
372 * @path: path to put the reference to 384 * @path: path to put the reference to
373 * 385 *
@@ -381,6 +393,18 @@ void path_put(struct path *path)
381EXPORT_SYMBOL(path_put); 393EXPORT_SYMBOL(path_put);
382 394
383/** 395/**
396 * path_put_long - put a long reference to a path
397 * @path: path to put the reference to