From c234e34ad80801acd902c6d4892a7722fd084a87 Mon Sep 17 00:00:00 2001 From: Justus Winter <4winter@informatik.uni-hamburg.de> Date: Wed, 15 Apr 2015 13:17:06 +0200 Subject: libdiskfs: implement a node cache Previously, all users of libdiskfs implemented a node cache on their own. Move the node cache from ext2fs into libdiskfs. We preserve the previous API by marking all functions that we pull from ext2fs as weak, so that users like tmpfs can still implement their own node cache. * ext2fs/dir.c (diskfs_lookup_hard): Adjust accordingly. * ext2fs/ext2fs.c (main): Don't call `inode_init'. * ext2fs/ext2fs.h (struct disknode): Drop `hnext', `hprevp'. * ext2fs/inode.c: Move the node cache into diskfs. (diskfs_user_make_node): New function. (diskfs_try_dropping_softrefs): Rename to `diskfs_user_try_dropping_softrefs'. (read_node): Rename to `diskfs_user_read_node'. Also move a chunk of code dealing with generations from `diskfs_cached_lookup' here. * libdiskfs/Makefile (OTHERSRCS): Add `node-cache.c'. * libdiskfs/diskfs.h (struct node): Add `hnext', `hprevp'. Amend existing comments, add forward declarations. * libdiskfs/node-cache.c: New file. --- libdiskfs/node-cache.c | 260 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 260 insertions(+) create mode 100644 libdiskfs/node-cache.c (limited to 'libdiskfs/node-cache.c') diff --git a/libdiskfs/node-cache.c b/libdiskfs/node-cache.c new file mode 100644 index 00000000..6b70da83 --- /dev/null +++ b/libdiskfs/node-cache.c @@ -0,0 +1,260 @@ +/* Inode cache. + + Copyright (C) 1994-2015 Free Software Foundation, Inc. + + 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 the GNU Hurd. If not, see . */ + +#include "priv.h" + +#define INOHSZ 8192 +#if ((INOHSZ&(INOHSZ-1)) == 0) +#define INOHASH(ino) ((ino)&(INOHSZ-1)) +#else +#define INOHASH(ino) (((unsigned)(ino))%INOHSZ) +#endif + +/* The nodehash is a cache of nodes. + + Access to nodehash and nodehash_nr_items is protected by + nodecache_lock. + + Every node in the nodehash carries a light reference. When we are + asked to give up that light reference, we reacquire our lock + momentarily to check whether someone else reacquired a reference + through the nodehash. */ +static struct node *nodehash[INOHSZ]; +static size_t nodehash_nr_items; +static pthread_rwlock_t nodecache_lock = PTHREAD_RWLOCK_INITIALIZER; + +/* Initialize the inode hash table. */ +static void __attribute__ ((constructor)) +nodecache_init () +{ +} + +/* Lookup node with inode number INUM. Returns NULL if the node is + not found in the node cache. */ +static struct node * +lookup (ino_t inum) +{ + struct node *np; + for (np = nodehash[INOHASH(inum)]; np; np = np->hnext) + if (np->cache_id == inum) + return np; + return NULL; +} + +/* Fetch inode INUM, set *NPP to the node structure; + gain one user reference and lock the node. */ +error_t __attribute__ ((weak)) +diskfs_cached_lookup (ino_t inum, struct node **npp) +{ + return diskfs_cached_lookup_context (inum, npp, NULL); +} + +/* Fetch inode INUM, set *NPP to the node structure; + gain one user reference and lock the node. */ +error_t +diskfs_cached_lookup_context (ino_t inum, struct node **npp, + struct lookup_context *ctx) +{ + error_t err; + struct node *np, *tmp; + + pthread_rwlock_rdlock (&nodecache_lock); + np = lookup (inum); + if (np) + goto gotit; + pthread_rwlock_unlock (&nodecache_lock); + + err = diskfs_user_make_node (&np, ctx); + if (err) + return err; + + np->cache_id = inum; + pthread_mutex_lock (&np->lock); + + /* Put NP in NODEHASH. */ + pthread_rwlock_wrlock (&nodecache_lock); + tmp = lookup (inum); + if (tmp) + { + /* We lost a race. */ + diskfs_nput (np); + np = tmp; + goto gotit; + } + + np->hnext = nodehash[INOHASH(inum)]; + if (np->hnext) + np->hnext->hprevp = &np->hnext; + np->hprevp = &nodehash[INOHASH(inum)]; + nodehash[INOHASH(inum)] = np; + diskfs_nref_light (np); + nodehash_nr_items += 1; + pthread_rwlock_unlock (&nodecache_lock); + + /* Get the contents of NP off disk. */ + err = diskfs_user_read_node (np, ctx); + if (err) + return err; + else + { + *npp = np; + return 0; + } + + gotit: + diskfs_nref (np); + pthread_rwlock_unlock (&nodecache_lock); + pthread_mutex_lock (&np->lock); + *npp = np; + return 0; +} + +/* Lookup node INUM (which must have a reference already) and return it + without allocating any new references. */ +struct node * +diskfs_cached_ifind (ino_t inum) +{ + struct node *np; + + pthread_rwlock_rdlock (&nodecache_lock); + np = lookup (inum); + pthread_rwlock_unlock (&nodecache_lock); + + assert (np); + return np; +} + +void __attribute__ ((weak)) +diskfs_try_dropping_softrefs (struct node *np) +{ + pthread_rwlock_wrlock (&nodecache_lock); + if (np->hprevp != NULL) + { + /* Check if someone reacquired a reference through the + nodehash. */ + struct references result; + refcounts_references (&np->refcounts, &result); + + if (result.hard > 0) + { + /* A reference was reacquired through a hash table lookup. + It's fine, we didn't touch anything yet. */ + pthread_rwlock_unlock (&nodecache_lock); + return; + } + + *np->hprevp = np->hnext; + if (np->hnext) + np->hnext->hprevp = np->hprevp; + np->hnext = NULL; + np->hprevp = NULL; + nodehash_nr_items -= 1; + diskfs_nrele_light (np); + } + pthread_rwlock_unlock (&nodecache_lock); + + diskfs_user_try_dropping_softrefs (np); +} + +/* For each active node, call FUN. The node is to be locked around the call + to FUN. If FUN returns non-zero for any node, then immediately stop, and + return that value. */ +error_t __attribute__ ((weak)) +diskfs_node_iterate (error_t (*fun)(struct node *)) +{ + error_t err = 0; + int n; + size_t num_nodes; + struct node *node, **node_list, **p; + + pthread_rwlock_rdlock (&nodecache_lock); + + /* We must copy everything from the hash table into another data structure + to avoid running into any problems with the hash-table being modified + during processing (normally we delegate access to hash-table with + nodecache_lock, but we can't hold this while locking the + individual node locks). */ + /* XXX: Can we? */ + num_nodes = nodehash_nr_items; + + /* TODO This method doesn't scale beyond a few dozen nodes and should be + replaced. */ + node_list = malloc (num_nodes * sizeof (struct node *)); + if (node_list == NULL) + { + pthread_rwlock_unlock (&nodecache_lock); + error (0, 0, "unable to allocate temporary node table"); + return ENOMEM; + } + + p = node_list; + for (n = 0; n < INOHSZ; n++) + for (node = nodehash[n]; node; node = node->hnext) + { + *p++ = node; + + /* We acquire a hard reference for node, but without using + diskfs_nref. We do this so that diskfs_new_hardrefs will not + get called. */ + refcounts_ref (&node->refcounts, NULL); + } + + pthread_rwlock_unlock (&nodecache_lock); + + p = node_list; + while (num_nodes-- > 0) + { + node = *p++; + if (!err) + { + pthread_mutex_lock (&node->lock); + err = (*fun)(node); + pthread_mutex_unlock (&node->lock); + } + diskfs_nrele (node); + } + + free (node_list); + return err; +} + +/* The user must define this function if she wants to use the node + cache. Create and initialize a node. */ +error_t __attribute__ ((weak)) +diskfs_user_make_node (struct node **npp, struct lookup_context *ctx) +{ + assert (! "diskfs_user_make_node not implemented"); +} + +/* The user must define this function if she wants to use the node + cache. Read stat information out of the on-disk node. */ +error_t __attribute__ ((weak)) +diskfs_user_read_node (struct node *np, struct lookup_context *ctx) +{ + assert (! "diskfs_user_read_node not implemented"); +} + +/* The user must define this function if she wants to use the node + cache. The last hard reference to a node has gone away; arrange to + have all the weak references dropped that can be. */ +void __attribute__ ((weak)) +diskfs_user_try_dropping_softrefs (struct node *np) +{ + assert (! "diskfs_user_try_dropping_softrefs not implemented"); +} -- cgit v1.2.3