#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/lock.h>
#include <sys/thread.h>
#include <sys/globaldata.h>
#include <sys/sysctl.h>
#include <sys/ktr.h>
#include <sys/kthread.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/thread2.h>
#include <vm/vm_page2.h>
#if (__VM_CACHELINE_SIZE == 32)
#define CAN_CACHEALIGN(sz) ((sz) >= 256)
#elif (__VM_CACHELINE_SIZE == 64)
#define CAN_CACHEALIGN(sz) ((sz) >= 512)
#elif (__VM_CACHELINE_SIZE == 128)
#define CAN_CACHEALIGN(sz) ((sz) >= 1024)
#else
#error "unsupported cacheline size"
#endif
#define btokup(z) (&pmap_kvtom((vm_offset_t)(z))->ku_pagecnt)
#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(memory);
KTR_INFO(KTR_MEMORY, memory, malloc_beg, 0, "malloc begin");
KTR_INFO(KTR_MEMORY, memory, malloc_end, 1, MEMORY_STRING, MEMORY_ARGS);
KTR_INFO(KTR_MEMORY, memory, free_zero, 2, MEMORY_STRING, MEMORY_ARGS);
KTR_INFO(KTR_MEMORY, memory, free_ovsz, 3, MEMORY_STRING, MEMORY_ARGS);
KTR_INFO(KTR_MEMORY, memory, free_ovsz_delayed, 4, MEMORY_STRING, MEMORY_ARGS);
KTR_INFO(KTR_MEMORY, memory, free_chunk, 5, MEMORY_STRING, MEMORY_ARGS);
KTR_INFO(KTR_MEMORY, memory, free_request, 6, MEMORY_STRING, MEMORY_ARGS);
KTR_INFO(KTR_MEMORY, memory, free_rem_beg, 7, MEMORY_STRING, MEMORY_ARGS);
KTR_INFO(KTR_MEMORY, memory, free_rem_end, 8, MEMORY_STRING, MEMORY_ARGS);
KTR_INFO(KTR_MEMORY, memory, free_beg, 9, "free begin");
KTR_INFO(KTR_MEMORY, memory, free_end, 10, "free end");
#define logmemory(name, ptr, type, size, flags) \
KTR_LOG(memory_ ## name, ptr, type, size, flags)
#define logmemory_quick(name) \
KTR_LOG(memory_ ## name)
__read_frequently static int ZoneSize;
__read_frequently static int ZoneLimit;
__read_frequently static int ZonePageCount;
__read_frequently static uintptr_t ZoneMask;
__read_frequently struct malloc_type *kmemstatistics;
#if defined(INVARIANTS)
static void chunk_mark_allocated(SLZone *z, void *chunk);
static void chunk_mark_free(SLZone *z, void *chunk);
#else
#define chunk_mark_allocated(z, chunk)
#define chunk_mark_free(z, chunk)
#endif
#define ZONE_RELS_THRESH 32
#ifdef INVARIANTS
#define WEIRD_ADDR 0xdeadc0de
#endif
#define ZERO_LENGTH_PTR ((void *)-8)
MALLOC_DEFINE(M_CACHE, "cache", "Various Dynamically allocated caches");
MALLOC_DEFINE(M_DEVBUF, "devbuf", "device driver memory");
MALLOC_DEFINE(M_TEMP, "temp", "misc temporary data buffers");
MALLOC_DEFINE(M_DRM, "m_drm", "DRM memory allocations");
MALLOC_DEFINE(M_IP6OPT, "ip6opt", "IPv6 options");
MALLOC_DEFINE(M_IP6NDP, "ip6ndp", "IPv6 Neighbor Discovery");
static void kmeminit(void *dummy);
static void kmemfinishinit(void *dummy);
char *ZeroPage;
SYSINIT(kmem1, SI_BOOT1_ALLOCATOR, SI_ORDER_FIRST, kmeminit, NULL);
SYSINIT(kmem2, SI_BOOT2_POST_SMP, SI_ORDER_FIRST, kmemfinishinit, NULL);
#ifdef INVARIANTS
__read_frequently static int use_malloc_pattern;
SYSCTL_INT(_debug, OID_AUTO, use_malloc_pattern, CTLFLAG_RW,
&use_malloc_pattern, 0,
"Initialize memory to -1 if M_ZERO not specified");
__read_frequently static int32_t weirdary[16];
__read_frequently static int use_weird_array;
SYSCTL_INT(_debug, OID_AUTO, use_weird_array, CTLFLAG_RW,
&use_weird_array, 0,
"Initialize memory to weird values on kfree()");
#endif
__read_frequently static int ZoneRelsThresh = ZONE_RELS_THRESH;
SYSCTL_INT(_kern, OID_AUTO, zone_cache, CTLFLAG_RW, &ZoneRelsThresh, 0, "");
__read_frequently static int kzone_pollfreq = 1;
SYSCTL_INT(_kern, OID_AUTO, kzone_pollfreq, CTLFLAG_RW, &kzone_pollfreq, 0, "");
static struct spinlock kmemstat_spin =
SPINLOCK_INITIALIZER(&kmemstat_spin, "malinit");
static struct malloc_type *kmemstat_poll;
size_t
kmem_lim_size(void)
{
size_t limsize;
limsize = (size_t)vmstats.v_page_count * PAGE_SIZE;
if (limsize > KvaSize)
limsize = KvaSize;
return (limsize / (1024 * 1024));
}
static void
kmeminit(void *dummy)
{
size_t limsize;
int usesize;
#ifdef INVARIANTS
int i;
#endif
limsize = kmem_lim_size();
usesize = (int)(limsize * 1024);
if (ZoneRelsThresh == ZONE_RELS_THRESH) {
if (limsize >= 7 * 1024)
ZoneRelsThresh *= 2;
if (limsize >= 15 * 1024)
ZoneRelsThresh *= 2;
if (limsize >= 31 * 1024)
ZoneRelsThresh *= 2;
if (limsize >= 63 * 1024)
ZoneRelsThresh *= 2;
if (limsize >= 127 * 1024)
ZoneRelsThresh *= 2;
}
ZoneSize = ZALLOC_MIN_ZONE_SIZE;
while (ZoneSize < ZALLOC_MAX_ZONE_SIZE && (ZoneSize << 1) < usesize)
ZoneSize <<= 1;
ZoneLimit = ZoneSize / 4;
if (ZoneLimit > ZALLOC_ZONE_LIMIT)
ZoneLimit = ZALLOC_ZONE_LIMIT;
ZoneMask = ~(uintptr_t)(ZoneSize - 1);
ZonePageCount = ZoneSize / PAGE_SIZE;
#ifdef INVARIANTS
for (i = 0; i < NELEM(weirdary); ++i)
weirdary[i] = WEIRD_ADDR;
#endif
ZeroPage = kmem_slab_alloc(PAGE_SIZE, PAGE_SIZE, M_WAITOK|M_ZERO);
if (bootverbose)
kprintf("Slab ZoneSize set to %dKB\n", ZoneSize / 1024);
}
static void
kmemfinishinit(void *dummy)
{
if (ncpus > 32)
ZoneRelsThresh = ZoneRelsThresh * 32 / ncpus;
}
void
slab_gdinit(globaldata_t gd)
{
SLGlobalData *slgd;
int i;
slgd = &gd->gd_slab;
for (i = 0; i < NZONES; ++i)
TAILQ_INIT(&slgd->ZoneAry[i]);
TAILQ_INIT(&slgd->FreeZones);
TAILQ_INIT(&slgd->FreeOvZones);
}
void
malloc_init(void *data)
{
struct malloc_type *type = data;
struct kmalloc_use *use;
size_t limsize;
int n;
if (type->ks_magic != M_MAGIC)
panic("malloc type lacks magic");
if (type->ks_limit != 0)
return;
if (vmstats.v_page_count == 0)
panic("malloc_init not allowed before vm init");
limsize = kmem_lim_size() * (1024 * 1024);
type->ks_limit = limsize / 10;
if (type->ks_flags & KSF_OBJSIZE)
malloc_mgt_init(type, &type->ks_mgt, type->ks_objsize);
if (ncpus == 1)
use = &type->ks_use0;
else
use = kmalloc(ncpus * sizeof(*use), M_TEMP, M_WAITOK | M_ZERO);
if (type->ks_flags & KSF_OBJSIZE) {
for (n = 0; n < ncpus; ++n)
malloc_mgt_init(type, &use[n].mgt, type->ks_objsize);
}
spin_lock(&kmemstat_spin);
type->ks_next = kmemstatistics;
type->ks_use = use;
kmemstatistics = type;
spin_unlock(&kmemstat_spin);
}
void
malloc_uninit(void *data)
{
struct malloc_type *type = data;
struct malloc_type *t;
int i;
#ifdef INVARIANTS
long ttl;
#endif
if (type->ks_magic != M_MAGIC)
panic("malloc type lacks magic");
if (vmstats.v_page_count == 0)
panic("malloc_uninit not allowed before vm init");
if (type->ks_limit == 0)
panic("malloc_uninit on uninitialized type");
lwkt_synchronize_ipiqs("muninit");
spin_lock(&kmemstat_spin);
while (type->ks_flags & KSF_POLLING)
ssleep(type, &kmemstat_spin, 0, "kmuninit", 0);
if (kmemstat_poll == type)
kmemstat_poll = type->ks_next;
if (kmemstatistics == type) {
kmemstatistics = type->ks_next;
} else {
for (t = kmemstatistics; t->ks_next != NULL; t = t->ks_next) {
if (t->ks_next == type) {
t->ks_next = type->ks_next;
break;
}
}
}
type->ks_next = NULL;
type->ks_limit = 0;
spin_unlock(&kmemstat_spin);
#ifdef INVARIANTS
ttl = 0;
#endif
for (i = 0; i < ncpus; ++i) {
#ifdef INVARIANTS
ttl += type->ks_use[i].memuse;
#endif
if (type->ks_flags & KSF_OBJSIZE)
malloc_mgt_uninit(type, &type->ks_use[i].mgt);
}
if (type->ks_flags & KSF_OBJSIZE)
malloc_mgt_uninit(type, &type->ks_mgt);
#ifdef INVARIANTS
if (ttl) {
kprintf("malloc_uninit: %ld bytes of '%s' still allocated on cpu %d\n",
ttl, type->ks_shortdesc, i);
}
#endif
if (type->ks_use != &type->ks_use0) {
kfree(type->ks_use, M_TEMP);
type->ks_use = NULL;
}
}
static void
kmalloc_poller_thread(void)
{
struct malloc_type *type;
for (;;) {
int sticks;
sticks = kzone_pollfreq;
cpu_ccfence();
if (sticks > 0)
sticks = hz / sticks + 1;
else
sticks = hz;
tsleep((caddr_t)&sticks, 0, "kmslp", sticks);
spin_lock(&kmemstat_spin);
type = kmemstat_poll;
if (type == NULL)
type = kmemstatistics;
if (type) {
atomic_set_int(&type->ks_flags, KSF_POLLING);
spin_unlock(&kmemstat_spin);
if (malloc_mgt_poll(type)) {
spin_lock(&kmemstat_spin);
kmemstat_poll = type->ks_next;
} else {
spin_lock(&kmemstat_spin);
}
atomic_clear_int(&type->ks_flags, KSF_POLLING);
wakeup(type);
} else {
kmemstat_poll = NULL;
}
spin_unlock(&kmemstat_spin);
}
}
static struct thread *kmalloc_poller_td;
static struct kproc_desc kmalloc_poller_kp = {
"kmalloc_poller",
kmalloc_poller_thread,
&kmalloc_poller_td
};
SYSINIT(kmalloc_polller, SI_SUB_KTHREAD_PAGE, SI_ORDER_FIRST,
kproc_start, &kmalloc_poller_kp);
void
malloc_reinit_ncpus(void)
{
struct malloc_type *t;
struct kmalloc_use *use;
int n;
if (ncpus <= 1)
return;
for (t = kmemstatistics; t; t = t->ks_next) {
KKASSERT(t->ks_use == &t->ks_use0);
t->ks_use = kmalloc(sizeof(*use) * ncpus, M_TEMP, M_WAITOK|M_ZERO);
t->ks_use[0] = t->ks_use0;
if (t->ks_flags & KSF_OBJSIZE) {
malloc_mgt_relocate(&t->ks_use0.mgt, &t->ks_use[0].mgt);
for (n = 1; n < ncpus; ++n)
malloc_mgt_init(t, &t->ks_use[n].mgt, t->ks_objsize);
}
}
}
void
kmalloc_raise_limit(struct malloc_type *type, size_t bytes)
{
KKASSERT(type->ks_limit != 0);
if (bytes == 0)
bytes = KvaSize;
if (type->ks_limit < bytes)
type->ks_limit = bytes;
}
void
kmalloc_set_unlimited(struct malloc_type *type)
{
type->ks_limit = kmem_lim_size() * (1024 * 1024);
}
void
kmalloc_create(struct malloc_type **typep, const char *descr)
{
struct malloc_type *type;
if (*typep == NULL) {
type = kmalloc(sizeof(*type), M_TEMP, M_WAITOK | M_ZERO);
type->ks_magic = M_MAGIC;
type->ks_shortdesc = descr;
malloc_init(type);
*typep = type;
}
}
void
_kmalloc_create_obj(struct malloc_type **typep, const char *descr,
size_t objsize)
{
struct malloc_type *type;
if (*typep == NULL) {
type = kmalloc(sizeof(*type), M_TEMP, M_WAITOK | M_ZERO);
type->ks_magic = M_MAGIC;
type->ks_shortdesc = descr;
type->ks_flags = KSF_OBJSIZE;
type->ks_objsize = __VM_CACHELINE_ALIGN(objsize);
malloc_init(type);
*typep = type;
}
}
void
kmalloc_destroy(struct malloc_type **typep)
{
if (*typep != NULL) {
malloc_uninit(*typep);
kfree(*typep, M_TEMP);
*typep = NULL;
}
}
static __inline int
zoneindex(unsigned long *bytes, unsigned long *align)
{
unsigned int n = (unsigned int)*bytes;
if (n < 128) {
*bytes = n = (n + 7) & ~7;
*align = 8;
return(n / 8 - 1);
}
if (n < 256) {
*bytes = n = (n + 15) & ~15;
*align = 16;
return(n / 16 + 7);
}
if (n < 8192) {
if (n < 512) {
*bytes = n = (n + 31) & ~31;
*align = 32;
return(n / 32 + 15);
}
if (n < 1024) {
*bytes = n = (n + 63) & ~63;
*align = 64;
return(n / 64 + 23);
}
if (n < 2048) {
*bytes = n = (n + 127) & ~127;
*align = 128;
return(n / 128 + 31);
}
if (n < 4096) {
*bytes = n = (n + 255) & ~255;
*align = 256;
return(n / 256 + 39);
}
*bytes = n = (n + 511) & ~511;
*align = 512;
return(n / 512 + 47);
}
#if ZALLOC_ZONE_LIMIT > 8192
if (n < 16384) {
*bytes = n = (n + 1023) & ~1023;
*align = 1024;
return(n / 1024 + 55);
}
#endif
#if ZALLOC_ZONE_LIMIT > 16384
if (n < 32768) {
*bytes = n = (n + 2047) & ~2047;
*align = 2048;
return(n / 2048 + 63);
}
#endif
panic("Unexpected byte count %d", n);
return(0);
}
static __inline void
clean_zone_rchunks(SLZone *z)
{
SLChunk *bchunk;
while ((bchunk = z->z_RChunks) != NULL) {
cpu_ccfence();
if (atomic_cmpset_ptr(&z->z_RChunks, bchunk, NULL)) {
*z->z_LChunksp = bchunk;
while (bchunk) {
chunk_mark_free(z, bchunk);
z->z_LChunksp = &bchunk->c_Next;
bchunk = bchunk->c_Next;
++z->z_NFree;
}
break;
}
}
}
static __inline SLZone *
check_zone_free(SLGlobalData *slgd, SLZone *z)
{
SLZone *znext;
znext = TAILQ_NEXT(z, z_Entry);
if (z->z_NFree == z->z_NMax && z->z_RCount == 0 &&
(TAILQ_FIRST(&slgd->ZoneAry[z->z_ZoneIndex]) != z || znext)) {
int *kup;
TAILQ_REMOVE(&slgd->ZoneAry[z->z_ZoneIndex], z, z_Entry);
z->z_Magic = -1;
TAILQ_INSERT_HEAD(&slgd->FreeZones, z, z_Entry);
++slgd->NFreeZones;
kup = btokup(z);
*kup = 0;
}
return znext;
}
#ifdef SLAB_DEBUG
static void
slab_record_source(SLZone *z, const char *file, int line)
{
int i;
int b = line & (SLAB_DEBUG_ENTRIES - 1);
i = b;
do {
if (z->z_Sources[i].file == file && z->z_Sources[i].line == line)
return;
if (z->z_Sources[i].file == NULL)
break;
i = (i + 1) & (SLAB_DEBUG_ENTRIES - 1);
} while (i != b);
z->z_Sources[i].file = file;
z->z_Sources[i].line = line;
}
#endif
static __inline unsigned long
powerof2_size(unsigned long size)
{
int i;
if (size == 0 || powerof2(size))
return size;
i = flsl(size);
return (1UL << i);
}
#undef kmalloc
#ifdef SLAB_DEBUG
void *
_kmalloc_debug(unsigned long size, struct malloc_type *type, int flags,
const char *file, int line)
#else
void *
_kmalloc(unsigned long size, struct malloc_type *type, int flags)
#endif
{
SLZone *z;
SLChunk *chunk;
SLGlobalData *slgd;
struct globaldata *gd;
unsigned long align;
int zi;
#ifdef INVARIANTS
int i;
#endif
logmemory_quick(malloc_beg);
gd = mycpu;
slgd = &gd->gd_slab;
KKASSERT(type->ks_limit != 0);
++type->ks_use[gd->gd_cpuid].calls;
if (flags & M_CACHEALIGN) {
if (size < __VM_CACHELINE_SIZE)
size = __VM_CACHELINE_SIZE;
else if (!CAN_CACHEALIGN(size))
flags |= M_POWEROF2;
}
if (flags & M_POWEROF2)
size = powerof2_size(size);
while (type->ks_loosememuse >= type->ks_limit) {
int i;
long ttl;
for (i = ttl = 0; i < ncpus; ++i)
ttl += type->ks_use[i].memuse;
type->ks_loosememuse = ttl;
if ((ssize_t)ttl < 0)
ttl = 0;
if (ttl >= type->ks_limit) {
if (flags & M_NULLOK) {
logmemory(malloc_end, NULL, type, size, flags);
return(NULL);
}
panic("%s: malloc limit exceeded", type->ks_shortdesc);
}
}
if (size == 0) {
logmemory(malloc_end, ZERO_LENGTH_PTR, type, size, flags);
return(ZERO_LENGTH_PTR);
}
while (slgd->NFreeZones > ZoneRelsThresh && (flags & M_RNOWAIT) == 0) {
crit_enter();
if (slgd->NFreeZones > ZoneRelsThresh) {
int *kup;
z = TAILQ_LAST(&slgd->FreeZones, SLZoneList);
KKASSERT(z != NULL);
TAILQ_REMOVE(&slgd->FreeZones, z, z_Entry);
--slgd->NFreeZones;
kup = btokup(z);
*kup = 0;
kmem_slab_free(z, ZoneSize);
}
crit_exit();
}
while (TAILQ_FIRST(&slgd->FreeOvZones) && (flags & M_RNOWAIT) == 0) {
crit_enter();
if ((z = TAILQ_LAST(&slgd->FreeOvZones, SLZoneList)) != NULL) {
vm_size_t tsize;
KKASSERT(z->z_Magic == ZALLOC_OVSZ_MAGIC);
TAILQ_REMOVE(&slgd->FreeOvZones, z, z_Entry);
tsize = z->z_ChunkSize;
kmem_slab_free(z, tsize);
}
crit_exit();
}
if (size >= ZoneLimit || ((size & PAGE_MASK) == 0 && size > PAGE_SIZE*2)) {
int *kup;
size = round_page(size);
chunk = kmem_slab_alloc(size, PAGE_SIZE, flags);
if (chunk == NULL) {
logmemory(malloc_end, NULL, type, size, flags);
return(NULL);
}
flags &= ~M_ZERO;
flags |= M_PASSIVE_ZERO;
kup = btokup(chunk);
*kup = size / PAGE_SIZE;
crit_enter();
goto done;
}
zi = zoneindex(&size, &align);
KKASSERT(zi < NZONES);
crit_enter();
if ((z = TAILQ_LAST(&slgd->ZoneAry[zi], SLZoneList)) != NULL) {
if (--z->z_NFree <= 0) {
KKASSERT(z->z_NFree == 0);
if (z->z_RChunks == NULL)
atomic_swap_int(&z->z_RSignal, 1);
clean_zone_rchunks(z);
if (z->z_NFree == 0) {
TAILQ_REMOVE(&slgd->ZoneAry[zi], z, z_Entry);
} else {
z->z_RSignal = 0;
}
}
chunk = z->z_LChunks;
if (chunk) {
chunk_mark_allocated(z, chunk);
z->z_LChunks = chunk->c_Next;
if (z->z_LChunks == NULL)
z->z_LChunksp = &z->z_LChunks;
#ifdef SLAB_DEBUG
slab_record_source(z, file, line);
#endif
goto done;
}
if (z->z_UIndex + 1 != z->z_NMax)
++z->z_UIndex;
else
z->z_UIndex = 0;
if (z->z_UIndex == z->z_UEndIndex)
panic("slaballoc: corrupted zone");
chunk = (SLChunk *)(z->z_BasePtr + z->z_UIndex * size);
if ((z->z_Flags & SLZF_UNOTZEROD) == 0) {
flags &= ~M_ZERO;
flags |= M_PASSIVE_ZERO;
}
chunk_mark_allocated(z, chunk);
#ifdef SLAB_DEBUG
slab_record_source(z, file, line);
#endif
goto done;
}
{
int off;
int *kup;
if ((z = TAILQ_FIRST(&slgd->FreeZones)) != NULL) {
TAILQ_REMOVE(&slgd->FreeZones, z, z_Entry);
--slgd->NFreeZones;
bzero(z, sizeof(SLZone));
z->z_Flags |= SLZF_UNOTZEROD;
} else {
z = kmem_slab_alloc(ZoneSize, ZoneSize, flags|M_ZERO);
if (z == NULL)
goto fail;
}
#if defined(INVARIANTS)
off = offsetof(SLZone, z_Bitmap[(ZoneSize / size + 31) / 32]);
bzero(z->z_Bitmap, (ZoneSize / size + 31) / 8);
#else
off = sizeof(SLZone);
#endif
if (powerof2(size))
align = size;
off = roundup2(off, align);
z->z_Magic = ZALLOC_SLAB_MAGIC;
z->z_ZoneIndex = zi;
z->z_NMax = (ZoneSize - off) / size;
z->z_NFree = z->z_NMax - 1;
z->z_BasePtr = (char *)z + off;
z->z_UIndex = z->z_UEndIndex = slgd->JunkIndex % z->z_NMax;
z->z_ChunkSize = size;
z->z_CpuGd = gd;
z->z_Cpu = gd->gd_cpuid;
z->z_LChunksp = &z->z_LChunks;
#ifdef SLAB_DEBUG
bcopy(z->z_Sources, z->z_AltSources, sizeof(z->z_Sources));
bzero(z->z_Sources, sizeof(z->z_Sources));
#endif
chunk = (SLChunk *)(z->z_BasePtr + z->z_UIndex * size);
TAILQ_INSERT_HEAD(&slgd->ZoneAry[zi], z, z_Entry);
if ((z->z_Flags & SLZF_UNOTZEROD) == 0) {
flags &= ~M_ZERO;
flags |= M_PASSIVE_ZERO;
}
kup = btokup(z);
*kup = -(z->z_Cpu + 1);
chunk_mark_allocated(z, chunk);
#ifdef SLAB_DEBUG
slab_record_source(z, file, line);
#endif
slgd->JunkIndex = (slgd->JunkIndex + ZALLOC_SLAB_SLIDE)
& (ZALLOC_MAX_ZONE_SIZE - 1);
}
done:
++type->ks_use[gd->gd_cpuid].inuse;
type->ks_use[gd->gd_cpuid].memuse += size;
type->ks_use[gd->gd_cpuid].loosememuse += size;
if (type->ks_use[gd->gd_cpuid].loosememuse >= ZoneSize) {
type->ks_loosememuse += type->ks_use[gd->gd_cpuid].loosememuse;
type->ks_use[gd->gd_cpuid].loosememuse = 0;
}
crit_exit();
if (flags & M_ZERO)
bzero(chunk, size);
#ifdef INVARIANTS
else if ((flags & (M_ZERO|M_PASSIVE_ZERO)) == 0) {
if (use_malloc_pattern) {
for (i = 0; i < size; i += sizeof(int)) {
*(int *)((char *)chunk + i) = -1;
}
}
chunk->c_Next = (void *)-1;
}
#endif
logmemory(malloc_end, chunk, type, size, flags);
return(chunk);
fail:
crit_exit();
logmemory(malloc_end, NULL, type, size, flags);
return(NULL);
}
#ifdef SLAB_DEBUG
void *
krealloc_debug(void *ptr, unsigned long size,
struct malloc_type *type, int flags,
const char *file, int line)
#else
void *
krealloc(void *ptr, unsigned long size, struct malloc_type *type, int flags)
#endif
{
unsigned long osize;
unsigned long align;
SLZone *z;
void *nptr;
int *kup;
KKASSERT((flags & M_ZERO) == 0);
if (ptr == NULL || ptr == ZERO_LENGTH_PTR)
return(_kmalloc_debug(size, type, flags, file, line));
if (size == 0) {
kfree(ptr, type);
return(NULL);
}
kup = btokup(ptr);
if (*kup > 0) {
osize = *kup << PAGE_SHIFT;
if (osize == round_page(size))
return(ptr);
if ((nptr = _kmalloc_debug(size, type, flags, file, line)) == NULL)
return(NULL);
bcopy(ptr, nptr, min(size, osize));
kfree(ptr, type);
return(nptr);
}
z = (SLZone *)((uintptr_t)ptr & ZoneMask);
kup = btokup(z);
KKASSERT(*kup < 0);
KKASSERT(z->z_Magic == ZALLOC_SLAB_MAGIC);
if (size < ZoneLimit) {
zoneindex(&size, &align);
if (z->z_ChunkSize == size)
return(ptr);
}
if ((nptr = _kmalloc_debug(size, type, flags, file, line)) == NULL)
return(NULL);
bcopy(ptr, nptr, min(size, z->z_ChunkSize));
kfree(ptr, type);
return(nptr);
}
size_t
kmalloc_usable_size(const void *ptr)
{
unsigned long size;
SLZone *z;
int *kup;
if (ptr == NULL)
return 0;
if (ptr == ZERO_LENGTH_PTR)
return 0;
kup = btokup(ptr);
if (*kup > 0) {
size = *kup << PAGE_SHIFT;
return size;
}
z = (SLZone *)((uintptr_t)ptr & ZoneMask);
KKASSERT(z->z_Magic == ZALLOC_SLAB_MAGIC);
return (z->z_ChunkSize);
}
long
kmalloc_limit(struct malloc_type *type)
{
KKASSERT(type->ks_limit != 0);
return(type->ks_limit);
}
#ifdef SLAB_DEBUG
char *
kstrdup_debug(const char *str, struct malloc_type *type,
const char *file, int line)
#else
char *
kstrdup(const char *str, struct malloc_type *type)
#endif
{
int zlen;
char *nstr;
if (str == NULL)
return(NULL);
zlen = strlen(str) + 1;
nstr = _kmalloc_debug(zlen, type, M_WAITOK, file, line);
bcopy(str, nstr, zlen);
return(nstr);
}
#ifdef SLAB_DEBUG
char *
kstrndup_debug(const char *str, size_t maxlen, struct malloc_type *type,
const char *file, int line)
#else
char *
kstrndup(const char *str, size_t maxlen, struct malloc_type *type)
#endif
{
int zlen;
char *nstr;
if (str == NULL)
return(NULL);
zlen = strnlen(str, maxlen) + 1;
nstr = _kmalloc_debug(zlen, type, M_WAITOK, file, line);
bcopy(str, nstr, zlen);
nstr[zlen - 1] = '\0';
return(nstr);
}
static void
kfree_remote(void *ptr)
{
SLGlobalData *slgd;
SLZone *z;
int nfree;
int *kup;
slgd = &mycpu->gd_slab;
z = ptr;
kup = btokup(z);
KKASSERT(*kup == -((int)mycpuid + 1));
KKASSERT(z->z_RCount > 0);
atomic_subtract_int(&z->z_RCount, 1);
logmemory(free_rem_beg, z, NULL, 0L, 0);
KKASSERT(z->z_Magic == ZALLOC_SLAB_MAGIC);
KKASSERT(z->z_Cpu == mycpu->gd_cpuid);
nfree = z->z_NFree;
if (z->z_RChunks)
z->z_RSignal = 0;
clean_zone_rchunks(z);
if (z->z_NFree && nfree == 0) {
TAILQ_INSERT_HEAD(&slgd->ZoneAry[z->z_ZoneIndex], z, z_Entry);
}
check_zone_free(slgd, z);
logmemory(free_rem_end, z, NULL, 0L, 0);
}
void
_kfree(void *ptr, struct malloc_type *type)
{
SLZone *z;
SLChunk *chunk;
SLGlobalData *slgd;
struct globaldata *gd;
int *kup;
unsigned long size;
SLChunk *bchunk;
int rsignal;
logmemory_quick(free_beg);
gd = mycpu;
slgd = &gd->gd_slab;
if (ptr == NULL)
panic("trying to free NULL pointer");
if (ptr == ZERO_LENGTH_PTR) {
logmemory(free_zero, ptr, type, -1UL, 0);
logmemory_quick(free_end);
return;
}
if (type->ks_magic != M_MAGIC)
panic("free: malloc type lacks magic");
kup = btokup(ptr);
if (*kup > 0) {
size = *kup << PAGE_SHIFT;
*kup = 0;
#ifdef INVARIANTS
if (use_weird_array) {
KKASSERT(sizeof(weirdary) <= size);
bcopy(weirdary, ptr, sizeof(weirdary));
}
#endif
crit_enter();
--type->ks_use[gd->gd_cpuid].inuse;
type->ks_use[gd->gd_cpuid].memuse -= size;
if (mycpu->gd_intr_nesting_level ||
(gd->gd_curthread->td_flags & TDF_INTTHREAD)) {
logmemory(free_ovsz_delayed, ptr, type, size, 0);
z = (SLZone *)ptr;
z->z_Magic = ZALLOC_OVSZ_MAGIC;
z->z_ChunkSize = size;
TAILQ_INSERT_HEAD(&slgd->FreeOvZones, z, z_Entry);
crit_exit();
} else {
crit_exit();
logmemory(free_ovsz, ptr, type, size, 0);
kmem_slab_free(ptr, size);
}
logmemory_quick(free_end);
return;
}
z = (SLZone *)((uintptr_t)ptr & ZoneMask);
kup = btokup(z);
KKASSERT(*kup < 0);
KKASSERT(z->z_Magic == ZALLOC_SLAB_MAGIC);
if (z->z_CpuGd != gd) {
crit_enter();
--type->ks_use[gd->gd_cpuid].inuse;
type->ks_use[gd->gd_cpuid].memuse -= z->z_ChunkSize;
crit_exit();
rsignal = z->z_RSignal;
cpu_lfence();
if (rsignal)
atomic_add_int(&z->z_RCount, 1);
chunk = ptr;
for (;;) {
bchunk = z->z_RChunks;
cpu_ccfence();
chunk->c_Next = bchunk;
cpu_sfence();
if (atomic_cmpset_ptr(&z->z_RChunks, bchunk, chunk))
break;
}
if (bchunk == NULL && rsignal) {
logmemory(free_request, ptr, type,
(unsigned long)z->z_ChunkSize, 0);
lwkt_send_ipiq_passive(z->z_CpuGd, kfree_remote, z);
} else if (rsignal) {
atomic_subtract_int(&z->z_RCount, 1);
}
logmemory_quick(free_end);
return;
}
logmemory(free_chunk, ptr, type, (unsigned long)z->z_ChunkSize, 0);
crit_enter();
chunk = ptr;
chunk_mark_free(z, chunk);
#ifdef INVARIANTS
if (use_weird_array) {
if (z->z_ChunkSize < sizeof(weirdary))
bcopy(weirdary, chunk, z->z_ChunkSize);
else
bcopy(weirdary, chunk, sizeof(weirdary));
}
#endif
#ifdef INVARIANTS
if ((vm_offset_t)chunk < KvaStart || (vm_offset_t)chunk >= KvaEnd)
panic("BADFREE %p", chunk);
#endif
chunk->c_Next = z->z_LChunks;
z->z_LChunks = chunk;
if (chunk->c_Next == NULL)
z->z_LChunksp = &chunk->c_Next;
#ifdef INVARIANTS
if (chunk->c_Next && (vm_offset_t)chunk->c_Next < KvaStart)
panic("BADFREE2");
#endif
if (z->z_NFree++ == 0)
TAILQ_INSERT_HEAD(&slgd->ZoneAry[z->z_ZoneIndex], z, z_Entry);
--type->ks_use[gd->gd_cpuid].inuse;
type->ks_use[gd->gd_cpuid].memuse -= z->z_ChunkSize;
check_zone_free(slgd, z);
logmemory_quick(free_end);
crit_exit();
}
void
slab_cleanup(void)
{
SLGlobalData *slgd = &mycpu->gd_slab;
SLZone *z;
int i;
crit_enter();
for (i = 0; i < NZONES; ++i) {
if ((z = TAILQ_FIRST(&slgd->ZoneAry[i])) == NULL)
continue;
while (z) {
clean_zone_rchunks(z);
z = check_zone_free(slgd, z);
}
}
crit_exit();
}
#if defined(INVARIANTS)
static void
chunk_mark_allocated(SLZone *z, void *chunk)
{
int bitdex = ((char *)chunk - (char *)z->z_BasePtr) / z->z_ChunkSize;
uint32_t *bitptr;
KKASSERT((((intptr_t)chunk ^ (intptr_t)z) & ZoneMask) == 0);
KASSERT(bitdex >= 0 && bitdex < z->z_NMax,
("memory chunk %p bit index %d is illegal", chunk, bitdex));
bitptr = &z->z_Bitmap[bitdex >> 5];
bitdex &= 31;
KASSERT((*bitptr & (1 << bitdex)) == 0,
("memory chunk %p is already allocated!", chunk));
*bitptr |= 1 << bitdex;
}
static void
chunk_mark_free(SLZone *z, void *chunk)
{
int bitdex = ((char *)chunk - (char *)z->z_BasePtr) / z->z_ChunkSize;
uint32_t *bitptr;
KKASSERT((((intptr_t)chunk ^ (intptr_t)z) & ZoneMask) == 0);
KASSERT(bitdex >= 0 && bitdex < z->z_NMax,
("memory chunk %p bit index %d is illegal!", chunk, bitdex));
bitptr = &z->z_Bitmap[bitdex >> 5];
bitdex &= 31;
KASSERT((*bitptr & (1 << bitdex)) != 0,
("memory chunk %p is already free!", chunk));
*bitptr &= ~(1 << bitdex);
}
#endif
void *
kmem_slab_alloc(vm_size_t size, vm_offset_t align, int flags)
{
vm_size_t i;
vm_offset_t addr;
int count, vmflags, base_vmflags;
vm_page_t mbase = NULL;
vm_page_t m;
thread_t td;
size = round_page(size);
addr = vm_map_min(kernel_map);
count = vm_map_entry_reserve(MAP_RESERVE_COUNT);
crit_enter();
vm_map_lock(kernel_map);
if (vm_map_findspace(kernel_map, addr, size, align, 0, &addr)) {
vm_map_unlock(kernel_map);
if ((flags & M_NULLOK) == 0)
panic("kmem_slab_alloc(): kernel_map ran out of space!");
vm_map_entry_release(count);
crit_exit();
return(NULL);
}
vm_object_hold(kernel_object);
vm_object_reference_locked(kernel_object);
vm_map_insert(kernel_map, &count,
kernel_object, NULL,
addr, NULL,
addr, addr + size,
VM_MAPTYPE_NORMAL,
VM_SUBSYS_KMALLOC,
VM_PROT_ALL, VM_PROT_ALL, 0);
vm_object_drop(kernel_object);
vm_map_set_wired_quick(kernel_map, addr, size, &count);
vm_map_unlock(kernel_map);
td = curthread;
base_vmflags = 0;
if (flags & M_ZERO)
base_vmflags |= VM_ALLOC_ZERO;
if (flags & M_USE_RESERVE)
base_vmflags |= VM_ALLOC_SYSTEM;
if (flags & M_USE_INTERRUPT_RESERVE)
base_vmflags |= VM_ALLOC_INTERRUPT;
if ((flags & (M_RNOWAIT|M_WAITOK)) == 0) {
panic("kmem_slab_alloc: bad flags %08x (%p)",
flags, ((int **)&size)[-1]);
}
vmflags = base_vmflags;
if (flags & M_WAITOK) {
if (td->td_preempted)
vmflags |= VM_ALLOC_SYSTEM;
else
vmflags |= VM_ALLOC_NORMAL;
}
vm_object_hold(kernel_object);
for (i = 0; i < size; i += PAGE_SIZE) {
m = vm_page_alloc(kernel_object, OFF_TO_IDX(addr + i), vmflags);
if (i == 0)
mbase = m;
if (m == NULL) {
if (flags & M_WAITOK) {
if (td->td_preempted) {
lwkt_switch();
} else {
vm_wait(0);
}
i -= PAGE_SIZE;
continue;
}
break;
}
}
if (i != size) {
while (i != 0) {
i -= PAGE_SIZE;
m = vm_page_lookup(kernel_object, OFF_TO_IDX(addr + i));
vm_page_free(m);
}
vm_map_lock(kernel_map);
vm_map_delete(kernel_map, addr, addr + size, &count);
vm_map_unlock(kernel_map);
vm_object_drop(kernel_object);
vm_map_entry_release(count);
crit_exit();
return(NULL);
}
vm_object_drop(kernel_object);
crit_exit();
m = mbase;
i = 0;
while (i < size) {
m->valid = VM_PAGE_BITS_ALL;
pmap_enter(kernel_pmap, addr + i, m,
VM_PROT_ALL | VM_PROT_NOSYNC, 1, NULL);
if (flags & M_ZERO)
pagezero((char *)addr + i);
KKASSERT(m->flags & (PG_WRITEABLE | PG_MAPPED));
vm_page_flag_set(m, PG_REFERENCED);
vm_page_wakeup(m);
i += PAGE_SIZE;
vm_object_hold(kernel_object);
m = vm_page_next(m);
vm_object_drop(kernel_object);
}
smp_invltlb();
vm_map_entry_release(count);
return((void *)addr);
}
void
kmem_slab_free(void *ptr, vm_size_t size)
{
crit_enter();
vm_map_remove(kernel_map, (vm_offset_t)ptr, (vm_offset_t)ptr + size);
crit_exit();
}