diff options
Diffstat (limited to 'sys/gnu/fs/xfs/xfs_refcache.c')
-rw-r--r-- | sys/gnu/fs/xfs/xfs_refcache.c | 445 |
1 files changed, 445 insertions, 0 deletions
diff --git a/sys/gnu/fs/xfs/xfs_refcache.c b/sys/gnu/fs/xfs/xfs_refcache.c new file mode 100644 index 0000000..58015ec --- /dev/null +++ b/sys/gnu/fs/xfs/xfs_refcache.c @@ -0,0 +1,445 @@ +/* + * Copyright (c) 2000-2003 Silicon Graphics, Inc. All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it would be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * Further, this software is distributed without any warranty that it is + * free of the rightful claim of any third person regarding infringement + * or the like. Any license provided herein, whether implied or + * otherwise, applies only to this software file. Patent licenses, if + * any, provided herein do not apply to combinations of this program with + * other software, or any other product whatsoever. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write the Free Software Foundation, Inc., 59 + * Temple Place - Suite 330, Boston MA 02111-1307, USA. + * + * Contact information: Silicon Graphics, Inc., 1600 Amphitheatre Pkwy, + * Mountain View, CA 94043, or: + * + * http://www.sgi.com + * + * For further information regarding this notice, see: + * + * http://oss.sgi.com/projects/GenInfo/SGIGPLNoticeExplan/ + */ + +#include "xfs.h" +#include "xfs_macros.h" +#include "xfs_types.h" +#include "xfs_inum.h" +#include "xfs_log.h" +#include "xfs_trans.h" +#include "xfs_sb.h" +#include "xfs_ag.h" +#include "xfs_dir.h" +#include "xfs_dir2.h" +#include "xfs_dmapi.h" +#include "xfs_mount.h" +#include "xfs_alloc_btree.h" +#include "xfs_bmap_btree.h" +#include "xfs_ialloc_btree.h" +#include "xfs_itable.h" +#include "xfs_btree.h" +#include "xfs_alloc.h" +#include "xfs_ialloc.h" +#include "xfs_attr.h" +#include "xfs_attr_sf.h" +#include "xfs_dir_sf.h" +#include "xfs_dir2_sf.h" +#include "xfs_dinode.h" +#include "xfs_inode_item.h" +#include "xfs_inode.h" +#include "xfs_bmap.h" +#include "xfs_error.h" +#include "xfs_buf_item.h" +#include "xfs_refcache.h" + +STATIC lock_t xfs_refcache_lock; +STATIC xfs_inode_t **xfs_refcache; +STATIC int xfs_refcache_index; +STATIC int xfs_refcache_busy; +STATIC int xfs_refcache_count; + +void +xfs_refcache_init(void) +{ + spinlock_init(&xfs_refcache_lock, "xfs_refcache"); +} +/* + * Insert the given inode into the reference cache. + */ +void +xfs_refcache_insert( + xfs_inode_t *ip) +{ + vnode_t *vp; + xfs_inode_t *release_ip; + xfs_inode_t **refcache; + + ASSERT(ismrlocked(&(ip->i_iolock), MR_UPDATE)); + + /* + * If an unmount is busy blowing entries out of the cache, + * then don't bother. + */ + if (xfs_refcache_busy) { + return; + } + + /* + * If we tuned the refcache down to zero, don't do anything. + */ + if (!xfs_refcache_size) { + return; + } + + /* + * The inode is already in the refcache, so don't bother + * with it. + */ + if (ip->i_refcache != NULL) { + return; + } + + vp = XFS_ITOV(ip); + /* ASSERT(vp->v_count > 0); */ + VN_HOLD(vp); + + /* + * We allocate the reference cache on use so that we don't + * waste the memory on systems not being used as NFS servers. + */ + if (xfs_refcache == NULL) { + refcache = (xfs_inode_t **)kmem_zalloc(XFS_REFCACHE_SIZE_MAX * + sizeof(xfs_inode_t *), + KM_SLEEP); + } else { + refcache = NULL; + } + + spin_lock(&xfs_refcache_lock); + + /* + * If we allocated memory for the refcache above and it still + * needs it, then use the memory we allocated. Otherwise we'll + * free the memory below. + */ + if (refcache != NULL) { + if (xfs_refcache == NULL) { + xfs_refcache = refcache; + refcache = NULL; + } + } + + /* + * If an unmount is busy clearing out the cache, don't add new + * entries to it. + */ + if (xfs_refcache_busy) { + spin_unlock(&xfs_refcache_lock); + VN_RELE(vp); + /* + * If we allocated memory for the refcache above but someone + * else beat us to using it, then free the memory now. + */ + if (refcache != NULL) { + kmem_free(refcache, + XFS_REFCACHE_SIZE_MAX * sizeof(xfs_inode_t *)); + } + return; + } + release_ip = xfs_refcache[xfs_refcache_index]; + if (release_ip != NULL) { + release_ip->i_refcache = NULL; + xfs_refcache_count--; + ASSERT(xfs_refcache_count >= 0); + } + xfs_refcache[xfs_refcache_index] = ip; + ASSERT(ip->i_refcache == NULL); + ip->i_refcache = &(xfs_refcache[xfs_refcache_index]); + xfs_refcache_count++; + ASSERT(xfs_refcache_count <= xfs_refcache_size); + xfs_refcache_index++; + if (xfs_refcache_index == xfs_refcache_size) { + xfs_refcache_index = 0; + } + spin_unlock(&xfs_refcache_lock); + + /* + * Save the pointer to the inode to be released so that we can + * VN_RELE it once we've dropped our inode locks in xfs_rwunlock(). + * The pointer may be NULL, but that's OK. + */ + ip->i_release = release_ip; + + /* + * If we allocated memory for the refcache above but someone + * else beat us to using it, then free the memory now. + */ + if (refcache != NULL) { + kmem_free(refcache, + XFS_REFCACHE_SIZE_MAX * sizeof(xfs_inode_t *)); + } +} + + +/* + * If the given inode is in the reference cache, purge its entry and + * release the reference on the vnode. + */ +void +xfs_refcache_purge_ip( + xfs_inode_t *ip) +{ + vnode_t *vp; + int error; + + /* + * If we're not pointing to our entry in the cache, then + * we must not be in the cache. + */ + if (ip->i_refcache == NULL) { + return; + } + + spin_lock(&xfs_refcache_lock); + if (ip->i_refcache == NULL) { + spin_unlock(&xfs_refcache_lock); + return; + } + + /* + * Clear both our pointer to the cache entry and its pointer + * back to us. + */ + ASSERT(*(ip->i_refcache) == ip); + *(ip->i_refcache) = NULL; + ip->i_refcache = NULL; + xfs_refcache_count--; + ASSERT(xfs_refcache_count >= 0); + spin_unlock(&xfs_refcache_lock); + + vp = XFS_ITOV(ip); + /* ASSERT(vp->v_count > 1); */ + VOP_RELEASE(vp, error); + VN_RELE(vp); +} + + +/* + * This is called from the XFS unmount code to purge all entries for the + * given mount from the cache. It uses the refcache busy counter to + * make sure that new entries are not added to the cache as we purge them. + */ +void +xfs_refcache_purge_mp( + xfs_mount_t *mp) +{ + vnode_t *vp; + int error, i; + xfs_inode_t *ip; + + if (xfs_refcache == NULL) { + return; + } + + spin_lock(&xfs_refcache_lock); + /* + * Bumping the busy counter keeps new entries from being added + * to the cache. We use a counter since multiple unmounts could + * be in here simultaneously. + */ + xfs_refcache_busy++; + + for (i = 0; i < xfs_refcache_size; i++) { + ip = xfs_refcache[i]; + if ((ip != NULL) && (ip->i_mount == mp)) { + xfs_refcache[i] = NULL; + ip->i_refcache = NULL; + xfs_refcache_count--; + ASSERT(xfs_refcache_count >= 0); + spin_unlock(&xfs_refcache_lock); + vp = XFS_ITOV(ip); + VOP_RELEASE(vp, error); + VN_RELE(vp); + spin_lock(&xfs_refcache_lock); + } + } + + xfs_refcache_busy--; + ASSERT(xfs_refcache_busy >= 0); + spin_unlock(&xfs_refcache_lock); +} + + +/* + * This is called from the XFS sync code to ensure that the refcache + * is emptied out over time. We purge a small number of entries with + * each call. + */ +void +xfs_refcache_purge_some(xfs_mount_t *mp) +{ + int error, i; + xfs_inode_t *ip; + int iplist_index; + xfs_inode_t **iplist; + + if ((xfs_refcache == NULL) || (xfs_refcache_count == 0)) { + return; + } + + iplist_index = 0; + iplist = (xfs_inode_t **)kmem_zalloc(xfs_refcache_purge_count * + sizeof(xfs_inode_t *), KM_SLEEP); + + spin_lock(&xfs_refcache_lock); + + /* + * Store any inodes we find in the next several entries + * into the iplist array to be released after dropping + * the spinlock. We always start looking from the currently + * oldest place in the cache. We move the refcache index + * forward as we go so that we are sure to eventually clear + * out the entire cache when the system goes idle. + */ + for (i = 0; i < xfs_refcache_purge_count; i++) { + ip = xfs_refcache[xfs_refcache_index]; + if (ip != NULL) { + xfs_refcache[xfs_refcache_index] = NULL; + ip->i_refcache = NULL; + xfs_refcache_count--; + ASSERT(xfs_refcache_count >= 0); + iplist[iplist_index] = ip; + iplist_index++; + } + xfs_refcache_index++; + if (xfs_refcache_index == xfs_refcache_size) { + xfs_refcache_index = 0; + } + } + + spin_unlock(&xfs_refcache_lock); + + /* + * Now drop the inodes we collected. + */ + for (i = 0; i < iplist_index; i++) { + VOP_RELEASE(XFS_ITOV(iplist[i]), error); + VN_RELE(XFS_ITOV(iplist[i])); + } + + kmem_free(iplist, xfs_refcache_purge_count * + sizeof(xfs_inode_t *)); +} + +/* + * This is called when the refcache is dynamically resized + * via a sysctl. + * + * If the new size is smaller than the old size, purge all + * entries in slots greater than the new size, and move + * the index if necessary. + * + * If the refcache hasn't even been allocated yet, or the + * new size is larger than the old size, just set the value + * of xfs_refcache_size. + */ + +void +xfs_refcache_resize(int xfs_refcache_new_size) +{ + int i; + xfs_inode_t *ip; + int iplist_index = 0; + xfs_inode_t **iplist; + int error; + + /* + * If the new size is smaller than the current size, + * purge entries to create smaller cache, and + * reposition index if necessary. + * Don't bother if no refcache yet. + */ + if (xfs_refcache && (xfs_refcache_new_size < xfs_refcache_size)) { + + iplist = (xfs_inode_t **)kmem_zalloc(XFS_REFCACHE_SIZE_MAX * + sizeof(xfs_inode_t *), KM_SLEEP); + + spin_lock(&xfs_refcache_lock); + + for (i = xfs_refcache_new_size; i < xfs_refcache_size; i++) { + ip = xfs_refcache[i]; + if (ip != NULL) { + xfs_refcache[i] = NULL; + ip->i_refcache = NULL; + xfs_refcache_count--; + ASSERT(xfs_refcache_count >= 0); + iplist[iplist_index] = ip; + iplist_index++; + } + } + + xfs_refcache_size = xfs_refcache_new_size; + + /* + * Move index to beginning of cache if it's now past the end + */ + if (xfs_refcache_index >= xfs_refcache_new_size) + xfs_refcache_index = 0; + + spin_unlock(&xfs_refcache_lock); + + /* + * Now drop the inodes we collected. + */ + for (i = 0; i < iplist_index; i++) { + VOP_RELEASE(XFS_ITOV(iplist[i]), error); + VN_RELE(XFS_ITOV(iplist[i])); + } + + kmem_free(iplist, XFS_REFCACHE_SIZE_MAX * + sizeof(xfs_inode_t *)); + } else { + spin_lock(&xfs_refcache_lock); + xfs_refcache_size = xfs_refcache_new_size; + spin_unlock(&xfs_refcache_lock); + } +} + +void +xfs_refcache_iunlock( + xfs_inode_t *ip, + uint lock_flags) +{ + xfs_inode_t *release_ip; + int error; + + release_ip = ip->i_release; + ip->i_release = NULL; + + xfs_iunlock(ip, lock_flags); + + if (release_ip != NULL) { + VOP_RELEASE(XFS_ITOV(release_ip), error); + VN_RELE(XFS_ITOV(release_ip)); + } +} + +void +xfs_refcache_destroy(void) +{ + if (xfs_refcache) { + kmem_free(xfs_refcache, + XFS_REFCACHE_SIZE_MAX * sizeof(xfs_inode_t *)); + xfs_refcache = NULL; + } + spinlock_destroy(&xfs_refcache_lock); +} |