#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/systm.h>
#include <sys/callout.h>
#include <sys/globaldata.h>
#include <sys/malloc.h>
#include <sys/queue.h>
#include <sys/objcache.h>
#include <sys/spinlock.h>
#include <sys/thread.h>
#include <sys/thread2.h>
#include <sys/spinlock2.h>
#include <sys/sysctl.h>
static MALLOC_DEFINE(M_OBJCACHE, "objcache", "Object Cache");
static MALLOC_DEFINE(M_OBJMAG, "objcache mag", "Object Cache Magazine");
#define INITIAL_MAG_CAPACITY 64
struct magazine {
int rounds;
int capacity;
SLIST_ENTRY(magazine) nextmagazine;
void *objects[];
};
SLIST_HEAD(magazinelist, magazine);
#define MAGAZINE_HDRSIZE __offsetof(struct magazine, objects[0])
#define MAGAZINE_CAPACITY_MAX 4096
#define MAGAZINE_CAPACITY_MIN 4
struct magazinedepot {
struct magazinelist fullmagazines;
struct magazinelist emptymagazines;
int magcapacity;
struct spinlock spin;
int unallocated_objects;
int cluster_limit;
int waiting;
int contested;
} __cachealign;
struct percpu_objcache {
struct magazine *loaded_magazine;
struct magazine *previous_magazine;
u_long gets_cumulative;
u_long gets_null;
u_long allocs_cumulative;
u_long puts_cumulative;
u_long gets_exhausted;
#ifdef notyet
u_long puts_othercluster;
#endif
int waiting;
} __cachealign;
#define MAXCLUSTERS 1
#define myclusterid 0
#define CLUSTER_OF(obj) 0
struct objcache_desc {
LIST_ENTRY(objcache_desc) next;
struct objcache *objcache;
int total_objects;
int reserved;
char name[OBJCACHE_NAMELEN];
};
struct objcache {
objcache_ctor_fn *ctor;
objcache_dtor_fn *dtor;
void *privdata;
objcache_alloc_fn *alloc;
objcache_free_fn *free;
void *allocator_args;
struct objcache_desc *desc;
struct magazinedepot depot[MAXCLUSTERS];
struct percpu_objcache cache_percpu[];
};
SYSCTL_NODE(_kern, OID_AUTO, objcache, CTLFLAG_RW, 0, "objcache");
static struct spinlock objcachelist_spin;
static LIST_HEAD(objcachelist, objcache_desc) allobjcaches;
static int magazine_capmin;
static int magazine_capmax;
static struct magazine *
mag_alloc(int capacity)
{
struct magazine *mag;
int size;
size = __offsetof(struct magazine, objects[capacity]);
KASSERT(size > 0 && (size & __VM_CACHELINE_MASK) == 0,
("magazine size is not multiple cache line size"));
mag = kmalloc(size, M_OBJMAG, M_INTWAIT | M_ZERO | M_CACHEALIGN);
mag->capacity = capacity;
mag->rounds = 0;
return (mag);
}
static int
mag_capacity_align(int mag_capacity)
{
int mag_size;
mag_size = __VM_CACHELINE_ALIGN(
__offsetof(struct magazine, objects[mag_capacity]));
mag_capacity = (mag_size - MAGAZINE_HDRSIZE) / sizeof(void *);
return mag_capacity;
}
static void
null_dtor(void *obj, void *privdata)
{
}
static boolean_t
null_ctor(void *obj, void *privdata, int ocflags)
{
return TRUE;
}
struct objcache *
objcache_create(const char *name, int cluster_limit, int nom_cache,
objcache_ctor_fn *ctor, objcache_dtor_fn *dtor, void *privdata,
objcache_alloc_fn *alloc, objcache_free_fn *free,
void *allocator_args)
{
struct objcache_desc *desc;
struct objcache *oc;
struct magazinedepot *depot;
int cpuid;
int nmagdepot;
int mag_capacity;
int i;
desc = kmalloc(sizeof(*desc), M_OBJCACHE, M_WAITOK | M_ZERO);
oc = kmalloc(__offsetof(struct objcache, cache_percpu[ncpus]),
M_OBJCACHE,
M_WAITOK | M_ZERO | M_CACHEALIGN);
oc->ctor = ctor ? ctor : null_ctor;
oc->dtor = dtor ? dtor : null_dtor;
oc->privdata = privdata;
oc->alloc = alloc;
oc->free = free;
oc->allocator_args = allocator_args;
oc->desc = desc;
desc->objcache = oc;
strlcpy(desc->name, name, sizeof(desc->name));
depot = &oc->depot[0];
spin_init(&depot->spin, "objcachedepot");
SLIST_INIT(&depot->fullmagazines);
SLIST_INIT(&depot->emptymagazines);
if (nom_cache == 0)
nom_cache = cluster_limit / 2;
if (cluster_limit && nom_cache > cluster_limit)
nom_cache = cluster_limit;
if (nom_cache == 0)
nom_cache = INITIAL_MAG_CAPACITY * 2;
mag_capacity = mag_capacity_align(nom_cache / (ncpus + 1) / 2 + 1);
if (mag_capacity > magazine_capmax)
mag_capacity = magazine_capmax;
else if (mag_capacity < magazine_capmin)
mag_capacity = magazine_capmin;
depot->magcapacity = mag_capacity;
if (cluster_limit == 0) {
depot->unallocated_objects = OBJCACHE_UNLIMITED;
} else {
depot->unallocated_objects = ncpus * mag_capacity * 2 +
cluster_limit;
}
desc->total_objects = depot->unallocated_objects;
depot->cluster_limit = cluster_limit;
for (cpuid = 0; cpuid < ncpus; cpuid++) {
struct percpu_objcache *cache_percpu = &oc->cache_percpu[cpuid];
cache_percpu->loaded_magazine = mag_alloc(mag_capacity);
cache_percpu->previous_magazine = mag_alloc(mag_capacity);
}
nmagdepot = nom_cache / mag_capacity + 1;
if (nmagdepot < 2)
nmagdepot = 2;
for (i = 0; i < nmagdepot; i++) {
struct magazine *mag = mag_alloc(mag_capacity);
SLIST_INSERT_HEAD(&depot->emptymagazines, mag, nextmagazine);
}
spin_lock(&objcachelist_spin);
LIST_INSERT_HEAD(&allobjcaches, desc, next);
spin_unlock(&objcachelist_spin);
return (oc);
}
void
objcache_set_cluster_limit(struct objcache *oc, int cluster_limit)
{
struct magazinedepot *depot;
depot = &oc->depot[myclusterid];
if (depot->cluster_limit != cluster_limit) {
int delta;
spin_lock(&depot->spin);
delta = cluster_limit - depot->cluster_limit;
depot->unallocated_objects += delta;
depot->cluster_limit = cluster_limit;
spin_unlock(&depot->spin);
wakeup(depot);
oc->desc->total_objects += delta;
}
}
struct objcache *
objcache_create_simple(malloc_type_t mtype, size_t objsize)
{
struct objcache_malloc_args *margs;
struct objcache *oc;
margs = kmalloc(sizeof(*margs), M_OBJCACHE, M_WAITOK|M_ZERO);
margs->objsize = objsize;
margs->mtype = mtype;
oc = objcache_create(mtype->ks_shortdesc, 0, 0,
NULL, NULL, NULL,
objcache_malloc_alloc, objcache_malloc_free,
margs);
return (oc);
}
struct objcache *
objcache_create_mbacked(malloc_type_t mtype, size_t objsize,
int cluster_limit, int nom_cache,
objcache_ctor_fn *ctor, objcache_dtor_fn *dtor,
void *privdata)
{
struct objcache_malloc_args *margs;
struct objcache *oc;
margs = kmalloc(sizeof(*margs), M_OBJCACHE, M_WAITOK|M_ZERO);
margs->objsize = objsize;
margs->mtype = mtype;
oc = objcache_create(mtype->ks_shortdesc,
cluster_limit, nom_cache,
ctor, dtor, privdata,
objcache_malloc_alloc, objcache_malloc_free,
margs);
return(oc);
}
#define MAGAZINE_EMPTY(mag) (mag->rounds == 0)
#define MAGAZINE_NOTEMPTY(mag) (mag->rounds != 0)
#define MAGAZINE_FULL(mag) (mag->rounds == mag->capacity)
#define swap(x, y) ({ struct magazine *t = x; x = y; y = t; })
void *
objcache_get(struct objcache *oc, int ocflags)
{
struct percpu_objcache *cpucache = &oc->cache_percpu[mycpuid];
struct magazine *loadedmag;
struct magazine *emptymag;
void *obj;
struct magazinedepot *depot;
KKASSERT((ocflags & M_ZERO) == 0);
crit_enter();
++cpucache->gets_cumulative;
retry:
loadedmag = cpucache->loaded_magazine;
if (MAGAZINE_NOTEMPTY(loadedmag)) {
obj = loadedmag->objects[--loadedmag->rounds];
crit_exit();
return (obj);
}
if (MAGAZINE_NOTEMPTY(cpucache->previous_magazine)) {
swap(cpucache->loaded_magazine, cpucache->previous_magazine);
loadedmag = cpucache->loaded_magazine;
obj = loadedmag->objects[--loadedmag->rounds];
crit_exit();
return (obj);
}
depot = &oc->depot[myclusterid];
spin_lock(&depot->spin);
if (MAGAZINE_NOTEMPTY(cpucache->loaded_magazine) ||
MAGAZINE_NOTEMPTY(cpucache->previous_magazine)
) {
spin_unlock(&depot->spin);
goto retry;
}
if (!SLIST_EMPTY(&depot->fullmagazines)) {
emptymag = cpucache->previous_magazine;
cpucache->previous_magazine = cpucache->loaded_magazine;
cpucache->loaded_magazine = SLIST_FIRST(&depot->fullmagazines);
SLIST_REMOVE_HEAD(&depot->fullmagazines, nextmagazine);
KKASSERT(MAGAZINE_EMPTY(emptymag));
SLIST_INSERT_HEAD(&depot->emptymagazines,
emptymag, nextmagazine);
spin_unlock(&depot->spin);
goto retry;
}
if (__predict_true(depot->unallocated_objects > 0)) {
--depot->unallocated_objects;
spin_unlock(&depot->spin);
++cpucache->allocs_cumulative;
crit_exit();
obj = oc->alloc(oc->allocator_args, ocflags);
if (obj) {
if (oc->ctor(obj, oc->privdata, ocflags))
return (obj);
oc->free(obj, oc->allocator_args);
obj = NULL;
}
if (obj == NULL) {
spin_lock(&depot->spin);
++depot->unallocated_objects;
spin_unlock(&depot->spin);
if (depot->waiting)
wakeup(depot);
crit_enter();
++cpucache->gets_null;
--cpucache->gets_cumulative;
crit_exit();
}
return(obj);
}
if (__predict_false(cpucache->gets_exhausted++ == 0)) {
kprintf("Warning: objcache(%s) exhausted on cpu%d!\n",
oc->desc->name, mycpuid);
}
if ((ocflags & (M_WAITOK|M_NULLOK)) == M_WAITOK) {
++cpucache->waiting;
++depot->waiting;
ssleep(depot, &depot->spin, 0, "objcache_get", 0);
--cpucache->waiting;
--depot->waiting;
spin_unlock(&depot->spin);
goto retry;
}
++cpucache->gets_null;
--cpucache->gets_cumulative;
crit_exit();
spin_unlock(&depot->spin);
return (NULL);
}
void *
objcache_malloc_alloc(void *allocator_args, int ocflags)
{
struct objcache_malloc_args *alloc_args = allocator_args;
return (kmalloc(alloc_args->objsize, alloc_args->mtype,
ocflags & OC_MFLAGS));
}
void *
objcache_malloc_alloc_zero(void *allocator_args, int ocflags)
{
struct objcache_malloc_args *alloc_args = allocator_args;
return (kmalloc(alloc_args->objsize, alloc_args->mtype,
(ocflags & OC_MFLAGS) | M_ZERO));
}
void
objcache_malloc_free(void *obj, void *allocator_args)
{
struct objcache_malloc_args *alloc_args = allocator_args;
kfree(obj, alloc_args->mtype);
}
void *
objcache_nop_alloc(void *allocator_args, int ocflags)
{
return (NULL);
}
void
objcache_nop_free(void *obj, void *allocator_args)
{
}
void
objcache_put(struct objcache *oc, void *obj)
{
struct percpu_objcache *cpucache = &oc->cache_percpu[mycpuid];
struct magazine *loadedmag;
struct magazinedepot *depot;
crit_enter();
++cpucache->puts_cumulative;
if (CLUSTER_OF(obj) != myclusterid) {
#ifdef notyet
++cpucache->puts_othercluster;
crit_exit();
return;
#endif
}
retry:
loadedmag = cpucache->loaded_magazine;
if (!MAGAZINE_FULL(loadedmag)) {
loadedmag->objects[loadedmag->rounds++] = obj;
if (cpucache->waiting)
wakeup_mycpu(&oc->depot[myclusterid]);
crit_exit();
return;
}
if (!MAGAZINE_FULL(cpucache->previous_magazine)) {
swap(cpucache->loaded_magazine, cpucache->previous_magazine);
loadedmag = cpucache->loaded_magazine;
loadedmag->objects[loadedmag->rounds++] = obj;
if (cpucache->waiting)
wakeup_mycpu(&oc->depot[myclusterid]);
crit_exit();
return;
}
depot = &oc->depot[myclusterid];
spin_lock(&depot->spin);
if (!SLIST_EMPTY(&depot->emptymagazines)) {
loadedmag = cpucache->previous_magazine;
cpucache->previous_magazine = cpucache->loaded_magazine;
cpucache->loaded_magazine = SLIST_FIRST(&depot->emptymagazines);
SLIST_REMOVE_HEAD(&depot->emptymagazines, nextmagazine);
if (MAGAZINE_EMPTY(loadedmag)) {
SLIST_INSERT_HEAD(&depot->emptymagazines,
loadedmag, nextmagazine);
spin_unlock(&depot->spin);
} else {
SLIST_INSERT_HEAD(&depot->fullmagazines,
loadedmag, nextmagazine);
spin_unlock(&depot->spin);
if (depot->waiting)
wakeup(depot);
}
goto retry;
}
++depot->unallocated_objects;
spin_unlock(&depot->spin);
if (depot->waiting)
wakeup(depot);
crit_exit();
oc->dtor(obj, oc->privdata);
oc->free(obj, oc->allocator_args);
}
void
objcache_dtor(struct objcache *oc, void *obj)
{
struct magazinedepot *depot;
depot = &oc->depot[myclusterid];
spin_lock(&depot->spin);
++depot->unallocated_objects;
spin_unlock(&depot->spin);
if (depot->waiting)
wakeup(depot);
oc->dtor(obj, oc->privdata);
oc->free(obj, oc->allocator_args);
}
static int
mag_purge(struct objcache *oc, struct magazine **magp, int freeit)
{
struct magazine *mag = *magp;
int count;
void *obj;
count = 0;
while (mag->rounds) {
obj = mag->objects[--mag->rounds];
oc->dtor(obj, oc->privdata);
oc->free(obj, oc->allocator_args);
++count;
if ((count & 15) == 0) {
crit_exit();
crit_enter();
}
if (*magp != mag) {
kprintf("mag_purge: mag ripped out\n");
break;
}
}
if (freeit) {
KKASSERT(*magp == mag);
*magp = NULL;
kfree(mag, M_OBJMAG);
}
return(count);
}
static void
maglist_disassociate(struct magazinedepot *depot, struct magazinelist *maglist,
struct magazinelist *tmplist, boolean_t purgeall)
{
struct magazine *mag;
while ((mag = SLIST_FIRST(maglist)) != NULL) {
SLIST_REMOVE_HEAD(maglist, nextmagazine);
SLIST_INSERT_HEAD(tmplist, mag, nextmagazine);
depot->unallocated_objects += mag->rounds;
}
}
static int
maglist_purge(struct objcache *oc, struct magazinelist *maglist)
{
struct magazine *mag;
int count = 0;
crit_enter();
while ((mag = SLIST_FIRST(maglist)) != NULL) {
SLIST_REMOVE_HEAD(maglist, nextmagazine);
count += mag_purge(oc, &mag, TRUE);
}
crit_exit();
return(count);
}
static void
depot_disassociate(struct magazinedepot *depot, struct magazinelist *tmplist)
{
maglist_disassociate(depot, &depot->fullmagazines, tmplist, TRUE);
maglist_disassociate(depot, &depot->emptymagazines, tmplist, TRUE);
}
boolean_t
objcache_reclaimlist(struct objcache *oclist[], int nlist)
{
struct objcache *oc;
struct percpu_objcache *cpucache;
struct magazinedepot *depot;
struct magazinelist tmplist;
int i, count;
SLIST_INIT(&tmplist);
for (i = 0; i < nlist; i++) {
oc = oclist[i];
cpucache = &oc->cache_percpu[mycpuid];
depot = &oc->depot[myclusterid];
crit_enter();
count = mag_purge(oc, &cpucache->loaded_magazine, FALSE);
if (count == 0)
count += mag_purge(oc, &cpucache->previous_magazine, FALSE);
crit_exit();
if (count > 0) {
spin_lock(&depot->spin);
depot->unallocated_objects += count;
spin_unlock(&depot->spin);
if (depot->waiting)
wakeup(depot);
return (TRUE);
}
spin_lock(&depot->spin);
maglist_disassociate(depot, &depot->fullmagazines,
&tmplist, FALSE);
spin_unlock(&depot->spin);
count = maglist_purge(oc, &tmplist);
if (count > 0) {
if (depot->waiting)
wakeup(depot);
return (TRUE);
}
}
return (FALSE);
}
void
objcache_destroy(struct objcache *oc)
{
struct objcache_desc *desc = oc->desc;
struct percpu_objcache *cache_percpu;
struct magazinedepot *depot;
int clusterid, cpuid;
struct magazinelist tmplist;
spin_lock(&objcachelist_spin);
LIST_REMOVE(desc, next);
spin_unlock(&objcachelist_spin);
SLIST_INIT(&tmplist);
for (clusterid = 0; clusterid < MAXCLUSTERS; clusterid++) {
depot = &oc->depot[clusterid];
spin_lock(&depot->spin);
depot_disassociate(depot, &tmplist);
spin_unlock(&depot->spin);
}
maglist_purge(oc, &tmplist);
for (cpuid = 0; cpuid < ncpus; cpuid++) {
cache_percpu = &oc->cache_percpu[cpuid];
crit_enter();
mag_purge(oc, &cache_percpu->loaded_magazine, TRUE);
mag_purge(oc, &cache_percpu->previous_magazine, TRUE);
crit_exit();
cache_percpu->loaded_magazine = NULL;
cache_percpu->previous_magazine = NULL;
}
kfree(desc, M_OBJCACHE);
kfree(oc, M_OBJCACHE);
}
static int
sysctl_ocstats(SYSCTL_HANDLER_ARGS)
{
struct objcache_stats stat;
struct objcache_desc marker, *desc;
int error;
memset(&marker, 0, sizeof(marker));
spin_lock(&objcachelist_spin);
LIST_INSERT_HEAD(&allobjcaches, &marker, next);
while ((desc = LIST_NEXT(&marker, next)) != NULL) {
u_long puts, unalloc;
int cpu;
LIST_REMOVE(&marker, next);
LIST_INSERT_AFTER(desc, &marker, next);
if (desc->total_objects == 0) {
continue;
}
memset(&stat, 0, sizeof(stat));
strlcpy(stat.oc_name, desc->name, sizeof(stat.oc_name));
stat.oc_limit = desc->total_objects;
unalloc = desc->objcache->depot[0].unallocated_objects;
puts = 0;
for (cpu = 0; cpu < ncpus; ++cpu) {
const struct percpu_objcache *cache;
cache = &desc->objcache->cache_percpu[cpu];
puts += cache->puts_cumulative;
stat.oc_requested += cache->gets_cumulative;
stat.oc_exhausted += cache->gets_exhausted;
stat.oc_failed += cache->gets_null;
stat.oc_allocated += cache->allocs_cumulative;
}
spin_unlock(&objcachelist_spin);
if (stat.oc_requested > puts)
stat.oc_used = stat.oc_requested - puts;
if (stat.oc_limit > unalloc + stat.oc_used) {
stat.oc_cached = stat.oc_limit -
(unalloc + stat.oc_used);
}
stat.oc_requested += stat.oc_failed;
error = SYSCTL_OUT(req, &stat, sizeof(stat));
spin_lock(&objcachelist_spin);
if (error)
break;
}
LIST_REMOVE(&marker, next);
spin_unlock(&objcachelist_spin);
return error;
}
SYSCTL_PROC(_kern_objcache, OID_AUTO, stats, (CTLTYPE_OPAQUE | CTLFLAG_RD),
0, 0, sysctl_ocstats, "S,objcache_stats", "objcache statistics");
static void
objcache_init(void)
{
spin_init(&objcachelist_spin, "objcachelist");
magazine_capmin = mag_capacity_align(MAGAZINE_CAPACITY_MIN);
magazine_capmax = mag_capacity_align(MAGAZINE_CAPACITY_MAX);
if (bootverbose) {
kprintf("objcache: magazine cap [%d, %d]\n",
magazine_capmin, magazine_capmax);
}
#if 0
callout_init_mp(&objcache_callout);
objcache_rebalance_period = 60 * hz;
callout_reset(&objcache_callout, objcache_rebalance_period,
objcache_timer, NULL);
#endif
}
SYSINIT(objcache, SI_BOOT2_OBJCACHE, SI_ORDER_FIRST, objcache_init, 0);