blob: 1f634c63a77193c6e1498017c7ffc40cffee34a9 [file] [log] [blame]
/**
* @file mem.c Memory management with reference counting
*
* Copyright (C) 2010 Creytiv.com
*/
#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#ifdef HAVE_PTHREAD
#include <pthread.h>
#endif
#include <re_types.h>
#include <re_list.h>
#include <re_fmt.h>
#include <re_mbuf.h>
#include <re_mem.h>
#define DEBUG_MODULE "mem"
#define DEBUG_LEVEL 5
#include <re_dbg.h>
#ifndef RELEASE
#define MEM_DEBUG 1 /**< Enable memory debugging */
#endif
/** Defines a reference-counting memory object */
struct mem {
uint32_t nrefs; /**< Number of references */
mem_destroy_h *dh; /**< Destroy handler */
#if MEM_DEBUG
struct le le; /**< Linked list element */
uint32_t magic; /**< Magic number */
size_t size; /**< Size of memory object */
#endif
};
#if MEM_DEBUG
/* Memory debugging */
static struct list meml = LIST_INIT;
static const uint32_t mem_magic = 0xe7fb9ac4;
static ssize_t threshold = -1; /**< Memory threshold, disabled by default */
static struct memstat memstat = {
0,0,0,0,~0,0
};
#ifdef HAVE_PTHREAD
static pthread_mutex_t mem_mutex = PTHREAD_MUTEX_INITIALIZER;
static inline void mem_lock(void)
{
pthread_mutex_lock(&mem_mutex);
}
static inline void mem_unlock(void)
{
pthread_mutex_unlock(&mem_mutex);
}
#else
#define mem_lock() /**< Stub */
#define mem_unlock() /**< Stub */
#endif
/** Update statistics for mem_zalloc() */
#define STAT_ALLOC(m, size) \
mem_lock(); \
memstat.bytes_cur += (size); \
memstat.bytes_peak = max(memstat.bytes_cur, memstat.bytes_peak); \
++memstat.blocks_cur; \
memstat.blocks_peak = max(memstat.blocks_cur, memstat.blocks_peak); \
memstat.size_min = min(memstat.size_min, size); \
memstat.size_max = max(memstat.size_max, size); \
mem_unlock(); \
(m)->size = (size); \
(m)->magic = mem_magic;
/** Update statistics for mem_realloc() */
#define STAT_REALLOC(m, size) \
mem_lock(); \
memstat.bytes_cur += ((size) - (m)->size); \
memstat.bytes_peak = max(memstat.bytes_cur, memstat.bytes_peak); \
memstat.size_min = min(memstat.size_min, size); \
memstat.size_max = max(memstat.size_max, size); \
mem_unlock(); \
(m)->size = (size)
/** Update statistics for mem_deref() */
#define STAT_DEREF(m) \
mem_lock(); \
memstat.bytes_cur -= (m)->size; \
--memstat.blocks_cur; \
mem_unlock(); \
memset((m), 0xb5, sizeof(struct mem) + (m)->size)
/** Check magic number in memory object */
#define MAGIC_CHECK(m) \
if (mem_magic != (m)->magic) { \
DEBUG_WARNING("%s: magic check failed 0x%08x (%p)\n", \
__REFUNC__, (m)->magic, (m)+1); \
BREAKPOINT; \
}
#else
#define STAT_ALLOC(m, size)
#define STAT_REALLOC(m, size)
#define STAT_DEREF(m)
#define MAGIC_CHECK(m)
#endif
/**
* Allocate a new reference-counted memory object
*
* @param size Size of memory object
* @param dh Optional destructor, called when destroyed
*
* @return Pointer to allocated object
*/
void *mem_alloc(size_t size, mem_destroy_h *dh)
{
struct mem *m;
#if MEM_DEBUG
mem_lock();
if (-1 != threshold && (memstat.blocks_cur >= (size_t)threshold)) {
mem_unlock();
return NULL;
}
mem_unlock();
#endif
m = malloc(sizeof(*m) + size);
if (!m)
return NULL;
#if MEM_DEBUG
memset(&m->le, 0, sizeof(struct le));
mem_lock();
list_append(&meml, &m->le, m);
mem_unlock();
#endif
m->nrefs = 1;
m->dh = dh;
STAT_ALLOC(m, size);
return (void *)(m + 1);
}
/**
* Allocate a new reference-counted memory object. Memory is zeroed.
*
* @param size Size of memory object
* @param dh Optional destructor, called when destroyed
*
* @return Pointer to allocated object
*/
void *mem_zalloc(size_t size, mem_destroy_h *dh)
{
void *p;
p = mem_alloc(size, dh);
if (!p)
return NULL;
memset(p, 0, size);
return p;
}
/**
* Re-allocate a reference-counted memory object
*
* @param data Memory object
* @param size New size of memory object
*
* @return New pointer to allocated object
*
* @note Realloc NULL pointer is not supported
*/
void *mem_realloc(void *data, size_t size)
{
struct mem *m, *m2;
if (!data)
return NULL;
m = ((struct mem *)data) - 1;
MAGIC_CHECK(m);
#if MEM_DEBUG
mem_lock();
/* Simulate OOM */
if (-1 != threshold && size > m->size) {
if (memstat.blocks_cur >= (size_t)threshold) {
mem_unlock();
return NULL;
}
}
list_unlink(&m->le);
mem_unlock();
#endif
m2 = realloc(m, sizeof(*m2) + size);
#if MEM_DEBUG
mem_lock();
list_append(&meml, m2 ? &m2->le : &m->le, m2 ? m2 : m);
mem_unlock();
#endif
if (!m2) {
return NULL;
}
STAT_REALLOC(m2, size);
return (void *)(m2 + 1);
}
#ifndef SIZE_MAX
#define SIZE_MAX (~((size_t)0))
#endif
/**
* Re-allocate a reference-counted array
*
* @param ptr Pointer to existing array, NULL to allocate a new array
* @param nmemb Number of members in array
* @param membsize Number of bytes in each member
* @param dh Optional destructor, only used when ptr is NULL
*
* @return New pointer to allocated array
*/
void *mem_reallocarray(void *ptr, size_t nmemb, size_t membsize,
mem_destroy_h *dh)
{
size_t tsize;
if (membsize && nmemb > SIZE_MAX / membsize) {
return NULL;
}
tsize = nmemb * membsize;
if (ptr) {
return mem_realloc(ptr, tsize);
}
else {
return mem_alloc(tsize, dh);
}
}
/**
* Reference a reference-counted memory object
*
* @param data Memory object
*
* @return Memory object (same as data)
*/
void *mem_ref(void *data)
{
struct mem *m;
if (!data)
return NULL;
m = ((struct mem *)data) - 1;
MAGIC_CHECK(m);
++m->nrefs;
return data;
}
/**
* Dereference a reference-counted memory object. When the reference count
* is zero, the destroy handler will be called (if present) and the memory
* will be freed
*
* @param data Memory object
*
* @return Always NULL
*/
void *mem_deref(void *data)
{
struct mem *m;
if (!data)
return NULL;
m = ((struct mem *)data) - 1;
MAGIC_CHECK(m);
if (--m->nrefs > 0)
return NULL;
if (m->dh)
m->dh(data);
/* NOTE: check if the destructor called mem_ref() */
if (m->nrefs > 0)
return NULL;
#if MEM_DEBUG
mem_lock();
list_unlink(&m->le);
mem_unlock();
#endif
STAT_DEREF(m);
free(m);
return NULL;
}
/**
* Get number of references to a reference-counted memory object
*
* @param data Memory object
*
* @return Number of references
*/
uint32_t mem_nrefs(const void *data)
{
struct mem *m;
if (!data)
return 0;
m = ((struct mem *)data) - 1;
MAGIC_CHECK(m);
return m->nrefs;
}
#if MEM_DEBUG
static bool debug_handler(struct le *le, void *arg)
{
struct mem *m = le->data;
const uint8_t *p = (const uint8_t *)(m + 1);
size_t i;
(void)arg;
(void)re_fprintf(stderr, " %p: nrefs=%-2u", p, m->nrefs);
(void)re_fprintf(stderr, " size=%-7u", m->size);
(void)re_fprintf(stderr, " [");
for (i=0; i<16; i++) {
if (i >= m->size)
(void)re_fprintf(stderr, " ");
else
(void)re_fprintf(stderr, "%02x ", p[i]);
}
(void)re_fprintf(stderr, "] [");
for (i=0; i<16; i++) {
if (i >= m->size)
(void)re_fprintf(stderr, " ");
else
(void)re_fprintf(stderr, "%c",
isprint(p[i]) ? p[i] : '.');
}
(void)re_fprintf(stderr, "]");
MAGIC_CHECK(m);
(void)re_fprintf(stderr, "\n");
return false;
}
#endif
/**
* Debug all allocated memory objects
*/
void mem_debug(void)
{
#if MEM_DEBUG
uint32_t n;
mem_lock();
n = list_count(&meml);
mem_unlock();
if (!n)
return;
DEBUG_WARNING("Memory leaks (%u):\n", n);
mem_lock();
(void)list_apply(&meml, true, debug_handler, NULL);
mem_unlock();
#endif
}
/**
* Set the memory allocation threshold. This is only used for debugging
* and out-of-memory simulation
*
* @param n Threshold value
*/
void mem_threshold_set(ssize_t n)
{
#if MEM_DEBUG
mem_lock();
threshold = n;
mem_unlock();
#else
(void)n;
#endif
}
/**
* Print memory status
*
* @param pf Print handler for debug output
* @param unused Unused parameter
*
* @return 0 if success, otherwise errorcode
*/
int mem_status(struct re_printf *pf, void *unused)
{
#if MEM_DEBUG
struct memstat stat;
uint32_t c;
int err = 0;
(void)unused;
mem_lock();
memcpy(&stat, &memstat, sizeof(stat));
c = list_count(&meml);
mem_unlock();
err |= re_hprintf(pf, "Memory status: (%u bytes overhead pr block)\n",
sizeof(struct mem));
err |= re_hprintf(pf, " Cur: %u blocks, %u bytes (total %u bytes)\n",
stat.blocks_cur, stat.bytes_cur,
stat.bytes_cur
+(stat.blocks_cur*sizeof(struct mem)));
err |= re_hprintf(pf, " Peak: %u blocks, %u bytes (total %u bytes)\n",
stat.blocks_peak, stat.bytes_peak,
stat.bytes_peak
+(stat.blocks_peak*sizeof(struct mem)));
err |= re_hprintf(pf, " Block size: min=%u, max=%u\n",
stat.size_min, stat.size_max);
err |= re_hprintf(pf, " Total %u blocks allocated\n", c);
return err;
#else
(void)pf;
(void)unused;
return 0;
#endif
}
/**
* Get memory statistics
*
* @param mstat Returned memory statistics
*
* @return 0 if success, otherwise errorcode
*/
int mem_get_stat(struct memstat *mstat)
{
if (!mstat)
return EINVAL;
#if MEM_DEBUG
mem_lock();
memcpy(mstat, &memstat, sizeof(*mstat));
mem_unlock();
return 0;
#else
return ENOSYS;
#endif
}