Module Name: src Committed By: ad Date: Sat May 30 20:16:14 UTC 2020
Modified Files: src/sys/kern: vfs_cache.c vfs_lookup.c src/sys/sys: namei.src Log Message: A couple of small changes to lookup that cut 5-10% system time from "build.sh release" on my test system: - Crossing mount points during lookup is slow because the set up for, and act of doing VFS_ROOT() is quite involved. Use the name cache to help with this. Cache an "impossible" zero-length name with covered vnodes, that points to the root of the file system mounted there. Use it to cross mounts. When cache_purge() is called on either of the vnodes involved the cache entry will disappear. All of the needed calls for that are already in place (vnode reclaim, unmount, etc). - In lookup_fastforward(), if the the last component has been found and the parent directory (searchdir) is not going to be returned, then don't get a reference to it. To generate a diff of this commit: cvs rdiff -u -r1.145 -r1.146 src/sys/kern/vfs_cache.c cvs rdiff -u -r1.220 -r1.221 src/sys/kern/vfs_lookup.c cvs rdiff -u -r1.57 -r1.58 src/sys/sys/namei.src Please note that diffs are not public domain; they are subject to the copyright notices on the relevant files.
Modified files: Index: src/sys/kern/vfs_cache.c diff -u src/sys/kern/vfs_cache.c:1.145 src/sys/kern/vfs_cache.c:1.146 --- src/sys/kern/vfs_cache.c:1.145 Sat May 30 18:06:17 2020 +++ src/sys/kern/vfs_cache.c Sat May 30 20:16:14 2020 @@ -1,4 +1,4 @@ -/* $NetBSD: vfs_cache.c,v 1.145 2020/05/30 18:06:17 ad Exp $ */ +/* $NetBSD: vfs_cache.c,v 1.146 2020/05/30 20:16:14 ad Exp $ */ /*- * Copyright (c) 2008, 2019, 2020 The NetBSD Foundation, Inc. @@ -172,7 +172,7 @@ */ #include <sys/cdefs.h> -__KERNEL_RCSID(0, "$NetBSD: vfs_cache.c,v 1.145 2020/05/30 18:06:17 ad Exp $"); +__KERNEL_RCSID(0, "$NetBSD: vfs_cache.c,v 1.146 2020/05/30 20:16:14 ad Exp $"); #define __NAMECACHE_PRIVATE #ifdef _KERNEL_OPT @@ -269,6 +269,15 @@ int cache_stat_interval __read_mostly = static struct sysctllog *cache_sysctllog; /* + * This is a dummy name that cannot usually occur anywhere in the cache nor + * file system. It's used when caching the root vnode of mounted file + * systems. The name is attached to the directory that the file system is + * mounted on. + */ +static const char cache_mp_name[] = ""; +static const int cache_mp_nlen = sizeof(cache_mp_name) - 1; + +/* * Red-black tree stuff. */ static const rb_tree_ops_t cache_rbtree_ops = { @@ -507,6 +516,8 @@ cache_lookup(struct vnode *dvp, const ch bool hit; krw_t op; + KASSERT(namelen != cache_mp_nlen || name == cache_mp_name); + /* Establish default result values */ if (iswht_ret != NULL) { *iswht_ret = 0; @@ -630,6 +641,8 @@ cache_lookup_linked(struct vnode *dvp, c uint64_t key; int error; + KASSERT(namelen != cache_mp_nlen || name == cache_mp_name); + /* If disabled, or file system doesn't support this, bail out. */ if (__predict_false((dvp->v_mount->mnt_iflag & IMNT_NCLOOKUP) == 0)) { return false; @@ -714,6 +727,7 @@ cache_lookup_linked(struct vnode *dvp, c } if (ncp->nc_vp == NULL) { /* found negative entry; vn is already null from above */ + KASSERT(namelen != cache_mp_nlen && name != cache_mp_name); COUNT(ncs_neghits); } else { COUNT(ncs_goodhits); /* XXX can be "badhits" */ @@ -797,6 +811,13 @@ cache_revlookup(struct vnode *vp, struct nlen = ncp->nc_nlen; /* + * Ignore mountpoint entries. + */ + if (ncp->nc_nlen == cache_mp_nlen) { + continue; + } + + /* * The queue is partially sorted. Once we hit dots, nothing * else remains but dots and dotdots, so bail out. */ @@ -866,6 +887,8 @@ cache_enter(struct vnode *dvp, struct vn struct namecache *ncp, *oncp; int total; + KASSERT(namelen != cache_mp_nlen || name == cache_mp_name); + /* First, check whether we can/should add a cache entry. */ if ((cnflags & MAKEENTRY) == 0 || __predict_false(namelen > cache_maxlen)) { @@ -1002,6 +1025,49 @@ cache_have_id(struct vnode *vp) } /* + * Enter a mount point. cvp is the covered vnode, and rvp is the root of + * the mounted file system. + */ +void +cache_enter_mount(struct vnode *cvp, struct vnode *rvp) +{ + + KASSERT(vrefcnt(cvp) > 0); + KASSERT(vrefcnt(rvp) > 0); + KASSERT(cvp->v_type == VDIR); + KASSERT((rvp->v_vflag & VV_ROOT) != 0); + + if (rvp->v_type == VDIR) { + cache_enter(cvp, rvp, cache_mp_name, cache_mp_nlen, MAKEENTRY); + } +} + +/* + * Look up a cached mount point. Used in the strongly locked path. + */ +bool +cache_lookup_mount(struct vnode *dvp, struct vnode **vn_ret) +{ + bool ret; + + ret = cache_lookup(dvp, cache_mp_name, cache_mp_nlen, LOOKUP, + MAKEENTRY, NULL, vn_ret); + KASSERT((*vn_ret != NULL) == ret); + return ret; +} + +/* + * Try to cross a mount point. For use with cache_lookup_linked(). + */ +bool +cache_cross_mount(struct vnode **dvp, krwlock_t **plock) +{ + + return cache_lookup_linked(*dvp, cache_mp_name, cache_mp_nlen, + dvp, plock, FSCRED); +} + +/* * Name cache initialization, from vfs_init() when the system is booting. */ void Index: src/sys/kern/vfs_lookup.c diff -u src/sys/kern/vfs_lookup.c:1.220 src/sys/kern/vfs_lookup.c:1.221 --- src/sys/kern/vfs_lookup.c:1.220 Tue May 26 18:38:37 2020 +++ src/sys/kern/vfs_lookup.c Sat May 30 20:16:14 2020 @@ -1,4 +1,4 @@ -/* $NetBSD: vfs_lookup.c,v 1.220 2020/05/26 18:38:37 ad Exp $ */ +/* $NetBSD: vfs_lookup.c,v 1.221 2020/05/30 20:16:14 ad Exp $ */ /* * Copyright (c) 1982, 1986, 1989, 1993 @@ -37,7 +37,7 @@ */ #include <sys/cdefs.h> -__KERNEL_RCSID(0, "$NetBSD: vfs_lookup.c,v 1.220 2020/05/26 18:38:37 ad Exp $"); +__KERNEL_RCSID(0, "$NetBSD: vfs_lookup.c,v 1.221 2020/05/30 20:16:14 ad Exp $"); #ifdef _KERNEL_OPT #include "opt_magiclinks.h" @@ -925,7 +925,7 @@ lookup_crossmount(struct namei_state *st bool *searchdir_locked) { struct componentname *cnp = state->cnp; - struct vnode *foundobj; + struct vnode *foundobj, *vp; struct vnode *searchdir; struct mount *mp; int error, lktype; @@ -954,38 +954,65 @@ lookup_crossmount(struct namei_state *st (mp = foundobj->v_mountedhere) != NULL && (cnp->cn_flags & NOCROSSMOUNT) == 0) { KASSERTMSG(searchdir != foundobj, "same vn %p", searchdir); + /* - * First get the vnode stable. LK_SHARED works brilliantly - * here because almost nothing else wants to lock the - * covered vnode. + * Try the namecache first. If that doesn't work, do + * it the hard way. */ - error = vn_lock(foundobj, LK_SHARED); - if (error != 0) { + if (cache_lookup_mount(foundobj, &vp)) { vrele(foundobj); - foundobj = NULL; - break; - } + foundobj = vp; + } else { + /* First get the vnode stable. */ + error = vn_lock(foundobj, LK_SHARED); + if (error != 0) { + vrele(foundobj); + foundobj = NULL; + break; + } - /* Then check to see if something is still mounted on it. */ - if ((mp = foundobj->v_mountedhere) == NULL) { + /* + * Check to see if something is still mounted on it. + */ + if ((mp = foundobj->v_mountedhere) == NULL) { + VOP_UNLOCK(foundobj); + break; + } + + /* + * Get a reference to the mountpoint, and unlock + * foundobj. + */ + error = vfs_busy(mp); VOP_UNLOCK(foundobj); - break; - } + if (error != 0) { + vrele(foundobj); + foundobj = NULL; + break; + } - /* Get a reference to the mountpoint, and ditch foundobj. */ - error = vfs_busy(mp); - vput(foundobj); - if (error != 0) { - foundobj = NULL; - break; - } + /* + * Now get a reference on the root vnode. + * XXX Future - maybe allow only VDIR here. + */ + error = VFS_ROOT(mp, LK_NONE, &vp); - /* Now get a reference on the root vnode, and drop mount. */ - error = VFS_ROOT(mp, LK_NONE, &foundobj); - vfs_unbusy(mp); - if (error) { - foundobj = NULL; - break; + /* + * If successful, enter it into the cache while + * holding the mount busy (competing with unmount). + */ + if (error == 0) { + cache_enter_mount(foundobj, vp); + } + + /* Finally, drop references to foundobj & mountpoint. */ + vrele(foundobj); + vfs_unbusy(mp); + if (error) { + foundobj = NULL; + break; + } + foundobj = vp; } /* @@ -1261,6 +1288,7 @@ lookup_fastforward(struct namei_state *s int error, error2; size_t oldpathlen; const char *oldnameptr; + bool terminal; /* * Eat as many path name components as possible before giving up and @@ -1271,6 +1299,7 @@ lookup_fastforward(struct namei_state *s searchdir = *searchdir_ret; oldnameptr = cnp->cn_nameptr; oldpathlen = ndp->ni_pathlen; + terminal = false; for (;;) { foundobj = NULL; @@ -1304,7 +1333,8 @@ lookup_fastforward(struct namei_state *s /* * Can't deal with last component when modifying; this needs * searchdir locked and VOP_LOOKUP() called (which can and - * does modify state, despite the name). + * does modify state, despite the name). NB: this case means + * terminal is never set true when LOCKPARENT. */ if ((cnp->cn_flags & ISLASTCN) != 0) { if (cnp->cn_nameiop != LOOKUP || @@ -1338,26 +1368,61 @@ lookup_fastforward(struct namei_state *s error = EOPNOTSUPP; } else { error = ENOENT; + terminal = ((cnp->cn_flags & ISLASTCN) != 0); } break; } /* - * Stop and get a hold on the vnode if there's something - * that can't be handled here: - * - * - we've reached the last component. - * - or encountered a mount point that needs to be crossed. - * - or encountered something other than a directory. - */ - if ((cnp->cn_flags & ISLASTCN) != 0 || - foundobj->v_type != VDIR || - (foundobj->v_type == VDIR && - foundobj->v_mountedhere != NULL)) { + * Stop and get a hold on the vnode if we've encountered + * something other than a dirctory. + */ + if (foundobj->v_type != VDIR) { + error = vcache_tryvget(foundobj); + if (error != 0) { + foundobj = NULL; + error = EOPNOTSUPP; + } + break; + } + + /* + * Try to cross mountpoints, bearing in mind that they can + * be stacked. If at any point we can't go further, stop + * and try to get a reference on the vnode. If we are able + * to get a ref then lookup_crossmount() will take care of + * it, otherwise we'll fall through to lookup_once(). + */ + if (foundobj->v_mountedhere != NULL) { + while (foundobj->v_mountedhere != NULL && + (cnp->cn_flags & NOCROSSMOUNT) == 0 && + cache_cross_mount(&foundobj, &plock)) { + KASSERT(foundobj != NULL); + KASSERT(foundobj->v_type == VDIR); + } + if (foundobj->v_mountedhere != NULL) { + error = vcache_tryvget(foundobj); + if (error != 0) { + foundobj = NULL; + error = EOPNOTSUPP; + } + break; + } else { + searchdir = NULL; + } + } + + /* + * Time to stop if we found the last component & traversed + * all mounts. + */ + if ((cnp->cn_flags & ISLASTCN) != 0) { error = vcache_tryvget(foundobj); if (error != 0) { foundobj = NULL; error = EOPNOTSUPP; + } else { + terminal = (foundobj->v_type != VLNK); } break; } @@ -1371,14 +1436,28 @@ lookup_fastforward(struct namei_state *s searchdir = foundobj; } - /* - * If we ended up with a new search dir, ref it before dropping the - * namecache's lock. The lock prevents both searchdir and foundobj - * from disappearing. If we can't ref the new searchdir, we have a - * bit of a problem. Roll back the fastforward to the beginning and - * let lookup_once() take care of it. - */ - if (searchdir != *searchdir_ret) { + if (terminal) { + /* + * If we exited the loop above having successfully located + * the last component with a zero error code, and it's not a + * symbolic link, then the parent directory is not needed. + * Release reference to the starting parent and make the + * terminal parent disappear into thin air. + */ + KASSERT(plock != NULL); + rw_exit(plock); + vrele(*searchdir_ret); + *searchdir_ret = NULL; + } else if (searchdir != *searchdir_ret) { + /* + * Otherwise we need to return the parent. If we ended up + * with a new search dir, ref it before dropping the + * namecache's lock. The lock prevents both searchdir and + * foundobj from disappearing. If we can't ref the new + * searchdir, we have a bit of a problem. Roll back the + * fastforward to the beginning and let lookup_once() take + * care of it. + */ error2 = vcache_tryvget(searchdir); KASSERT(plock != NULL); rw_exit(plock); Index: src/sys/sys/namei.src diff -u src/sys/sys/namei.src:1.57 src/sys/sys/namei.src:1.58 --- src/sys/sys/namei.src:1.57 Wed May 27 02:03:30 2020 +++ src/sys/sys/namei.src Sat May 30 20:16:14 2020 @@ -1,4 +1,4 @@ -/* $NetBSD: namei.src,v 1.57 2020/05/27 02:03:30 rin Exp $ */ +/* $NetBSD: namei.src,v 1.58 2020/05/30 20:16:14 ad Exp $ */ /* * Copyright (c) 1985, 1989, 1991, 1993 @@ -300,6 +300,9 @@ bool cache_have_id(struct vnode *); void cache_vnode_init(struct vnode * ); void cache_vnode_fini(struct vnode * ); void cache_cpu_init(struct cpu_info *); +void cache_enter_mount(struct vnode *, struct vnode *); +bool cache_cross_mount(struct vnode **, krwlock_t **); +bool cache_lookup_mount(struct vnode *, struct vnode **); void nchinit(void); void namecache_count_pass2(void);