/* Directory name lookup caching Copyright (C) 1996, 1997, 1998 Free Software Foundation, Inc. Written by Michael I. Bushnell, p/BSG, & Miles Bader. This file is part of the GNU Hurd. The GNU Hurd is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. The GNU Hurd is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA. */ #include "priv.h" #include #include /* Maximum number of names to cache at once */ #define MAXCACHE 200 /* Maximum length of file name we bother caching */ #define CACHE_NAME_LEN 100 /* Cache entry */ struct lookup_cache { struct cacheq_hdr hdr; /* Used to indentify nodes to the fs dependent code. 0 for NODE_CACHE_ID means a `negative' entry -- recording that there's definitely no node with this name. */ ino64_t dir_cache_id, node_cache_id; /* Name of the node NODE_CACHE_ID in the directory DIR_CACHE_ID. Entries with names too long to fit in this buffer aren't cached at all. */ char name[CACHE_NAME_LEN]; /* Strlen of NAME. If this is zero, it's an unused entry. */ size_t name_len; /* XXX */ int stati; }; /* The contents of the cache in no particular order */ static struct cacheq lookup_cache = { sizeof (struct lookup_cache) }; static pthread_spinlock_t cache_lock = PTHREAD_SPINLOCK_INITIALIZER; /* Buffer to hold statistics */ static struct stats { long pos_hits; long neg_hits; long miss; long fetch_errors; } statistics; #define PARTIAL_THRESH 100 #define NPARTIALS MAXCACHE / PARTIAL_THRESH struct stats partial_stats [NPARTIALS]; /* If there's an entry for NAME, of length NAME_LEN, in directory DIR in the cache, return its entry, otherwise 0. CACHE_LOCK must be held. */ static struct lookup_cache * find_cache (struct node *dir, const char *name, size_t name_len) { struct lookup_cache *c; int i; /* Search the list. All unused entries are contiguous at the end of the list, so we can stop searching when we see the first one. */ for (i = 0, c = lookup_cache.mru; c && c->name_len; c = c->hdr.next, i++) if (c->name_len == name_len && c->dir_cache_id == dir->cache_id && c->name[0] == name[0] && strcmp (c->name, name) == 0) { c->stati = i / 100; return c; } return 0; } /* Node NP has just been found in DIR with NAME. If NP is null, that means that this name has been confirmed as absent in the directory. */ void diskfs_enter_lookup_cache (struct node *dir, struct node *np, const char *name) { struct lookup_cache *c; size_t name_len = strlen (name); if (name_len > CACHE_NAME_LEN - 1) return; pthread_spin_lock (&cache_lock); if (lookup_cache.length == 0) /* There should always be an lru_cache; this being zero means that the cache hasn't been initialized yet. Do so. */ cacheq_set_length (&lookup_cache, MAXCACHE); /* See if there's an old entry for NAME in DIR. If not, replace the least recently used entry. */ c = find_cache (dir, name, name_len) ?: lookup_cache.lru; /* Fill C with the new entry. */ c->dir_cache_id = dir->cache_id; c->node_cache_id = np ? np->cache_id : 0; strcpy (c->name, name); c->name_len = name_len; /* Now C becomes the MRU entry! */ cacheq_make_mru (&lookup_cache, c); pthread_spin_unlock (&cache_lock); } /* Purge all references in the cache to NP as a node inside directory DP. */ void diskfs_purge_lookup_cache (struct node *dp, struct node *np) { struct lookup_cache *c, *next; pthread_spin_lock (&cache_lock); for (c = lookup_cache.mru; c; c = next) { /* Save C->hdr.next, since we may move C from this position. */ next = c->hdr.next; if (c->name_len && c->dir_cache_id == dp->cache_id && c->node_cache_id == np->cache_id) { c->name_len = 0; cacheq_make_lru (&lookup_cache, c); /* Use C as the next free entry. */ } } pthread_spin_unlock (&cache_lock); } /* Register a negative hit for an entry in the Nth stat class */ void register_neg_hit (int n) { int i; statistics.neg_hits++; for (i = 0; i < n; i++) partial_stats[i].miss++; for (; i < NPARTIALS; i++) partial_stats[i].neg_hits++; } /* Register a positive hit for an entry in the Nth stat class */ void register_pos_hit (int n) { int i; statistics.pos_hits++; for (i = 0; i < n; i++) partial_stats[i].miss++; for (; i < NPARTIALS; i++) partial_stats[i].pos_hits++; } /* Register a miss */ void register_miss () { int i; statistics.miss++; for (i = 0; i < NPARTIALS; i++) partial_stats[i].miss++; } /* Scan the cache looking for NAME inside DIR. If we don't know anything entry at all, then return 0. If the entry is confirmed to not exist, then return -1. Otherwise, return NP for the entry, with a newly allocated reference. */ struct node * diskfs_check_lookup_cache (struct node *dir, const char *name) { struct lookup_cache *c; pthread_spin_lock (&cache_lock); c = find_cache (dir, name, strlen (name)); if (c) { int id = c->node_cache_id; cacheq_make_mru (&lookup_cache, c); /* Record C as recently used. */ if (id == 0) /* A negative cache entry. */ { register_neg_hit (c->stati); pthread_spin_unlock (&cache_lock); return (struct node *)-1; } else if (id == dir->cache_id) /* The cached node is the same as DIR. */ { register_pos_hit (c->stati); pthread_spin_unlock (&cache_lock); diskfs_nref (dir); return dir; } else /* Just a normal entry in DIR; get the actual node. */ { struct node *np; error_t err; register_pos_hit (c->stati); pthread_spin_unlock (&cache_lock); if (name[0] == '.' && name[1] == '.' && name[2] == '\0') { pthread_mutex_unlock (&dir->lock); err = diskfs_cached_lookup (id, &np); pthread_mutex_lock (&dir->lock); /* In the window where DP was unlocked, we might have lost. So check the cache again, and see if it's still there; if so, then we win. */ c = find_cache (dir, "..", 2); if (!c || c->node_cache_id != id) { /* Lose */ pthread_mutex_unlock (&np->lock); diskfs_nrele (np); return 0; } } else err = diskfs_cached_lookup (id, &np); return err ? 0 : np; } } register_miss (); pthread_spin_unlock (&cache_lock); return 0; }