Files
Arquivotheca.Solaris-2.5/uts/common/fs/dnlc.c
seta75D 7c4988eac0 Init
2021-10-11 19:38:01 -03:00

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);
}