771 lines
18 KiB
C
Executable File
771 lines
18 KiB
C
Executable File
/* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */
|
|
/* All Rights Reserved */
|
|
|
|
/* THIS IS UNPUBLISHED PROPRIETARY SOURCE CODE OF AT&T */
|
|
/* The copyright notice above does not evidence any */
|
|
/* actual or intended publication of such source code. */
|
|
|
|
/*
|
|
* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
|
* PROPRIETARY NOTICE (Combined)
|
|
*
|
|
* This source code is unpublished proprietary information
|
|
* constituting, or derived under license from AT&T's UNIX(r) System V.
|
|
* In addition, portions of such source code were derived from Berkeley
|
|
* 4.3 BSD under license from the Regents of the University of
|
|
* California.
|
|
*
|
|
*
|
|
*
|
|
* Copyright Notice
|
|
*
|
|
* Notice of copyright on this source code product does not indicate
|
|
* publication.
|
|
*
|
|
* (c) 1986,1987,1988,1989 Sun Microsystems, Inc
|
|
* (c) 1983,1984,1985,1986,1987,1988,1989 AT&T.
|
|
* All rights reserved.
|
|
*
|
|
*/
|
|
|
|
#pragma ident "@(#)dnlc.c 1.29 94/11/09 SMI"
|
|
|
|
#include <sys/types.h>
|
|
#include <sys/param.h>
|
|
#include <sys/t_lock.h>
|
|
#include <sys/systm.h>
|
|
#include <sys/vfs.h>
|
|
#include <sys/vnode.h>
|
|
#include <sys/cred.h>
|
|
#include <sys/dnlc.h>
|
|
#include <sys/kmem.h>
|
|
#include <sys/user.h>
|
|
#include <sys/debug.h>
|
|
#include <sys/cmn_err.h>
|
|
#include <sys/vtrace.h>
|
|
#include <sys/bitmap.h>
|
|
|
|
/*
|
|
* Directory name lookup cache.
|
|
* Based on code originally done by Robert Elz at Melbourne.
|
|
*
|
|
* Names found by directory scans are retained in a cache
|
|
* for future reference. Each hash chain is ordered by LRU
|
|
* Cache is indexed by hash value obtained from (vp, name)
|
|
* where the vp refers to the directory containing the name.
|
|
*
|
|
* For simplicity (and economy of storage), names longer than
|
|
* some (small) maximum length are not cached; they occur
|
|
* infrequently in any case, and are almost never of interest.
|
|
*/
|
|
|
|
/*
|
|
* NC_HASHAVELEN is the average length desired for this chain, from
|
|
* which the size of the nc_hash table is derived at create time.
|
|
*
|
|
* NC_MOVETOFRONT is the move-to-front threshold: if the hash lookup
|
|
* depth exceeds this value, we move the looked-up entry to the front of
|
|
* its hash chain. The idea is to make sure that the most frequently
|
|
* accessed entries are found most quickly (by keeping them near the
|
|
* front of their hash chains).
|
|
*/
|
|
#define NC_HASHAVELEN 2
|
|
#define NC_MOVETOFRONT 2
|
|
|
|
/*
|
|
* Hash table of name cache entries for fast lookup, dynamically
|
|
* allocated at startup.
|
|
*/
|
|
struct nc_hash {
|
|
struct ncache *hash_next;
|
|
struct ncache *hash_prev;
|
|
kmutex_t hash_lock;
|
|
} *nc_hash;
|
|
|
|
/*
|
|
* Rotors. Used to select entries on a round-robin basis.
|
|
*/
|
|
static struct ncache *dnlc_purge_rotor;
|
|
static struct nc_hash *dnlc_free_rotor;
|
|
|
|
/*
|
|
* Freelist. Entries with no vp's associated with them
|
|
*/
|
|
static struct ncache *dnlc_freelist;
|
|
static kmutex_t dnlc_free_lock;
|
|
|
|
/*
|
|
* The name cache itself, dynamically allocated at startup.
|
|
*/
|
|
static struct ncache *ncache;
|
|
static int nc_hashsz; /* size of hash table */
|
|
|
|
/*
|
|
* This table holds a list of vnodes to be released without holding other
|
|
* locks.
|
|
*
|
|
* We don't want to hold any dnlc locks while vn_rele()
|
|
* performs any I/O (e.g. nfs_inactive).
|
|
*/
|
|
static vnode_t **nc_rele; /* array of vnode_t ptrs */
|
|
static int nc_rsize; /* size of nc_rele */
|
|
static kmutex_t nc_rele_lock; /* protects nc_rele */
|
|
|
|
struct ncstats ncstats;
|
|
|
|
static int doingcache = 1;
|
|
|
|
static void nc_inshash(struct ncache *, struct nc_hash *);
|
|
static void nc_rmhash(struct ncache *);
|
|
static void nc_move_to_front(struct nc_hash *, struct ncache *);
|
|
static void dnlc_free(struct ncache *);
|
|
static struct ncache *dnlc_get(void);
|
|
static struct ncache *dnlc_search(vnode_t *, char *, int, int, cred_t *);
|
|
|
|
/*
|
|
* The dnlc hashing function.
|
|
* 'hash' and 'namlen' must be l-values.
|
|
*/
|
|
#define DNLC_HASH(name, vp, hash, namlen) \
|
|
{ \
|
|
char Xc, *Xcp; \
|
|
hash = (int)vp >> 8; \
|
|
for (Xcp = (name); (Xc = *Xcp) != 0; Xcp++) \
|
|
hash = (hash << 4) + hash + Xc; \
|
|
namlen = Xcp - (name); \
|
|
}
|
|
|
|
/*
|
|
* Initialize the directory cache.
|
|
* Put all the entries on the LRU chain and clear out the hash links.
|
|
*/
|
|
void
|
|
dnlc_init()
|
|
{
|
|
struct nc_hash *hp;
|
|
int i;
|
|
|
|
mutex_init(&nc_rele_lock, "dnlc vp list", MUTEX_DEFAULT, DEFAULT_WT);
|
|
mutex_init(&dnlc_free_lock, "dnlc freelist", MUTEX_DEFAULT, DEFAULT_WT);
|
|
|
|
/*
|
|
* Compute hash size rounding to the next power of two.
|
|
*/
|
|
nc_hashsz = ncsize / NC_HASHAVELEN;
|
|
nc_hashsz = 1 << highbit(nc_hashsz);
|
|
nc_rsize = 2 * ncsize; /* each ncache entry holds 2 vnodes */
|
|
if (ncsize <= 0) {
|
|
doingcache = 0;
|
|
cmn_err(CE_NOTE, "name cache (dnlc) disabled");
|
|
return;
|
|
}
|
|
ncache = kmem_zalloc(ncsize * sizeof (*ncache), KM_SLEEP);
|
|
nc_hash = kmem_zalloc(nc_hashsz * sizeof (*nc_hash), KM_SLEEP);
|
|
nc_rele = kmem_zalloc(nc_rsize * sizeof (vnode_t *), KM_SLEEP);
|
|
|
|
for (i = 0; i < nc_hashsz; i++) {
|
|
char buf[20];
|
|
|
|
hp = (struct nc_hash *)&nc_hash[i];
|
|
sprintf(buf, "dnlc hash %d", i);
|
|
mutex_init(&hp->hash_lock, buf, MUTEX_DEFAULT, DEFAULT_WT);
|
|
hp->hash_next = (struct ncache *)hp;
|
|
hp->hash_prev = (struct ncache *)hp;
|
|
}
|
|
/*
|
|
* Put all of the entries on the freelist
|
|
*/
|
|
for (i = 0; i < ncsize; i++) {
|
|
dnlc_free(&ncache[i]);
|
|
}
|
|
/*
|
|
* Initialize rotors
|
|
*/
|
|
dnlc_purge_rotor = &ncache[0];
|
|
dnlc_free_rotor = &nc_hash[0];
|
|
}
|
|
|
|
/*
|
|
* Add a name to the directory cache.
|
|
*/
|
|
void
|
|
dnlc_enter(vnode_t *dp, char *name, vnode_t *vp, cred_t *cred)
|
|
{
|
|
int namlen;
|
|
struct ncache *ncp;
|
|
struct nc_hash *hp;
|
|
int hash;
|
|
|
|
TRACE_0(TR_FAC_NFS, TR_DNLC_ENTER_START,
|
|
"dnlc_enter_start:");
|
|
|
|
if (!doingcache) {
|
|
TRACE_2(TR_FAC_NFS, TR_DNLC_ENTER_END,
|
|
"dnlc_enter_end:(%S) %d",
|
|
"not caching", 0);
|
|
return;
|
|
}
|
|
|
|
DNLC_HASH(name, dp, hash, namlen);
|
|
if (namlen > NC_NAMLEN) {
|
|
ncstats.long_enter++;
|
|
TRACE_2(TR_FAC_NFS, TR_DNLC_ENTER_END,
|
|
"dnlc_enter_end:(%S) %d",
|
|
"long name", ncstats.long_enter);
|
|
return;
|
|
}
|
|
/*
|
|
* Get a free dnlc entry. Assume the entry won't be in the cache
|
|
* and initialize it now
|
|
*/
|
|
if ((ncp = dnlc_get()) == NULL)
|
|
return;
|
|
ncp->dp = dp;
|
|
VN_HOLD(dp);
|
|
ncp->vp = vp;
|
|
VN_HOLD(vp);
|
|
ncp->namlen = (char)namlen;
|
|
bcopy(name, ncp->name, (unsigned)namlen);
|
|
ncp->cred = cred;
|
|
ncp->hash = hash;
|
|
if (cred)
|
|
crhold(cred);
|
|
hp = &nc_hash[hash & (nc_hashsz - 1)];
|
|
|
|
mutex_enter(&hp->hash_lock);
|
|
if (dnlc_search(dp, name, namlen, hash, cred) != NULL) {
|
|
mutex_exit(&hp->hash_lock);
|
|
ncstats.dbl_enters++;
|
|
VN_RELE(dp);
|
|
VN_RELE(vp);
|
|
dnlc_free(ncp); /* crfree done here */
|
|
TRACE_2(TR_FAC_NFS, TR_DNLC_ENTER_END,
|
|
"dnlc_enter_end:(%S) %d",
|
|
"dbl enter", ncstats.dbl_enters);
|
|
return;
|
|
}
|
|
/*
|
|
* Insert back into the hash chain.
|
|
*/
|
|
nc_inshash(ncp, hp);
|
|
mutex_exit(&hp->hash_lock);
|
|
ncstats.enters++;
|
|
TRACE_2(TR_FAC_NFS, TR_DNLC_ENTER_END,
|
|
"dnlc_enter_end:(%S) %d",
|
|
"done", ncstats.enters);
|
|
}
|
|
|
|
/*
|
|
* Look up a name in the directory name cache.
|
|
*
|
|
* Return a doubly-held vnode if found: one hold so that it may
|
|
* remain in the cache for other users, the other hold so that
|
|
* the cache is not re-cycled and the identity of the vnode is
|
|
* lost before the caller can use the vnode.
|
|
*/
|
|
vnode_t *
|
|
dnlc_lookup(vnode_t *dp, char *name, cred_t *cred)
|
|
{
|
|
int namlen, hash, depth;
|
|
struct ncache *ncp;
|
|
struct nc_hash *hp;
|
|
vnode_t *vp;
|
|
|
|
TRACE_2(TR_FAC_NFS, TR_DNLC_LOOKUP_START,
|
|
"dnlc_lookup_start:dp %x name %s", dp, name);
|
|
|
|
if (!doingcache) {
|
|
TRACE_4(TR_FAC_NFS, TR_DNLC_LOOKUP_END,
|
|
"dnlc_lookup_end:%S %d vp %x name %s",
|
|
"not_caching", 0, NULL, name);
|
|
return (NULL);
|
|
}
|
|
|
|
DNLC_HASH(name, dp, hash, namlen);
|
|
if (namlen > NC_NAMLEN) {
|
|
ncstats.long_look++;
|
|
TRACE_4(TR_FAC_NFS, TR_DNLC_LOOKUP_END,
|
|
"dnlc_lookup_end:%S %d vp %x name %s",
|
|
"too_long", ncstats.long_look, NULL, name);
|
|
return (NULL);
|
|
}
|
|
|
|
depth = 0;
|
|
hp = &nc_hash[hash & (nc_hashsz - 1)];
|
|
mutex_enter(&hp->hash_lock);
|
|
|
|
for (ncp = hp->hash_next; ncp != (struct ncache *)hp;
|
|
ncp = ncp->hash_next) {
|
|
depth++;
|
|
if (ncp->hash == hash && /* fast signature check */
|
|
ncp->dp == dp &&
|
|
ncp->namlen == namlen &&
|
|
ncp->cred == cred &&
|
|
bcmp(ncp->name, name, namlen) == 0) {
|
|
/*
|
|
* Move this entry to the head of its hash chain
|
|
* if it's not already close.
|
|
*/
|
|
if (depth > NC_MOVETOFRONT)
|
|
nc_move_to_front(hp, ncp);
|
|
/*
|
|
* Put a hold on the vnode now so it's identity
|
|
* can't change before the caller has a chance to
|
|
* put a hold on it.
|
|
*/
|
|
vp = ncp->vp;
|
|
VN_HOLD(vp);
|
|
mutex_exit(&hp->hash_lock);
|
|
ncstats.hits++;
|
|
TRACE_4(TR_FAC_NFS, TR_DNLC_LOOKUP_END,
|
|
"dnlc_lookup_end:%S %d vp %x name %s",
|
|
"hit", ncstats.hits, vp, name);
|
|
return (vp);
|
|
}
|
|
}
|
|
|
|
mutex_exit(&hp->hash_lock);
|
|
ncstats.misses++;
|
|
TRACE_4(TR_FAC_NFS, TR_DNLC_LOOKUP_END,
|
|
"dnlc_lookup_end:%S %d vp %x name %s", "miss", ncstats.misses,
|
|
NULL, name);
|
|
return (NULL);
|
|
}
|
|
|
|
/*
|
|
* Remove an entry in the directory name cache.
|
|
*/
|
|
void
|
|
dnlc_remove(vnode_t *dp, char *name)
|
|
{
|
|
int namlen;
|
|
struct ncache *ncp;
|
|
struct nc_hash *hp;
|
|
int hash;
|
|
|
|
if (!doingcache)
|
|
return;
|
|
DNLC_HASH(name, dp, hash, namlen);
|
|
if (namlen > NC_NAMLEN)
|
|
return;
|
|
hp = &nc_hash[hash & (nc_hashsz - 1)];
|
|
|
|
mutex_enter(&hp->hash_lock);
|
|
while (ncp = dnlc_search(dp, name, namlen, hash, ANYCRED)) {
|
|
vnode_t *vp = ncp->vp;
|
|
vnode_t *dp = ncp->dp;
|
|
|
|
/*
|
|
* Put it on the freelist
|
|
*/
|
|
nc_rmhash(ncp);
|
|
mutex_exit(&hp->hash_lock);
|
|
dnlc_free(ncp);
|
|
|
|
VN_RELE(vp);
|
|
VN_RELE(dp);
|
|
mutex_enter(&hp->hash_lock);
|
|
}
|
|
mutex_exit(&hp->hash_lock);
|
|
}
|
|
|
|
|
|
/*
|
|
* Purge the entire cache.
|
|
*/
|
|
void
|
|
dnlc_purge()
|
|
{
|
|
struct nc_hash *nch;
|
|
struct ncache *ncp;
|
|
int index = 0;
|
|
int i;
|
|
|
|
if (!doingcache)
|
|
return;
|
|
ncstats.purges++;
|
|
|
|
mutex_enter(&nc_rele_lock);
|
|
|
|
for (nch = nc_hash; nch < &nc_hash[nc_hashsz]; nch++) {
|
|
mutex_enter(&nch->hash_lock);
|
|
ncp = nch->hash_next;
|
|
while (ncp != (struct ncache *)nch) {
|
|
struct ncache *np;
|
|
|
|
np = ncp->hash_next;
|
|
nc_rele[index++] = ncp->vp;
|
|
nc_rele[index++] = ncp->dp;
|
|
|
|
nc_rmhash(ncp);
|
|
dnlc_free(ncp);
|
|
ncp = np;
|
|
}
|
|
mutex_exit(&nch->hash_lock);
|
|
}
|
|
|
|
/* Release holds on all the vnodes now */
|
|
for (i = 0; i < index; i++) {
|
|
VN_RELE(nc_rele[i]);
|
|
nc_rele[i] = NULL;
|
|
}
|
|
mutex_exit(&nc_rele_lock);
|
|
}
|
|
|
|
/*
|
|
* Purge any cache entries referencing a vnode.
|
|
*/
|
|
void
|
|
dnlc_purge_vp(vnode_t *vp)
|
|
{
|
|
struct nc_hash *nch;
|
|
struct ncache *ncp;
|
|
int index = 0;
|
|
int i;
|
|
|
|
if (!doingcache)
|
|
return;
|
|
ncstats.purges++;
|
|
|
|
mutex_enter(&nc_rele_lock);
|
|
|
|
for (nch = nc_hash; nch < &nc_hash[nc_hashsz]; nch++) {
|
|
mutex_enter(&nch->hash_lock);
|
|
ncp = nch->hash_next;
|
|
while (ncp != (struct ncache *)nch) {
|
|
struct ncache *np;
|
|
|
|
np = ncp->hash_next;
|
|
if (ncp->dp == vp || ncp->vp == vp) {
|
|
nc_rele[index++] = ncp->vp;
|
|
nc_rele[index++] = ncp->dp;
|
|
nc_rmhash(ncp);
|
|
dnlc_free(ncp);
|
|
}
|
|
ncp = np;
|
|
}
|
|
mutex_exit(&nch->hash_lock);
|
|
}
|
|
|
|
/* Release holds on all the vnodes now */
|
|
for (i = 0; i < index; i++) {
|
|
VN_RELE(nc_rele[i]);
|
|
nc_rele[i] = NULL;
|
|
}
|
|
mutex_exit(&nc_rele_lock);
|
|
}
|
|
|
|
/*
|
|
* Purge cache entries referencing a vfsp. Caller supplies a count
|
|
* of entries to purge; up to that many will be freed. A count of
|
|
* zero indicates that all such entries should be purged. Returns
|
|
* the number of entries that were purged.
|
|
*/
|
|
int
|
|
dnlc_purge_vfsp(struct vfs *vfsp, int count)
|
|
{
|
|
struct nc_hash *nch;
|
|
struct ncache *ncp;
|
|
int n = 0;
|
|
int index = 0;
|
|
int i;
|
|
|
|
if (!doingcache)
|
|
return (0);
|
|
ncstats.purges++;
|
|
|
|
mutex_enter(&nc_rele_lock);
|
|
|
|
for (nch = nc_hash; nch < &nc_hash[nc_hashsz]; nch++) {
|
|
mutex_enter(&nch->hash_lock);
|
|
ncp = nch->hash_next;
|
|
while (ncp != (struct ncache *)nch) {
|
|
struct ncache *np;
|
|
|
|
np = ncp->hash_next;
|
|
if ((ncp->dp != NULL && ncp->dp->v_vfsp == vfsp) ||
|
|
(ncp->vp != NULL && ncp->vp->v_vfsp == vfsp)) {
|
|
n++;
|
|
nc_rele[index++] = ncp->vp;
|
|
nc_rele[index++] = ncp->dp;
|
|
nc_rmhash(ncp);
|
|
dnlc_free(ncp);
|
|
if (count != 0 && n >= count)
|
|
break;
|
|
}
|
|
ncp = np;
|
|
}
|
|
mutex_exit(&nch->hash_lock);
|
|
}
|
|
|
|
/* Release holds on all the vnodes now */
|
|
for (i = 0; i < index; i++) {
|
|
VN_RELE(nc_rele[i]);
|
|
nc_rele[i] = NULL;
|
|
}
|
|
mutex_exit(&nc_rele_lock);
|
|
return (n);
|
|
}
|
|
|
|
/*
|
|
* Purge 1 entry from the dnlc that is part of the filesystem(s)
|
|
* represented by 'vop'. The purpose of this routine is to allow
|
|
* users of the dnlc to free a vnode that is being held by the dnlc.
|
|
*
|
|
* If we find a vnode that we release which will result in
|
|
* freeing the underlying vnode (count was 1), return 1, 0
|
|
* if no appropriate vnodes found.
|
|
*
|
|
* XXX vop is not the 'right' identifier for a filesystem.
|
|
*/
|
|
int
|
|
dnlc_fs_purge1(struct vnodeops *vop)
|
|
{
|
|
struct ncache *current;
|
|
struct ncache *finish;
|
|
struct nc_hash *hp;
|
|
int hash;
|
|
|
|
if (!doingcache)
|
|
return (0);
|
|
/*
|
|
* Scan the list of dnlc entries looking for a likely
|
|
* candidate. Since the ncache doesn't go away (kmem_free)
|
|
* we can scan the list without locks.
|
|
*/
|
|
current = finish = dnlc_purge_rotor;
|
|
|
|
do {
|
|
vnode_t *vp;
|
|
|
|
vp = current->vp;
|
|
if (vp && vp->v_op == vop && vp->v_pages == NULL &&
|
|
vp->v_count == 1) {
|
|
/*
|
|
* Looks like a good choice. Make sure everything
|
|
* still looks valid after getting the approriate
|
|
* lock. We only care about entries that are
|
|
* in the hash table (have an identity), hence
|
|
* we use the hash pointers as the check.
|
|
*/
|
|
hash = current->hash;
|
|
hp = &nc_hash[hash & (nc_hashsz - 1)];
|
|
|
|
mutex_enter(&hp->hash_lock);
|
|
vp = current->vp;
|
|
if (hash == current->hash && current->hash_next &&
|
|
vp && vp->v_op == vop && vp->v_pages == NULL &&
|
|
vp->v_count == 1) {
|
|
vnode_t *dp;
|
|
|
|
dp = current->dp;
|
|
nc_rmhash(current);
|
|
mutex_exit(&hp->hash_lock);
|
|
dnlc_free(current);
|
|
VN_RELE(vp)
|
|
VN_RELE(dp);
|
|
if (++current == &ncache[ncsize])
|
|
current = ncache;
|
|
dnlc_purge_rotor = current;
|
|
return (1);
|
|
}
|
|
mutex_exit(&hp->hash_lock);
|
|
}
|
|
current++; /* next */
|
|
if (current == &ncache[ncsize])
|
|
current = ncache;
|
|
} while (current != finish);
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* Iterator/purge function for the dnlc. Iterate over all the entries
|
|
* in the cache applying "func" to each "real" entry ("real" ==
|
|
* has an associated vnode). Stop the iteration
|
|
* when func returns 1 and purge the entry.
|
|
* returns 1 if entry purged, 0 otherwise.
|
|
*/
|
|
int
|
|
dnlc_iter_purge(int (*func)(), int arg)
|
|
{
|
|
struct nc_hash *nch;
|
|
struct ncache *ncp;
|
|
|
|
if (!doingcache)
|
|
return (0);
|
|
ncstats.purges++;
|
|
|
|
for (nch = nc_hash; nch < &nc_hash[nc_hashsz]; nch++) {
|
|
mutex_enter(&nch->hash_lock);
|
|
ncp = nch->hash_next;
|
|
while (ncp != (struct ncache *)nch) {
|
|
if (ncp->vp && func(ncp, arg)) {
|
|
vnode_t *vp = ncp->vp;
|
|
vnode_t *dp = ncp->dp;
|
|
|
|
nc_rmhash(ncp);
|
|
mutex_exit(&nch->hash_lock);
|
|
dnlc_free(ncp);
|
|
VN_RELE(vp);
|
|
VN_RELE(dp);
|
|
return (1);
|
|
}
|
|
ncp = ncp->hash_next;
|
|
}
|
|
mutex_exit(&nch->hash_lock);
|
|
}
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* Utility routine to search for a cache entry. Return a locked
|
|
* ncache entry if found, NULL otherwise.
|
|
*/
|
|
static struct ncache *
|
|
dnlc_search(vnode_t *dp, char *name, int namlen, int hash, cred_t *cred)
|
|
{
|
|
struct nc_hash *hp;
|
|
struct ncache *ncp;
|
|
|
|
hp = &nc_hash[hash & (nc_hashsz - 1)];
|
|
|
|
for (ncp = hp->hash_next; ncp != (struct ncache *)hp;
|
|
ncp = ncp->hash_next) {
|
|
if (ncp->hash == hash &&
|
|
ncp->dp == dp &&
|
|
ncp->namlen == namlen &&
|
|
(cred == ANYCRED || ncp->cred == cred) &&
|
|
bcmp(ncp->name, name, namlen) == 0)
|
|
return (ncp);
|
|
}
|
|
return (NULL);
|
|
}
|
|
|
|
/*
|
|
* Name cache hash list insertion and deletion routines. These should
|
|
* probably be recoded in assembly language for speed.
|
|
*/
|
|
|
|
/*
|
|
* Insert entry at the front of the list
|
|
*/
|
|
static void
|
|
nc_inshash(struct ncache *ncp, struct nc_hash *hp)
|
|
{
|
|
ncp->hash_next = hp->hash_next;
|
|
ncp->hash_prev = (struct ncache *)hp;
|
|
hp->hash_next->hash_prev = ncp;
|
|
hp->hash_next = ncp;
|
|
}
|
|
|
|
static void
|
|
nc_rmhash(struct ncache *ncp)
|
|
{
|
|
ncp->hash_prev->hash_next = ncp->hash_next;
|
|
ncp->hash_next->hash_prev = ncp->hash_prev;
|
|
ncp->hash_prev = NULL;
|
|
ncp->hash_next = NULL;
|
|
}
|
|
|
|
static void
|
|
nc_move_to_front(struct nc_hash *hp, struct ncache *ncp)
|
|
{
|
|
struct ncache *next = ncp->hash_next;
|
|
struct ncache *prev = ncp->hash_prev;
|
|
|
|
prev->hash_next = next;
|
|
next->hash_prev = prev;
|
|
|
|
ncp->hash_next = next = hp->hash_next;
|
|
ncp->hash_prev = (struct ncache *)hp;
|
|
next->hash_prev = ncp;
|
|
hp->hash_next = ncp;
|
|
|
|
ncstats.move_to_front++;
|
|
}
|
|
|
|
/*
|
|
* Find an available entry to use.
|
|
*/
|
|
static struct ncache *
|
|
dnlc_get()
|
|
{
|
|
struct ncache *ncp;
|
|
struct nc_hash *hp;
|
|
struct nc_hash *end;
|
|
struct vnode *vp;
|
|
|
|
/*
|
|
* Try getting an entry that has no identity. These entries
|
|
* are *not* in the hash chains
|
|
*/
|
|
if (dnlc_freelist) {
|
|
mutex_enter(&dnlc_free_lock);
|
|
if ((ncp = dnlc_freelist) != NULL) {
|
|
dnlc_freelist = ncp->next_free;
|
|
ncp->next_free = NULL;
|
|
mutex_exit(&dnlc_free_lock);
|
|
return (ncp);
|
|
}
|
|
mutex_exit(&dnlc_free_lock);
|
|
}
|
|
/*
|
|
* Steal an entry
|
|
*/
|
|
hp = end = dnlc_free_rotor;
|
|
do {
|
|
if (++hp == &nc_hash[nc_hashsz])
|
|
hp = nc_hash;
|
|
dnlc_free_rotor = hp;
|
|
if (hp->hash_next == (struct ncache *)hp)
|
|
continue;
|
|
mutex_enter(&hp->hash_lock);
|
|
for (ncp = hp->hash_prev;
|
|
ncp != (struct ncache *)hp;
|
|
ncp = ncp->hash_prev) {
|
|
vp = ncp->vp;
|
|
if ((vp->v_pages == NULL) && (vp->v_count == 1))
|
|
break;
|
|
}
|
|
if (ncp == (struct ncache *)hp)
|
|
ncp = hp->hash_prev;
|
|
if (ncp != (struct ncache *)hp) {
|
|
vnode_t *sdp = ncp->dp;
|
|
vnode_t *svp = ncp->vp;
|
|
|
|
/*
|
|
* Remove from hash chain. Caller
|
|
* must initialize all fields
|
|
*/
|
|
nc_rmhash(ncp);
|
|
mutex_exit(&hp->hash_lock);
|
|
if (ncp->cred != NULL) {
|
|
crfree(ncp->cred);
|
|
}
|
|
VN_RELE(sdp);
|
|
VN_RELE(svp);
|
|
return (ncp);
|
|
}
|
|
mutex_exit(&hp->hash_lock);
|
|
} while (hp != end);
|
|
return (NULL);
|
|
}
|
|
|
|
/*
|
|
* Put an entry with no identity on the freelist.
|
|
*/
|
|
static void
|
|
dnlc_free(struct ncache *ncp)
|
|
{
|
|
if (ncp->cred != NOCRED) {
|
|
crfree(ncp->cred);
|
|
ncp->cred = NOCRED;
|
|
}
|
|
ncp->dp = NULL;
|
|
ncp->vp = NULL;
|
|
mutex_enter(&dnlc_free_lock);
|
|
ncp->next_free = dnlc_freelist;
|
|
dnlc_freelist = ncp;
|
|
mutex_exit(&dnlc_free_lock);
|
|
}
|