aboutsummaryrefslogtreecommitdiff
path: root/sys/ufs/ufs/ufs_dirhash.c
diff options
context:
space:
mode:
authorSean Nicholas Barkas <snb@FreeBSD.org>2009-06-03 09:44:22 +0000
committerSean Nicholas Barkas <snb@FreeBSD.org>2009-06-03 09:44:22 +0000
commitaf0adb83127044514f274154db32b2cdf627d304 (patch)
treeb9cd7cc5cf9fb5137de08181e4c192636eb3c203 /sys/ufs/ufs/ufs_dirhash.c
parent195ebc7e9e4b129de810833791a19dfb4349d6a9 (diff)
downloadsrc-af0adb83127044514f274154db32b2cdf627d304.tar.gz
src-af0adb83127044514f274154db32b2cdf627d304.zip
Add vm_lowmem event handler for dirhash. This will cause dirhashes to be
deleted when the system is low on memory. This ought to allow an increase to vfs.ufs.dirhash_maxmem on machines that have lots of memory, without degrading performance by having too much memory reserved for dirhash when other things need it. The default value for dirhash_maxmem is being kept at 2MB for now, though. This work was mostly done during the 2008 Google Summer of Code. Approved by: dwmalone (mentor), re MFC after: 3 months
Notes
Notes: svn path=/head/; revision=193375
Diffstat (limited to 'sys/ufs/ufs/ufs_dirhash.c')
-rw-r--r--sys/ufs/ufs/ufs_dirhash.c133
1 files changed, 107 insertions, 26 deletions
diff --git a/sys/ufs/ufs/ufs_dirhash.c b/sys/ufs/ufs/ufs_dirhash.c
index 3b6d5e1fafa5..50663260ebfd 100644
--- a/sys/ufs/ufs/ufs_dirhash.c
+++ b/sys/ufs/ufs/ufs_dirhash.c
@@ -49,6 +49,8 @@ __FBSDID("$FreeBSD$");
#include <sys/refcount.h>
#include <sys/sysctl.h>
#include <sys/sx.h>
+#include <sys/eventhandler.h>
+#include <sys/time.h>
#include <vm/uma.h>
#include <ufs/ufs/quota.h>
@@ -81,6 +83,13 @@ SYSCTL_INT(_vfs_ufs, OID_AUTO, dirhash_mem, CTLFLAG_RD, &ufs_dirhashmem,
static int ufs_dirhashcheck = 0;
SYSCTL_INT(_vfs_ufs, OID_AUTO, dirhash_docheck, CTLFLAG_RW, &ufs_dirhashcheck,
0, "enable extra sanity tests");
+static int ufs_dirhashlowmemcount = 0;
+SYSCTL_INT(_vfs_ufs, OID_AUTO, dirhash_lowmemcount, CTLFLAG_RD,
+ &ufs_dirhashlowmemcount, 0, "number of times low memory hook called");
+static int ufs_dirhashreclaimage = 5;
+SYSCTL_INT(_vfs_ufs, OID_AUTO, dirhash_reclaimage, CTLFLAG_RW,
+ &ufs_dirhashreclaimage, 0,
+ "max time in seconds of hash inactivity before deletion in low VM events");
static int ufsdirhash_hash(struct dirhash *dh, char *name, int namelen);
@@ -90,6 +99,7 @@ static int ufsdirhash_findslot(struct dirhash *dh, char *name, int namelen,
doff_t offset);
static doff_t ufsdirhash_getprev(struct direct *dp, doff_t offset);
static int ufsdirhash_recycle(int wanted);
+static void ufsdirhash_lowmem(void);
static void ufsdirhash_free_locked(struct inode *ip);
static uma_zone_t ufsdirhash_zone;
@@ -393,6 +403,7 @@ ufsdirhash_build(struct inode *ip)
dh->dh_seqopt = 0;
dh->dh_seqoff = 0;
dh->dh_score = DH_SCOREINIT;
+ dh->dh_lastused = time_second;
/*
* Use non-blocking mallocs so that we will revert to a linear
@@ -569,6 +580,9 @@ ufsdirhash_lookup(struct inode *ip, char *name, int namelen, doff_t *offp,
/* Update the score. */
if (dh->dh_score < DH_SCOREMAX)
dh->dh_score++;
+
+ /* Update last used time. */
+ dh->dh_lastused = time_second;
DIRHASHLIST_UNLOCK();
vp = ip->i_vnode;
@@ -811,6 +825,9 @@ ufsdirhash_add(struct inode *ip, struct direct *dirp, doff_t offset)
dh->dh_hused++;
DH_ENTRY(dh, slot) = offset;
+ /* Update last used time. */
+ dh->dh_lastused = time_second;
+
/* Update the per-block summary info. */
ufsdirhash_adjfree(dh, offset, -DIRSIZ(0, dirp));
ufsdirhash_release(dh);
@@ -1150,6 +1167,46 @@ ufsdirhash_getprev(struct direct *dirp, doff_t offset)
}
/*
+ * Delete the given dirhash and reclaim its memory. Assumes that
+ * ufsdirhash_list is locked, and leaves it locked. Also assumes
+ * that dh is locked. Returns the amount of memory freed.
+ */
+static int
+ufsdirhash_destroy(struct dirhash *dh)
+{
+ doff_t **hash;
+ u_int8_t *blkfree;
+ int i, mem, narrays;
+
+ KASSERT(dh->dh_hash != NULL, ("dirhash: NULL hash on list"));
+
+ /* Remove it from the list and detach its memory. */
+ TAILQ_REMOVE(&ufsdirhash_list, dh, dh_list);
+ dh->dh_onlist = 0;
+ hash = dh->dh_hash;
+ dh->dh_hash = NULL;
+ blkfree = dh->dh_blkfree;
+ dh->dh_blkfree = NULL;
+ narrays = dh->dh_narrays;
+ mem = dh->dh_memreq;
+ dh->dh_memreq = 0;
+
+ /* Unlock everything, free the detached memory. */
+ ufsdirhash_release(dh);
+ DIRHASHLIST_UNLOCK();
+ for (i = 0; i < narrays; i++)
+ DIRHASH_BLKFREE(hash[i]);
+ free(hash, M_DIRHASH);
+ free(blkfree, M_DIRHASH);
+
+ /* Account for the returned memory. */
+ DIRHASHLIST_LOCK();
+ ufs_dirhashmem -= mem;
+
+ return (mem);
+}
+
+/*
* Try to free up `wanted' bytes by stealing memory from existing
* dirhashes. Returns zero with list locked if successful.
*/
@@ -1157,9 +1214,6 @@ static int
ufsdirhash_recycle(int wanted)
{
struct dirhash *dh;
- doff_t **hash;
- u_int8_t *blkfree;
- int i, mem, narrays;
DIRHASHLIST_LOCK();
dh = TAILQ_FIRST(&ufsdirhash_list);
@@ -1177,36 +1231,59 @@ ufsdirhash_recycle(int wanted)
dh = TAILQ_NEXT(dh, dh_list);
continue;
}
- KASSERT(dh->dh_hash != NULL, ("dirhash: NULL hash on list"));
- /* Remove it from the list and detach its memory. */
- TAILQ_REMOVE(&ufsdirhash_list, dh, dh_list);
- dh->dh_onlist = 0;
- hash = dh->dh_hash;
- dh->dh_hash = NULL;
- blkfree = dh->dh_blkfree;
- dh->dh_blkfree = NULL;
- narrays = dh->dh_narrays;
- mem = dh->dh_memreq;
- dh->dh_memreq = 0;
-
- /* Unlock everything, free the detached memory. */
- ufsdirhash_release(dh);
- DIRHASHLIST_UNLOCK();
- for (i = 0; i < narrays; i++)
- DIRHASH_BLKFREE(hash[i]);
- free(hash, M_DIRHASH);
- free(blkfree, M_DIRHASH);
-
- /* Account for the returned memory, and repeat if necessary. */
- DIRHASHLIST_LOCK();
- ufs_dirhashmem -= mem;
+ ufsdirhash_destroy(dh);
+
+ /* Repeat if necessary. */
dh = TAILQ_FIRST(&ufsdirhash_list);
}
/* Success; return with list locked. */
return (0);
}
+/*
+ * Callback that frees some dirhashes when the system is low on virtual memory.
+ */
+static void
+ufsdirhash_lowmem()
+{
+ struct dirhash *dh;
+ int memfreed = 0;
+ /* XXX: this 10% may need to be adjusted */
+ int memwanted = ufs_dirhashmem / 10;
+
+ ufs_dirhashlowmemcount++;
+
+ DIRHASHLIST_LOCK();
+ /*
+ * Delete dirhashes not used for more than ufs_dirhashreclaimage
+ * seconds. If we can't get a lock on the dirhash, it will be skipped.
+ */
+ for (dh = TAILQ_FIRST(&ufsdirhash_list); dh != NULL; dh =
+ TAILQ_NEXT(dh, dh_list)) {
+ if (!sx_try_xlock(&dh->dh_lock))
+ continue;
+ if (time_second - dh->dh_lastused > ufs_dirhashreclaimage)
+ memfreed += ufsdirhash_destroy(dh);
+ /* Unlock if we didn't delete the dirhash */
+ else
+ ufsdirhash_release(dh);
+ }
+
+ /*
+ * If not enough memory was freed, keep deleting hashes from the head
+ * of the dirhash list. The ones closest to the head should be the
+ * oldest.
+ */
+ for (dh = TAILQ_FIRST(&ufsdirhash_list); memfreed < memwanted &&
+ dh !=NULL; dh = TAILQ_NEXT(dh, dh_list)) {
+ if (!sx_try_xlock(&dh->dh_lock))
+ continue;
+ memfreed += ufsdirhash_destroy(dh);
+ }
+ DIRHASHLIST_UNLOCK();
+}
+
void
ufsdirhash_init()
@@ -1215,6 +1292,10 @@ ufsdirhash_init()
NULL, NULL, NULL, NULL, UMA_ALIGN_PTR, 0);
mtx_init(&ufsdirhash_mtx, "dirhash list", NULL, MTX_DEF);
TAILQ_INIT(&ufsdirhash_list);
+
+ /* Register a callback function to handle low memory signals */
+ EVENTHANDLER_REGISTER(vm_lowmem, ufsdirhash_lowmem, NULL,
+ EVENTHANDLER_PRI_FIRST);
}
void