#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/slaballoc.h>
#include <sys/mbuf.h>
#include <sys/vmmeter.h>
#include <sys/spinlock.h>
#include <sys/lock.h>
#include <sys/thread.h>
#include <sys/globaldata.h>
#include <sys/sysctl.h>
#include <sys/ktr.h>
#include <sys/malloc.h>
#include <vm/vm.h>
#include <vm/vm_param.h>
#include <vm/vm_kern.h>
#include <vm/vm_extern.h>
#include <vm/vm_object.h>
#include <vm/pmap.h>
#include <vm/vm_map.h>
#include <vm/vm_page.h>
#include <vm/vm_pageout.h>
#include <machine/cpu.h>
#include <sys/spinlock2.h>
#include <sys/thread2.h>
#include <sys/exislock2.h>
#include <vm/vm_page2.h>
#define MEMORY_STRING "ptr=%p type=%p size=%lu flags=%04x"
#define MEMORY_ARGS void *ptr, void *type, unsigned long size, int flags
#if !defined(KTR_MEMORY)
#define KTR_MEMORY KTR_ALL
#endif
KTR_INFO_MASTER(mem_obj);
KTR_INFO(KTR_MEMORY, mem_obj, malloc_beg, 0, "kmalloc_obj begin");
KTR_INFO(KTR_MEMORY, mem_obj, malloc_end, 1, MEMORY_STRING, MEMORY_ARGS);
#if 0
KTR_INFO(KTR_MEMORY, mem_obj, free_zero, 2, MEMORY_STRING, MEMORY_ARGS);
KTR_INFO(KTR_MEMORY, mem_obj, free_ovsz, 3, MEMORY_STRING, MEMORY_ARGS);
KTR_INFO(KTR_MEMORY, mem_obj, free_ovsz_delayed, 4, MEMORY_STRING, MEMORY_ARGS);
KTR_INFO(KTR_MEMORY, mem_obj, free_chunk, 5, MEMORY_STRING, MEMORY_ARGS);
KTR_INFO(KTR_MEMORY, mem_obj, free_request, 6, MEMORY_STRING, MEMORY_ARGS);
KTR_INFO(KTR_MEMORY, mem_obj, free_rem_beg, 7, MEMORY_STRING, MEMORY_ARGS);
KTR_INFO(KTR_MEMORY, mem_obj, free_rem_end, 8, MEMORY_STRING, MEMORY_ARGS);
#endif
KTR_INFO(KTR_MEMORY, mem_obj, free_beg, 9, "kfree_obj begin");
KTR_INFO(KTR_MEMORY, mem_obj, free_end, 10, "kfree_obj end");
#define logmemory(name, ptr, type, size, flags) \
KTR_LOG(mem_obj_ ## name, ptr, type, size, flags)
#define logmemory_quick(name) \
KTR_LOG(mem_obj_ ## name)
__read_frequently static int KMGDMaxFreeSlabs = KMGD_MAXFREESLABS;
SYSCTL_INT(_kern, OID_AUTO, kzone_cache, CTLFLAG_RW, &KMGDMaxFreeSlabs, 0, "");
__read_frequently static int kzone_bretire = 4;
SYSCTL_INT(_kern, OID_AUTO, kzone_bretire, CTLFLAG_RW, &kzone_bretire, 0, "");
__read_frequently static int kzone_debug;
SYSCTL_INT(_kern, OID_AUTO, kzone_debug, CTLFLAG_RW, &kzone_debug, 0, "");
__read_frequently struct kmalloc_slab kslab_dummy;
static void malloc_slab_destroy(struct malloc_type *type,
struct kmalloc_slab **slabp);
static __noinline
struct kmalloc_slab *
gslab_cache(struct kmalloc_slab *slab)
{
struct kmalloc_slab *save;
struct kmalloc_slab *next;
struct kmalloc_slab *res;
struct kmalloc_slab **resp;
struct kmalloc_slab **slabp;
globaldata_t rgd;
size_t count;
int cpuid;
res = NULL;
resp = &res;
KKASSERT(((uintptr_t)slab & KMALLOC_SLAB_MASK) == 0);
while (slab) {
cpuid = slab->orig_cpuid;
rgd = globaldata_find(cpuid);
KKASSERT(((uintptr_t)slab & KMALLOC_SLAB_MASK) == 0);
if (rgd->gd_kmslab.free_count >= KMGDMaxFreeSlabs) {
*resp = slab;
resp = &slab->next;
slab = slab->next;
continue;
}
KKASSERT(((uintptr_t)slab & KMALLOC_SLAB_MASK) == 0);
slabp = &slab->next;
count = 1;
slab->type = NULL;
while ((next = *slabp) != NULL &&
next->orig_cpuid == cpuid &&
rgd->gd_kmslab.free_count + count < KMGDMaxFreeSlabs)
{
KKASSERT(((uintptr_t)next & KMALLOC_SLAB_MASK) == 0);
next->type = NULL;
++count;
slabp = &next->next;
}
*slabp = NULL;
atomic_add_long(&rgd->gd_kmslab.free_count, count);
save = rgd->gd_kmslab.remote_free_slabs;
for (;;) {
KKASSERT(((uintptr_t)save & KMALLOC_SLAB_MASK) == 0);
*slabp = save;
cpu_ccfence();
if (atomic_fcmpset_ptr(
&rgd->gd_kmslab.remote_free_slabs,
&save, slab))
{
break;
}
}
slab = next;
}
*resp = NULL;
return res;
}
static __inline
struct kmalloc_slab *
gslab_alloc(globaldata_t gd)
{
struct kmalloc_slab *slab;
slab = gd->gd_kmslab.free_slabs;
if (slab == NULL) {
slab = atomic_swap_ptr(
(volatile void **)&gd->gd_kmslab.remote_free_slabs,
NULL);
KKASSERT(((uintptr_t)slab & KMALLOC_SLAB_MASK) == 0);
}
if (slab) {
gd->gd_kmslab.free_slabs = slab->next;
slab->next = NULL;
atomic_add_long(&gd->gd_kmslab.free_count, -1);
KKASSERT(((uintptr_t)slab & KMALLOC_SLAB_MASK) == 0);
}
return slab;
}
void
malloc_mgt_init(struct malloc_type *type __unused,
struct kmalloc_mgt *mgt, size_t size)
{
size_t offset;
size_t count;
bzero(mgt, sizeof(*mgt));
spin_init(&mgt->spin, "kmmgt");
mgt->active = &kslab_dummy;
mgt->alternate = &kslab_dummy;
mgt->empty_tailp = &mgt->empty;
offset = offsetof(struct kmalloc_slab, fobjs[0]);
count = (KMALLOC_SLAB_SIZE - offset) / (size + sizeof(void *));
offset = offsetof(struct kmalloc_slab, fobjs[count]);
offset = __VM_CACHELINE_ALIGN(offset);
while (offset + size * count > KMALLOC_SLAB_SIZE) {
--count;
offset = offsetof(struct kmalloc_slab, fobjs[count]);
offset = __VM_CACHELINE_ALIGN(offset);
KKASSERT (offset + size * count <= KMALLOC_SLAB_SIZE);
}
mgt->slab_offset = offset;
mgt->slab_count = count;
}
void
malloc_mgt_relocate(struct kmalloc_mgt *src, struct kmalloc_mgt *dst)
{
struct kmalloc_slab **slabp;
spin_init(&dst->spin, "kmmgt");
slabp = &dst->empty;
while (*slabp) {
slabp = &(*slabp)->next;
}
dst->empty_tailp = slabp;
}
void
malloc_mgt_uninit(struct malloc_type *type, struct kmalloc_mgt *mgt)
{
if (mgt->active != &kslab_dummy)
malloc_slab_destroy(type, &mgt->active);
mgt->active = NULL;
if (mgt->alternate != &kslab_dummy)
malloc_slab_destroy(type, &mgt->alternate);
mgt->alternate = NULL;
malloc_slab_destroy(type, &mgt->partial);
malloc_slab_destroy(type, &mgt->full);
malloc_slab_destroy(type, &mgt->empty);
mgt->npartial = 0;
mgt->nfull = 0;
mgt->nempty = 0;
mgt->empty_tailp = &mgt->empty;
spin_uninit(&mgt->spin);
}
static void
malloc_slab_destroy(struct malloc_type *type, struct kmalloc_slab **slabp)
{
struct kmalloc_slab *slab;
struct kmalloc_slab *base;
struct kmalloc_slab **basep;
size_t delta;
if (*slabp == NULL)
return;
base = NULL;
basep = &base;
while ((slab = *slabp) != NULL) {
KKASSERT(((uintptr_t)slab & KMALLOC_SLAB_MASK) == 0);
delta = slab->findex - slab->aindex;
if (delta == slab->ncount) {
*slabp = slab->next;
*basep = slab;
basep = &slab->next;
} else {
kprintf("%s: slab %p %zd objects "
"were still allocated\n",
type->ks_shortdesc, slab,
slab->ncount - delta);
slabp = &slab->next;
}
}
*basep = NULL;
if (base == NULL)
return;
base = gslab_cache(base);
while ((slab = base) != NULL) {
base = slab->next;
slab->next = (void *)(uintptr_t)-1;
kmem_slab_free(slab, KMALLOC_SLAB_SIZE);
}
}
static int
malloc_mgt_poll_empty_locked(struct kmalloc_mgt *ggm, int count)
{
struct kmalloc_slab *marker;
struct kmalloc_slab *slab;
size_t delta;
int got_something;
if (ggm->empty == NULL)
return 0;
got_something = 0;
marker = ggm->empty;
while (count-- && (slab = ggm->empty) != NULL) {
ggm->empty = slab->next;
slab->next = NULL;
--ggm->nempty;
if (ggm->empty_tailp == &slab->next)
ggm->empty_tailp = &ggm->empty;
delta = slab->findex - slab->aindex;
if (delta == slab->ncount) {
KKASSERT(slab->next == NULL);
exis_terminate(&slab->exis);
slab->next = ggm->full;
ggm->full = slab;
got_something = 1;
++ggm->nfull;
} else if (delta) {
KKASSERT(slab->next == NULL);
slab->next = ggm->partial;
ggm->partial = slab;
got_something = 1;
++ggm->npartial;
} else {
KKASSERT(slab->next == NULL);
*ggm->empty_tailp = slab;
ggm->empty_tailp = &slab->next;
++ggm->nempty;
if (ggm->empty == marker)
break;
}
}
return got_something;
}
int
malloc_mgt_poll(struct malloc_type *type)
{
struct kmalloc_mgt *ggm;
struct kmalloc_slab *slab;
struct kmalloc_slab **slabp;
struct kmalloc_slab *base;
struct kmalloc_slab **basep;
size_t delta;
int donext;
int count;
int retired;
if ((type->ks_flags & KSF_OBJSIZE) == 0)
return 1;
ggm = &type->ks_mgt;
spin_lock(&ggm->spin);
malloc_mgt_poll_empty_locked(ggm, 16);
base = NULL;
basep = &base;
count = ggm->nfull;
retired = 0;
cpu_ccfence();
if (count > KMALLOC_MAXFREEMAGS) {
slabp = &ggm->full;
count -= KMALLOC_MAXFREEMAGS;
if (count > 16)
count = 16;
while (count && (slab = *slabp) != NULL) {
delta = slab->findex - slab->aindex;
if (delta == slab->ncount &&
slab->xindex == slab->findex &&
exis_freeable(&slab->exis))
{
*slabp = slab->next;
*basep = slab;
basep = &slab->next;
--ggm->nfull;
++ggm->gcache_count;
if (++retired == kzone_bretire)
break;
} else {
slabp = &slab->next;
}
--count;
}
*basep = NULL;
donext = (*slabp == NULL);
} else {
donext = 1;
}
spin_unlock(&ggm->spin);
if (retired) {
if (kzone_debug) {
kprintf("kmalloc_poll: %s retire %d\n",
type->ks_shortdesc, retired);
}
base = gslab_cache(base);
while ((slab = base) != NULL) {
base = base->next;
slab->next = NULL;
kmem_slab_free(slab, KMALLOC_SLAB_SIZE);
}
}
return donext;
}
#ifdef KMALLOC_CHECK_DOUBLE_FREE
static __inline void
bmap_set(struct kmalloc_slab *slab, void *obj)
{
uint64_t *ptr;
uint64_t mask;
size_t i = (((uintptr_t)obj & KMALLOC_SLAB_MASK) - slab->offset) /
slab->objsize;
ptr = &slab->bmap[i >> 6];
mask = (uint64_t)1U << (i & 63);
KKASSERT(i < slab->ncount && (*ptr & mask) == 0);
atomic_set_64(ptr, mask);
}
static __inline void
bmap_clr(struct kmalloc_slab *slab, void *obj)
{
uint64_t *ptr;
uint64_t mask;
size_t i = (((uintptr_t)obj & KMALLOC_SLAB_MASK) - slab->offset) /
slab->objsize;
ptr = &slab->bmap[i >> 6];
mask = (uint64_t)1U << (i & 63);
KKASSERT(i < slab->ncount && (*ptr & mask) != 0);
atomic_clear_64(ptr, mask);
}
#endif
#if 0
static void
mgt_cleanup(struct kmalloc_mgt *mgt)
{
#if 0
struct kmalloc_slab **slabp;
struct kmalloc_slab *slab;
size_t delta;
size_t total;
#endif
}
#endif
#ifdef SLAB_DEBUG
void *
_kmalloc_obj_debug(unsigned long size, struct malloc_type *type, int flags,
const char *file, int line)
#else
void *
_kmalloc_obj(unsigned long size, struct malloc_type *type, int flags)
#endif
{
struct kmalloc_slab *slab;
struct kmalloc_use *use;
struct kmalloc_mgt *mgt;
struct kmalloc_mgt *ggm;
globaldata_t gd;
void *obj;
size_t delta;
while (__predict_false(type->ks_loosememuse >= type->ks_limit)) {
long ttl;
int n;
for (n = ttl = 0; n < ncpus; ++n)
ttl += type->ks_use[n].memuse;
type->ks_loosememuse = ttl;
if ((ssize_t)ttl < 0)
ttl = 0;
if (ttl >= type->ks_limit) {
if (flags & M_NULLOK)
return(NULL);
panic("%s: malloc limit exceeded", type->ks_shortdesc);
}
}
crit_enter();
logmemory_quick(malloc_beg);
KKASSERT(size == type->ks_objsize);
gd = mycpu;
use = &type->ks_use[gd->gd_cpuid];
retry:
mgt = &use->mgt;
slab = mgt->active;
delta = slab->findex - slab->aindex;
if (__predict_true(delta != 0)) {
size_t i;
i = slab->aindex % slab->ncount;
obj = slab->fobjs[i];
if (__predict_true(obj != NULL)) {
slab->fobjs[i] = NULL;
++slab->aindex;
#ifdef KMALLOC_CHECK_DOUBLE_FREE
bmap_set(slab, obj);
#endif
goto found;
}
}
slab = mgt->alternate;
delta = slab->findex - slab->aindex;
if (__predict_true(delta != 0)) {
size_t i;
mgt->alternate = mgt->active;
mgt->active = slab;
i = slab->aindex % slab->ncount;
obj = slab->fobjs[i];
if (__predict_true(obj != NULL)) {
slab->fobjs[i] = NULL;
++slab->aindex;
#ifdef KMALLOC_CHECK_DOUBLE_FREE
bmap_set(slab, obj);
#endif
goto found;
}
}
ggm = &type->ks_mgt;
spin_lock(&ggm->spin);
rerotate:
if (ggm->partial) {
slab = mgt->alternate;
mgt->alternate = mgt->active;
mgt->active = ggm->partial;
ggm->partial = ggm->partial->next;
mgt->active->next = NULL;
--ggm->npartial;
if (slab != &kslab_dummy) {
KKASSERT(slab->next == NULL);
*ggm->empty_tailp = slab;
ggm->empty_tailp = &slab->next;
++ggm->nempty;
}
spin_unlock(&ggm->spin);
goto retry;
}
if (ggm->full) {
slab = mgt->alternate;
mgt->alternate = mgt->active;
mgt->active = ggm->full;
ggm->full = ggm->full->next;
mgt->active->next = NULL;
--ggm->nfull;
exis_setlive(&mgt->active->exis);
if (slab != &kslab_dummy) {
KKASSERT(slab->next == NULL);
*ggm->empty_tailp = slab;
ggm->empty_tailp = &slab->next;
++ggm->nempty;
}
spin_unlock(&ggm->spin);
goto retry;
}
if (malloc_mgt_poll_empty_locked(ggm, 16))
goto rerotate;
spin_unlock(&ggm->spin);
if (gd->gd_kmslab.free_count == 0 || (slab = gslab_alloc(gd)) == NULL) {
slab = kmem_slab_alloc(KMALLOC_SLAB_SIZE, KMALLOC_SLAB_SIZE,
M_WAITOK);
}
bzero(slab, sizeof(*slab));
KKASSERT(offsetof(struct kmalloc_slab, fobjs[use->mgt.slab_count]) <=
use->mgt.slab_offset);
obj = (char *)slab + use->mgt.slab_offset;
slab->type = type;
slab->orig_cpuid = gd->gd_cpuid;
slab->ncount = use->mgt.slab_count;
slab->offset = use->mgt.slab_offset;
slab->objsize = type->ks_objsize;
slab->aindex = 0;
slab->findex = slab->ncount;
slab->xindex = slab->ncount;
for (delta = 0; delta < slab->ncount; ++delta) {
slab->fobjs[delta] = obj;
obj = (char *)obj + type->ks_objsize;
}
#if 0
KKASSERT(((((uintptr_t)obj - 1) ^ (uintptr_t)slab) &
~KMALLOC_SLAB_MASK) == 0);
#endif
KASSERT(((((uintptr_t)obj - 1) ^ (uintptr_t)slab) &
~KMALLOC_SLAB_MASK) == 0, ("SLAB %p ncount %zd objsize %zd obj=%p\n", slab, slab->ncount, slab->objsize, obj));
slab->magic = KMALLOC_SLAB_MAGIC;
spin_init(&slab->spin, "kmslb");
spin_lock(&ggm->spin);
if (mgt->alternate != &kslab_dummy) {
struct kmalloc_slab *slab_tmp;
slab_tmp = mgt->alternate;
slab_tmp->next = NULL;
*ggm->empty_tailp = slab_tmp;
ggm->empty_tailp = &slab_tmp->next;
++ggm->nempty;
}
mgt->alternate = mgt->active;
mgt->active = slab;
spin_unlock(&ggm->spin);
goto retry;
found:
++use->inuse;
++use->calls;
use->memuse += size;
use->loosememuse += size;
if (__predict_false(use->loosememuse >= KMALLOC_LOOSE_SIZE)) {
type->ks_loosememuse += use->loosememuse;
use->loosememuse = 0;
}
if (__predict_false(flags & M_ZERO))
bzero(obj, size);
crit_exit();
logmemory(malloc_end, NULL, type, size, flags);
return(obj);
}
void
_kfree_obj(void *obj, struct malloc_type *type)
{
struct kmalloc_slab *slab;
struct kmalloc_use *use;
globaldata_t gd;
size_t delta;
size_t i;
logmemory_quick(free_beg);
gd = mycpu;
slab = (void *)((uintptr_t)obj & ~KMALLOC_SLAB_MASK);
delta = slab->findex - slab->aindex;
KKASSERT(slab->magic == KMALLOC_SLAB_MAGIC && delta != slab->ncount);
use = &slab->type->ks_use[gd->gd_cpuid];
--use->inuse;
use->memuse -= slab->objsize;
i = atomic_fetchadd_long(&slab->findex, 1);
i = i % slab->ncount;
if (slab->fobjs[i] != NULL) {
kprintf("_kfree_obj failure %zd/%zd/%zd\n",
slab->aindex, slab->findex, slab->ncount);
}
#ifdef KMALLOC_CHECK_DOUBLE_FREE
bmap_clr(slab, obj);
#endif
KKASSERT(slab->fobjs[i] == NULL);
slab->fobjs[i] = obj;
atomic_add_long(&slab->xindex, 1);
logmemory_quick(free_end);
}