Path: blob/main/sys/fs/pseudofs/pseudofs_vncache.c
39483 views
/*-1* SPDX-License-Identifier: BSD-3-Clause2*3* Copyright (c) 2001 Dag-Erling Smørgrav4* All rights reserved.5*6* Redistribution and use in source and binary forms, with or without7* modification, are permitted provided that the following conditions8* are met:9* 1. Redistributions of source code must retain the above copyright10* notice, this list of conditions and the following disclaimer11* in this position and unchanged.12* 2. Redistributions in binary form must reproduce the above copyright13* notice, this list of conditions and the following disclaimer in the14* documentation and/or other materials provided with the distribution.15* 3. The name of the author may not be used to endorse or promote products16* derived from this software without specific prior written permission.17*18* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR19* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES20* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.21* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,22* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT23* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,24* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY25* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT26* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF27* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.28*/2930#include <sys/cdefs.h>31#include "opt_pseudofs.h"3233#include <sys/param.h>34#include <sys/kernel.h>35#include <sys/systm.h>36#include <sys/eventhandler.h>37#include <sys/lock.h>38#include <sys/malloc.h>39#include <sys/mutex.h>40#include <sys/proc.h>41#include <sys/sysctl.h>42#include <sys/vnode.h>4344#include <fs/pseudofs/pseudofs.h>45#include <fs/pseudofs/pseudofs_internal.h>4647static MALLOC_DEFINE(M_PFSVNCACHE, "pfs_vncache", "pseudofs vnode cache");4849static struct mtx pfs_vncache_mutex;50static eventhandler_tag pfs_exit_tag;51static void pfs_exit(void *arg, struct proc *p);52static void pfs_purge_all(void);5354static SYSCTL_NODE(_vfs_pfs, OID_AUTO, vncache, CTLFLAG_RW | CTLFLAG_MPSAFE, 0,55"pseudofs vnode cache");5657static int pfs_vncache_entries;58SYSCTL_INT(_vfs_pfs_vncache, OID_AUTO, entries, CTLFLAG_RD,59&pfs_vncache_entries, 0,60"number of entries in the vnode cache");6162static int pfs_vncache_maxentries;63SYSCTL_INT(_vfs_pfs_vncache, OID_AUTO, maxentries, CTLFLAG_RD,64&pfs_vncache_maxentries, 0,65"highest number of entries in the vnode cache");6667static int pfs_vncache_hits;68SYSCTL_INT(_vfs_pfs_vncache, OID_AUTO, hits, CTLFLAG_RD,69&pfs_vncache_hits, 0,70"number of cache hits since initialization");7172static int pfs_vncache_misses;73SYSCTL_INT(_vfs_pfs_vncache, OID_AUTO, misses, CTLFLAG_RD,74&pfs_vncache_misses, 0,75"number of cache misses since initialization");7677extern struct vop_vector pfs_vnodeops; /* XXX -> .h file */7879static SLIST_HEAD(pfs_vncache_head, pfs_vdata) *pfs_vncache_hashtbl;80static u_long pfs_vncache_hash;81#define PFS_VNCACHE_HASH(pid) (&pfs_vncache_hashtbl[(pid) & pfs_vncache_hash])8283/*84* Initialize vnode cache85*/86void87pfs_vncache_load(void)88{8990mtx_init(&pfs_vncache_mutex, "pfs_vncache", NULL, MTX_DEF);91pfs_vncache_hashtbl = hashinit(maxproc / 4, M_PFSVNCACHE, &pfs_vncache_hash);92pfs_exit_tag = EVENTHANDLER_REGISTER(process_exit, pfs_exit, NULL,93EVENTHANDLER_PRI_ANY);94}9596/*97* Tear down vnode cache98*/99void100pfs_vncache_unload(void)101{102103EVENTHANDLER_DEREGISTER(process_exit, pfs_exit_tag);104pfs_purge_all();105KASSERT(pfs_vncache_entries == 0,106("%d vncache entries remaining", pfs_vncache_entries));107mtx_destroy(&pfs_vncache_mutex);108hashdestroy(pfs_vncache_hashtbl, M_PFSVNCACHE, pfs_vncache_hash);109}110111/*112* Allocate a vnode113*/114int115pfs_vncache_alloc(struct mount *mp, struct vnode **vpp,116struct pfs_node *pn, pid_t pid)117{118struct pfs_vncache_head *hash;119struct pfs_vdata *pvd, *pvd2;120struct vnode *vp;121enum vgetstate vs;122int error;123124/*125* See if the vnode is in the cache.126*/127hash = PFS_VNCACHE_HASH(pid);128if (SLIST_EMPTY(hash))129goto alloc;130retry:131mtx_lock(&pfs_vncache_mutex);132SLIST_FOREACH(pvd, hash, pvd_hash) {133if (pvd->pvd_pn == pn && pvd->pvd_pid == pid &&134pvd->pvd_vnode->v_mount == mp) {135vp = pvd->pvd_vnode;136vs = vget_prep(vp);137mtx_unlock(&pfs_vncache_mutex);138if (vget_finish(vp, LK_EXCLUSIVE, vs) == 0) {139++pfs_vncache_hits;140*vpp = vp;141/*142* Some callers cache_enter(vp) later, so143* we have to make sure it's not in the144* VFS cache so it doesn't get entered145* twice. A better solution would be to146* make pfs_vncache_alloc() responsible147* for entering the vnode in the VFS148* cache.149*/150cache_purge(vp);151return (0);152}153goto retry;154}155}156mtx_unlock(&pfs_vncache_mutex);157alloc:158/* nope, get a new one */159pvd = malloc(sizeof *pvd, M_PFSVNCACHE, M_WAITOK);160error = getnewvnode("pseudofs", mp, &pfs_vnodeops, vpp);161if (error) {162free(pvd, M_PFSVNCACHE);163return (error);164}165pvd->pvd_pn = pn;166pvd->pvd_pid = pid;167(*vpp)->v_data = pvd;168switch (pn->pn_type) {169case pfstype_root:170(*vpp)->v_vflag = VV_ROOT;171#if 0172printf("root vnode allocated\n");173#endif174/* fall through */175case pfstype_dir:176case pfstype_this:177case pfstype_parent:178case pfstype_procdir:179(*vpp)->v_type = VDIR;180break;181case pfstype_file:182(*vpp)->v_type = VREG;183break;184case pfstype_symlink:185(*vpp)->v_type = VLNK;186break;187case pfstype_none:188KASSERT(0, ("pfs_vncache_alloc called for null node\n"));189default:190panic("%s has unexpected type: %d", pn->pn_name, pn->pn_type);191}192/*193* Propagate flag through to vnode so users know it can change194* if the process changes (i.e. execve)195*/196if ((pn->pn_flags & PFS_PROCDEP) != 0)197(*vpp)->v_vflag |= VV_PROCDEP;198pvd->pvd_vnode = *vpp;199vn_lock(*vpp, LK_EXCLUSIVE | LK_RETRY);200VN_LOCK_AREC(*vpp);201error = insmntque(*vpp, mp);202if (error != 0) {203free(pvd, M_PFSVNCACHE);204*vpp = NULL;205return (error);206}207vn_set_state(*vpp, VSTATE_CONSTRUCTED);208retry2:209mtx_lock(&pfs_vncache_mutex);210/*211* Other thread may race with us, creating the entry we are212* going to insert into the cache. Recheck after213* pfs_vncache_mutex is reacquired.214*/215SLIST_FOREACH(pvd2, hash, pvd_hash) {216if (pvd2->pvd_pn == pn && pvd2->pvd_pid == pid &&217pvd2->pvd_vnode->v_mount == mp) {218vp = pvd2->pvd_vnode;219vs = vget_prep(vp);220mtx_unlock(&pfs_vncache_mutex);221if (vget_finish(vp, LK_EXCLUSIVE, vs) == 0) {222++pfs_vncache_hits;223vgone(*vpp);224vput(*vpp);225*vpp = vp;226cache_purge(vp);227return (0);228}229goto retry2;230}231}232++pfs_vncache_misses;233if (++pfs_vncache_entries > pfs_vncache_maxentries)234pfs_vncache_maxentries = pfs_vncache_entries;235SLIST_INSERT_HEAD(hash, pvd, pvd_hash);236mtx_unlock(&pfs_vncache_mutex);237return (0);238}239240/*241* Free a vnode242*/243int244pfs_vncache_free(struct vnode *vp)245{246struct pfs_vdata *pvd, *pvd2;247248mtx_lock(&pfs_vncache_mutex);249pvd = (struct pfs_vdata *)vp->v_data;250KASSERT(pvd != NULL, ("pfs_vncache_free(): no vnode data\n"));251SLIST_FOREACH(pvd2, PFS_VNCACHE_HASH(pvd->pvd_pid), pvd_hash) {252if (pvd2 != pvd)253continue;254SLIST_REMOVE(PFS_VNCACHE_HASH(pvd->pvd_pid), pvd, pfs_vdata, pvd_hash);255--pfs_vncache_entries;256break;257}258mtx_unlock(&pfs_vncache_mutex);259260free(pvd, M_PFSVNCACHE);261vp->v_data = NULL;262return (0);263}264265/*266* Purge the cache of dead entries267*268* The code is not very efficient and this perhaps can be addressed without269* a complete rewrite. Previous iteration was walking a linked list from270* scratch every time. This code only walks the relevant hash chain (if pid271* is provided), but still resorts to scanning the entire cache at least twice272* if a specific component is to be removed which is slower. This can be273* augmented with resizing the hash.274*275* Explanation of the previous state:276*277* This is extremely inefficient due to the fact that vgone() not only278* indirectly modifies the vnode cache, but may also sleep. We can279* neither hold pfs_vncache_mutex across a vgone() call, nor make any280* assumptions about the state of the cache after vgone() returns. In281* consequence, we must start over after every vgone() call, and keep282* trying until we manage to traverse the entire cache.283*284* The only way to improve this situation is to change the data structure285* used to implement the cache.286*/287288static void289pfs_purge_one(struct vnode *vnp)290{291292VOP_LOCK(vnp, LK_EXCLUSIVE);293vgone(vnp);294VOP_UNLOCK(vnp);295vdrop(vnp);296}297298void299pfs_purge(struct pfs_node *pn)300{301struct pfs_vdata *pvd;302struct vnode *vnp;303u_long i, removed;304305mtx_lock(&pfs_vncache_mutex);306restart:307removed = 0;308for (i = 0; i <= pfs_vncache_hash; i++) {309restart_chain:310SLIST_FOREACH(pvd, &pfs_vncache_hashtbl[i], pvd_hash) {311if (pn != NULL && pvd->pvd_pn != pn)312continue;313vnp = pvd->pvd_vnode;314vhold(vnp);315mtx_unlock(&pfs_vncache_mutex);316pfs_purge_one(vnp);317removed++;318mtx_lock(&pfs_vncache_mutex);319goto restart_chain;320}321}322if (removed > 0)323goto restart;324mtx_unlock(&pfs_vncache_mutex);325}326327static void328pfs_purge_all(void)329{330331pfs_purge(NULL);332}333334/*335* Free all vnodes associated with a defunct process336*/337static void338pfs_exit(void *arg, struct proc *p)339{340struct pfs_vncache_head *hash;341struct pfs_vdata *pvd;342struct vnode *vnp;343int pid;344345pid = p->p_pid;346hash = PFS_VNCACHE_HASH(pid);347if (SLIST_EMPTY(hash))348return;349restart:350mtx_lock(&pfs_vncache_mutex);351SLIST_FOREACH(pvd, hash, pvd_hash) {352if (pvd->pvd_pid != pid)353continue;354vnp = pvd->pvd_vnode;355vhold(vnp);356mtx_unlock(&pfs_vncache_mutex);357pfs_purge_one(vnp);358goto restart;359}360mtx_unlock(&pfs_vncache_mutex);361}362363364