diff options
author | NeilBrown <neilb@suse.de> | 2014-10-13 18:52:14 -0400 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2014-10-13 20:18:16 -0400 |
commit | 23bfc2a24ea3d993cc5cc90c9970654e7232502e (patch) | |
tree | a2bc0e834578b8b8559e255363e9b95d08158e97 /fs/autofs4 | |
parent | 8a273345dcb1d74d12f28a0a76320b23e7e32f55 (diff) |
autofs4: allow RCU-walk to walk through autofs4
This series teaches autofs about RCU-walk so that we don't drop straight
into REF-walk when we hit an autofs directory, and so that we avoid
spinlocks as much as possible when performing an RCU-walk.
This is needed so that the benefits of the recent NFS support for
RCU-walk are fully available when NFS filesystems are automounted.
Patches have been carefully reviewed and tested both with test suites
and in production - thanks a lot to Ian Kent for his support there.
This patch (of 6):
Any attempt to look up a pathname that passes though an autofs4 mount is
currently forced out of RCU-walk into REF-walk.
This can significantly hurt performance of many-thread work loads on
many-core systems, especially if the automounted filesystem supports
RCU-walk but doesn't get to benefit from it.
So if autofs4_d_manage is called with rcu_walk set, only fail with -ECHILD
if it is necessary to wait longer than a spinlock.
Signed-off-by: NeilBrown <neilb@suse.de>
Reviewed-by: Ian Kent <raven@themaw.net>
Tested-by: Ian Kent <raven@themaw.net>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Diffstat (limited to 'fs/autofs4')
-rw-r--r-- | fs/autofs4/autofs_i.h | 2 | ||||
-rw-r--r-- | fs/autofs4/dev-ioctl.c | 2 | ||||
-rw-r--r-- | fs/autofs4/expire.c | 4 | ||||
-rw-r--r-- | fs/autofs4/root.c | 44 |
4 files changed, 34 insertions, 18 deletions
diff --git a/fs/autofs4/autofs_i.h b/fs/autofs4/autofs_i.h index 9e359fb20c0a..2f1032f12d91 100644 --- a/fs/autofs4/autofs_i.h +++ b/fs/autofs4/autofs_i.h | |||
@@ -148,7 +148,7 @@ void autofs4_free_ino(struct autofs_info *); | |||
148 | 148 | ||
149 | /* Expiration */ | 149 | /* Expiration */ |
150 | int is_autofs4_dentry(struct dentry *); | 150 | int is_autofs4_dentry(struct dentry *); |
151 | int autofs4_expire_wait(struct dentry *dentry); | 151 | int autofs4_expire_wait(struct dentry *dentry, int rcu_walk); |
152 | int autofs4_expire_run(struct super_block *, struct vfsmount *, | 152 | int autofs4_expire_run(struct super_block *, struct vfsmount *, |
153 | struct autofs_sb_info *, | 153 | struct autofs_sb_info *, |
154 | struct autofs_packet_expire __user *); | 154 | struct autofs_packet_expire __user *); |
diff --git a/fs/autofs4/dev-ioctl.c b/fs/autofs4/dev-ioctl.c index 5b570b6efa28..aaf96cb25452 100644 --- a/fs/autofs4/dev-ioctl.c +++ b/fs/autofs4/dev-ioctl.c | |||
@@ -450,7 +450,7 @@ static int autofs_dev_ioctl_requester(struct file *fp, | |||
450 | ino = autofs4_dentry_ino(path.dentry); | 450 | ino = autofs4_dentry_ino(path.dentry); |
451 | if (ino) { | 451 | if (ino) { |
452 | err = 0; | 452 | err = 0; |
453 | autofs4_expire_wait(path.dentry); | 453 | autofs4_expire_wait(path.dentry, 0); |
454 | spin_lock(&sbi->fs_lock); | 454 | spin_lock(&sbi->fs_lock); |
455 | param->requester.uid = from_kuid_munged(current_user_ns(), ino->uid); | 455 | param->requester.uid = from_kuid_munged(current_user_ns(), ino->uid); |
456 | param->requester.gid = from_kgid_munged(current_user_ns(), ino->gid); | 456 | param->requester.gid = from_kgid_munged(current_user_ns(), ino->gid); |
diff --git a/fs/autofs4/expire.c b/fs/autofs4/expire.c index 8fa3895cda02..1695eda8ac18 100644 --- a/fs/autofs4/expire.c +++ b/fs/autofs4/expire.c | |||
@@ -461,7 +461,7 @@ found: | |||
461 | return expired; | 461 | return expired; |
462 | } | 462 | } |
463 | 463 | ||
464 | int autofs4_expire_wait(struct dentry *dentry) | 464 | int autofs4_expire_wait(struct dentry *dentry, int rcu_walk) |
465 | { | 465 | { |
466 | struct autofs_sb_info *sbi = autofs4_sbi(dentry->d_sb); | 466 | struct autofs_sb_info *sbi = autofs4_sbi(dentry->d_sb); |
467 | struct autofs_info *ino = autofs4_dentry_ino(dentry); | 467 | struct autofs_info *ino = autofs4_dentry_ino(dentry); |
@@ -471,6 +471,8 @@ int autofs4_expire_wait(struct dentry *dentry) | |||
471 | spin_lock(&sbi->fs_lock); | 471 | spin_lock(&sbi->fs_lock); |
472 | if (ino->flags & AUTOFS_INF_EXPIRING) { | 472 | if (ino->flags & AUTOFS_INF_EXPIRING) { |
473 | spin_unlock(&sbi->fs_lock); | 473 | spin_unlock(&sbi->fs_lock); |
474 | if (rcu_walk) | ||
475 | return -ECHILD; | ||
474 | 476 | ||
475 | DPRINTK("waiting for expire %p name=%.*s", | 477 | DPRINTK("waiting for expire %p name=%.*s", |
476 | dentry, dentry->d_name.len, dentry->d_name.name); | 478 | dentry, dentry->d_name.len, dentry->d_name.name); |
diff --git a/fs/autofs4/root.c b/fs/autofs4/root.c index cdb25ebccc4c..2296c8301b66 100644 --- a/fs/autofs4/root.c +++ b/fs/autofs4/root.c | |||
@@ -210,7 +210,8 @@ next: | |||
210 | return NULL; | 210 | return NULL; |
211 | } | 211 | } |
212 | 212 | ||
213 | static struct dentry *autofs4_lookup_expiring(struct dentry *dentry) | 213 | static struct dentry *autofs4_lookup_expiring(struct dentry *dentry, |
214 | bool rcu_walk) | ||
214 | { | 215 | { |
215 | struct autofs_sb_info *sbi = autofs4_sbi(dentry->d_sb); | 216 | struct autofs_sb_info *sbi = autofs4_sbi(dentry->d_sb); |
216 | struct dentry *parent = dentry->d_parent; | 217 | struct dentry *parent = dentry->d_parent; |
@@ -229,6 +230,11 @@ static struct dentry *autofs4_lookup_expiring(struct dentry *dentry) | |||
229 | struct dentry *expiring; | 230 | struct dentry *expiring; |
230 | struct qstr *qstr; | 231 | struct qstr *qstr; |
231 | 232 | ||
233 | if (rcu_walk) { | ||
234 | spin_unlock(&sbi->lookup_lock); | ||
235 | return ERR_PTR(-ECHILD); | ||
236 | } | ||
237 | |||
232 | ino = list_entry(p, struct autofs_info, expiring); | 238 | ino = list_entry(p, struct autofs_info, expiring); |
233 | expiring = ino->dentry; | 239 | expiring = ino->dentry; |
234 | 240 | ||
@@ -264,13 +270,15 @@ next: | |||
264 | return NULL; | 270 | return NULL; |
265 | } | 271 | } |
266 | 272 | ||
267 | static int autofs4_mount_wait(struct dentry *dentry) | 273 | static int autofs4_mount_wait(struct dentry *dentry, bool rcu_walk) |
268 | { | 274 | { |
269 | struct autofs_sb_info *sbi = autofs4_sbi(dentry->d_sb); | 275 | struct autofs_sb_info *sbi = autofs4_sbi(dentry->d_sb); |
270 | struct autofs_info *ino = autofs4_dentry_ino(dentry); | 276 | struct autofs_info *ino = autofs4_dentry_ino(dentry); |
271 | int status = 0; | 277 | int status = 0; |
272 | 278 | ||
273 | if (ino->flags & AUTOFS_INF_PENDING) { | 279 | if (ino->flags & AUTOFS_INF_PENDING) { |
280 | if (rcu_walk) | ||
281 | return -ECHILD; | ||
274 | DPRINTK("waiting for mount name=%.*s", | 282 | DPRINTK("waiting for mount name=%.*s", |
275 | dentry->d_name.len, dentry->d_name.name); | 283 | dentry->d_name.len, dentry->d_name.name); |
276 | status = autofs4_wait(sbi, dentry, NFY_MOUNT); | 284 | status = autofs4_wait(sbi, dentry, NFY_MOUNT); |
@@ -280,20 +288,22 @@ static int autofs4_mount_wait(struct dentry *dentry) | |||
280 | return status; | 288 | return status; |
281 | } | 289 | } |
282 | 290 | ||
283 | static int do_expire_wait(struct dentry *dentry) | 291 | static int do_expire_wait(struct dentry *dentry, bool rcu_walk) |
284 | { | 292 | { |
285 | struct dentry *expiring; | 293 | struct dentry *expiring; |
286 | 294 | ||
287 | expiring = autofs4_lookup_expiring(dentry); | 295 | expiring = autofs4_lookup_expiring(dentry, rcu_walk); |
296 | if (IS_ERR(expiring)) | ||
297 | return PTR_ERR(expiring); | ||
288 | if (!expiring) | 298 | if (!expiring) |
289 | return autofs4_expire_wait(dentry); | 299 | return autofs4_expire_wait(dentry, rcu_walk); |
290 | else { | 300 | else { |
291 | /* | 301 | /* |
292 | * If we are racing with expire the request might not | 302 | * If we are racing with expire the request might not |
293 | * be quite complete, but the directory has been removed | 303 | * be quite complete, but the directory has been removed |
294 | * so it must have been successful, just wait for it. | 304 | * so it must have been successful, just wait for it. |
295 | */ | 305 | */ |
296 | autofs4_expire_wait(expiring); | 306 | autofs4_expire_wait(expiring, 0); |
297 | autofs4_del_expiring(expiring); | 307 | autofs4_del_expiring(expiring); |
298 | dput(expiring); | 308 | dput(expiring); |
299 | } | 309 | } |
@@ -345,7 +355,7 @@ static struct vfsmount *autofs4_d_automount(struct path *path) | |||
345 | * and the directory was removed, so just go ahead and try | 355 | * and the directory was removed, so just go ahead and try |
346 | * the mount. | 356 | * the mount. |
347 | */ | 357 | */ |
348 | status = do_expire_wait(dentry); | 358 | status = do_expire_wait(dentry, 0); |
349 | if (status && status != -EAGAIN) | 359 | if (status && status != -EAGAIN) |
350 | return NULL; | 360 | return NULL; |
351 | 361 | ||
@@ -353,7 +363,7 @@ static struct vfsmount *autofs4_d_automount(struct path *path) | |||
353 | spin_lock(&sbi->fs_lock); | 363 | spin_lock(&sbi->fs_lock); |
354 | if (ino->flags & AUTOFS_INF_PENDING) { | 364 | if (ino->flags & AUTOFS_INF_PENDING) { |
355 | spin_unlock(&sbi->fs_lock); | 365 | spin_unlock(&sbi->fs_lock); |
356 | status = autofs4_mount_wait(dentry); | 366 | status = autofs4_mount_wait(dentry, 0); |
357 | if (status) | 367 | if (status) |
358 | return ERR_PTR(status); | 368 | return ERR_PTR(status); |
359 | goto done; | 369 | goto done; |
@@ -394,7 +404,7 @@ static struct vfsmount *autofs4_d_automount(struct path *path) | |||
394 | } | 404 | } |
395 | ino->flags |= AUTOFS_INF_PENDING; | 405 | ino->flags |= AUTOFS_INF_PENDING; |
396 | spin_unlock(&sbi->fs_lock); | 406 | spin_unlock(&sbi->fs_lock); |
397 | status = autofs4_mount_wait(dentry); | 407 | status = autofs4_mount_wait(dentry, 0); |
398 | spin_lock(&sbi->fs_lock); | 408 | spin_lock(&sbi->fs_lock); |
399 | ino->flags &= ~AUTOFS_INF_PENDING; | 409 | ino->flags &= ~AUTOFS_INF_PENDING; |
400 | if (status) { | 410 | if (status) { |
@@ -430,21 +440,25 @@ static int autofs4_d_manage(struct dentry *dentry, bool rcu_walk) | |||
430 | return 0; | 440 | return 0; |
431 | } | 441 | } |
432 | 442 | ||
433 | /* We need to sleep, so we need pathwalk to be in ref-mode */ | ||
434 | if (rcu_walk) | ||
435 | return -ECHILD; | ||
436 | |||
437 | /* Wait for pending expires */ | 443 | /* Wait for pending expires */ |
438 | do_expire_wait(dentry); | 444 | if (do_expire_wait(dentry, rcu_walk) == -ECHILD) |
445 | return -ECHILD; | ||
439 | 446 | ||
440 | /* | 447 | /* |
441 | * This dentry may be under construction so wait on mount | 448 | * This dentry may be under construction so wait on mount |
442 | * completion. | 449 | * completion. |
443 | */ | 450 | */ |
444 | status = autofs4_mount_wait(dentry); | 451 | status = autofs4_mount_wait(dentry, rcu_walk); |
445 | if (status) | 452 | if (status) |
446 | return status; | 453 | return status; |
447 | 454 | ||
455 | if (rcu_walk) | ||
456 | /* it is always safe to return 0 as the worst that | ||
457 | * will happen is we retry in REF-walk mode. | ||
458 | * Better than always taking a lock. | ||
459 | */ | ||
460 | return 0; | ||
461 | |||
448 | spin_lock(&sbi->fs_lock); | 462 | spin_lock(&sbi->fs_lock); |
449 | /* | 463 | /* |
450 | * If the dentry has been selected for expire while we slept | 464 | * If the dentry has been selected for expire while we slept |