freeswitch/libs/sofia-sip/libsofia-sip-ua/su/su_alloc.c

1758 lines
43 KiB
C

/*
* This file is part of the Sofia-SIP package
*
* Copyright (C) 2005 Nokia Corporation.
*
* Contact: Pekka Pessi <pekka.pessi@nokia.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA
*
*/
#include "config.h"
/**@defgroup su_alloc Memory Management Tutorial
*
* This page gives a short overview of home-based memory management used
* with Sofia. Such home-based memory management is useful when a lot of
* memory blocks are allocated for given task. The allocations are done via
* the @e memory @e home, which keeps a reference to each block. When the
* memory home is then freed, it will free all blocks to which it has
* reference.
*
* Typically, there is a @e home @e object which contains a su_home_t
* structure in the beginning of the object (sort of inheritance from
* su_home_t):
* @code
* struct context {
* su_home_t ctx_home[1];
* other_t *ctx_stuff;
* ...
* }
* @endcode
*
* A new home memory pool can be created with su_home_new():
* @code
* struct context *ctx = su_home_new(sizeof (struct context));
* @endcode
*
* It is also possible to create a secondary memory pool that can be
* released separately:
*
* @code
* struct context *ctx = su_home_clone(tophome, sizeof (struct context));
* @endcode
*
* Note that the tophome has a reference to @a ctx structure; whenever
* tophome is freed, the @a ctx is also freed.
*
* You can also create an independent home object by passing NULL as @a
* tophome argument. This is identical to the call to su_home_new().
*
* The memory allocations using @a ctx proceed then as follows:
* @code
* zeroblock = su_zalloc(ctx->ctx_home, sizeof (*zeroblock));
* @endcode
*
* The home memory pool - the home object and all the memory blocks
* allocated using it - are freed when su_home_unref() is called:
*
* @code
* su_home_unref(ctx->ctx_home).
* @endcode
*
* @note For historical reasons, su_home_unref() is also known as
* su_home_zap().
*
* As you might have guessed, it is also possible to use reference counting
* with home objects. The function su_home_ref() increases the reference
* count, su_home_unref() decreases it. A newly allocated or initialized
* home object has reference count of 1.
*
* @note Please note that while it is possible to create new references to
* secondary home objects which have a parent home, the secondary home
* objects will always be destroyed when the parent home is destroyed even
* if there are other references left to them.
*
* The memory blocks in a cloned home object are freed when the object with
* home itself is freed:
* @code
* su_free(tophome, ctx);
* @endcode
*
* @note
*
* The su_home_destroy() function is deprecated as it does not free the home
* object itself. Like su_home_deinit(), it should be called only on home
* objects with reference count of 1.
*
* The function su_home_init() initializes a home object structure. When the
* initialized home object is destroyed or deinitialized or its reference
* count reaches zero, the memory allocate thorugh it reclaimed but the home
* object structure itself is not freed.
*
* @section su_home_destructor_usage Destructors
*
* It is possible to give a destructor function to a home object. The
* destructor releases other resources associated with the home object
* besides memory. The destructor function will be called when the reference
* count of home reaches zero (upon calling su_home_unref()) or the home
* object is otherwise deinitialized (calling su_home_deinit() on
* objects allocated from stack).
*
* @section su_home_move_example Combining Allocations
*
* In some cases, an operation that makes multiple memory allocations may
* fail, making those allocations redundant. If the allocations are made
* through a temporary home, they can be conveniently freed by calling
* su_home_deinit(), for instance. If, however, the operation is successful,
* and one wants to keep the allocations, the allocations can be combined
* into an existing home with su_home_move(). For example,
* @code
* int example(su_home_t *home, ...)
* {
* su_home_t temphome[1] = { SU_HOME_INIT(temphome) };
*
* ... do lot of allocations with temphome ...
*
* if (success)
* su_home_move(home, temphome);
* su_home_deinit(temphome);
*
* return success;
* }
* @endcode
*
* Note that the @a temphome is deinitialized in every case, but when
* operation is successful, the allocations are moved from @a temphome to @a
* home.
*
* @section su_alloc_threadsafe Threadsafe Operation
*
* If multiple threads need to access same home object, it must be marked as
* @e threadsafe by calling su_home_threadsafe() with the home pointer as
* argument. The threadsafeness is not inherited by clones.
*
* The threadsafe home objects can be locked and unlocked with
* su_home_mutex_lock() and su_home_mutex_unlock(). These operations are
* no-op on home object that is not threadsafe.
*
* @section su_alloc_preloading Preloading a Memory Home
*
* In some situations there is quite heavy overhead if the global heap
* allocator is used. The overhead caused by the large number of small
* allocations can be reduced by using su_home_preload(): it allocates or
* preloads some a memory to home to be used as a kind of private heap. The
* preloaded memory area is then used to satisfy small enough allocations.
* For instance, the SIP parser typically preloads some 2K of memory when it
* starts to parse the message.
*
* @section su_alloc_stack Using Stack
*
* In some situation, it is sensible to use memory allocated from stack for
* some operations. The su_home_auto() function can be used for that
* purpose. The memory area from stack is used to satisfy the allocations as
* far as possible; if it is not enough, allocation is made from heap.
*
* The word @e auto refers to the automatic scope; however, the home object
* that was initialized with su_home_auto() must be explicitly deinitialized
* with su_home_deinit() or su_home_unref() when the program exits the scope
* where the stack frame used in su_home_auto() was allocated.
*/
/**@ingroup su_alloc
* @CFILE su_alloc.c Home-based memory management.
*
* @author Pekka Pessi <Pekka.Pessi@nokia.com>.
*
* @date Created: Thu Aug 19 01:12:25 1999 ppessi
*/
#include <sofia-sip/su_config.h>
#include "sofia-sip/su_alloc.h"
#include "sofia-sip/su_alloc_stat.h"
#include "sofia-sip/su_errno.h"
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <memory.h>
#include <limits.h>
#include <assert.h>
int (*_su_home_locker)(void *mutex);
int (*_su_home_unlocker)(void *mutex);
int (*_su_home_mutex_locker)(void *mutex);
int (*_su_home_mutex_trylocker)(void *mutex);
int (*_su_home_mutex_unlocker)(void *mutex);
void (*_su_home_destroy_mutexes)(void *mutex);
#if HAVE_FREE_NULL
#define safefree(x) free((x))
#else
su_inline void safefree(void *b) { b ? free(b) : (void)0; }
#endif
#define MEMLOCK(h) \
((void)((h) && (h)->suh_lock ? _su_home_locker((h)->suh_lock) : 0), (h)->suh_blocks)
#define UNLOCK(h) ((void)((h) && (h)->suh_lock ? _su_home_unlocker((h)->suh_lock) : 0), NULL)
#ifdef NDEBUG
#define MEMCHECK 0
#define MEMCHECK_EXTRA 0
#elif !defined(MEMCHECK)
/* Default settings for valgrinding */
#define MEMCHECK 1
#define MEMCHECK_EXTRA 0
#elif !defined(MEMCHECK_EXTRA)
#define MEMCHECK_EXTRA sizeof (size_t)
#endif
enum {
SUB_N = 31, /**< Initial size */
SUB_N_AUTO = 7, /**< Initial size for autohome */
SUB_P = 29 /**< Secondary probe.
* Secondary probe must be relative prime
* with all sub_n values */
};
#define ALIGNMENT (8)
#define ALIGN(n) (size_t)(((n) + (ALIGNMENT - 1)) & (size_t)~(ALIGNMENT - 1))
#define SIZEBITS (sizeof (unsigned) * 8 - 1)
typedef struct {
unsigned sua_size:SIZEBITS; /**< Size of the block */
unsigned sua_home:1; /**< Is this another home? */
unsigned :0;
void *sua_data; /**< Data pointer */
} su_alloc_t;
struct su_block_s {
su_home_t *sub_parent; /**< Parent home */
char *sub_preload; /**< Preload area */
su_home_stat_t *sub_stats; /**< Statistics.. */
void (*sub_destructor)(void *); /**< Destructor function */
size_t sub_ref; /**< Reference count */
#define REF_MAX SIZE_MAX
size_t sub_used; /**< Number of blocks allocated */
size_t sub_n; /**< Size of hash table */
unsigned sub_prsize:16; /**< Preload size */
unsigned sub_prused:16; /**< Used from preload */
unsigned sub_hauto:1; /**< "Home" is not from malloc */
unsigned sub_auto:1; /**< struct su_block_s is not from malloc */
unsigned sub_preauto:1; /**< Preload is not from malloc */
unsigned sub_auto_all:1; /**< Everything is from stack! */
unsigned :0;
su_alloc_t sub_nodes[SUB_N]; /**< Pointers to data/lower blocks */
};
static void su_home_check_blocks(su_block_t const *b);
static void su_home_stats_alloc(su_block_t *, void *p, void *preload,
size_t size, int zero);
static void su_home_stats_free(su_block_t *sub, void *p, void *preload,
unsigned size);
static void _su_home_deinit(su_home_t *home);
#define SU_ALLOC_STATS 1
#if SU_ALLOC_STATS
size_t count_su_block_find, count_su_block_find_loop;
size_t size_su_block_find, used_su_block_find;
size_t max_size_su_block_find, max_used_su_block_find;
size_t su_block_find_collision, su_block_find_collision_used,
su_block_find_collision_size;
#endif
su_inline su_alloc_t *su_block_find(su_block_t const *b, void const *p)
{
size_t h, h0, probe;
#if SU_ALLOC_STATS
size_t collision = 0;
count_su_block_find++;
size_su_block_find += b->sub_n;
used_su_block_find += b->sub_used;
if (b->sub_n > max_size_su_block_find)
max_size_su_block_find = b->sub_n;
if (b->sub_used > max_used_su_block_find)
max_used_su_block_find = b->sub_used;
#endif
assert(p != NULL);
h = h0 = (size_t)((uintptr_t)p % b->sub_n);
probe = (b->sub_n > SUB_P) ? SUB_P : 1;
do {
if (b->sub_nodes[h].sua_data == p) {
su_alloc_t const *retval = &b->sub_nodes[h];
return (su_alloc_t *)retval; /* discard const */
}
h += probe;
if (h >= b->sub_n)
h -= b->sub_n;
#if SU_ALLOC_STATS
if (++collision > su_block_find_collision)
su_block_find_collision = collision,
su_block_find_collision_used = b->sub_used,
su_block_find_collision_size = b->sub_n;
count_su_block_find_loop++;
#endif
} while (h != h0);
return NULL;
}
su_inline su_alloc_t *su_block_add(su_block_t *b, void *p)
{
size_t h, probe;
assert(p != NULL);
h = (size_t)((uintptr_t)p % b->sub_n);
probe = (b->sub_n > SUB_P) ? SUB_P : 1;
while (b->sub_nodes[h].sua_data) {
h += probe;
if (h >= b->sub_n)
h -= b->sub_n;
}
b->sub_used++;
b->sub_nodes[h].sua_data = p;
return &b->sub_nodes[h];
}
su_inline int su_is_preloaded(su_block_t const *sub, char *data)
{
return
sub->sub_preload &&
sub->sub_preload <= data &&
sub->sub_preload + sub->sub_prsize > data;
}
su_inline int su_alloc_check(su_block_t const *sub, su_alloc_t const *sua)
{
#if MEMCHECK_EXTRA
size_t size, term;
assert(sua);
if (sua) {
size = (size_t)sua->sua_size;
memcpy(&term, (char *)sua->sua_data + size, sizeof (term));
assert(size - term == 0);
return size - term == 0;
}
else
return 0;
#endif
return sua != NULL;
}
/** Allocate the block hash table.
*
* @internal
*
* Allocate a block hash table of @a n elements.
*
* @param home pointer to home object
* @param n number of buckets in hash table
*
* @return
* This function returns a pointer to the allocated hash table or
* NULL if an error occurred.
*/
su_inline su_block_t *su_hash_alloc(size_t n)
{
su_block_t *b = calloc(1, offsetof(su_block_t, sub_nodes[n]));
if (b) {
/* Implicit su_home_init(); */
b->sub_ref = 1;
b->sub_hauto = 1;
b->sub_n = n;
}
return b;
}
enum sub_zero { do_malloc, do_calloc, do_clone };
/** Allocate a memory block.
*
* @internal
*
* Precondition: locked home
*
* @param home home to allocate
* @param sub block structure used to allocate
* @param size
* @param zero if true, zero allocated block;
* if > 1, allocate a subhome
*
*/
static
void *sub_alloc(su_home_t *home,
su_block_t *sub,
size_t size,
enum sub_zero zero)
{
void *data, *preload = NULL;
assert (size < (((size_t)1) << SIZEBITS));
if (size >= ((size_t)1) << SIZEBITS)
return (void)(errno = ENOMEM), NULL;
if (sub == NULL || 3 * sub->sub_used > 2 * sub->sub_n) {
/* Resize the hash table */
size_t i, n, n2, used;
su_block_t *b2;
if (sub)
n = home->suh_blocks->sub_n, n2 = 4 * n + 3, used = sub->sub_used;
else
n = 0, n2 = SUB_N, used = 0;
#if 0
printf("su_alloc(home = %p): realloc block hash of size %d\n", home, n2);
#endif
if (!(b2 = su_hash_alloc(n2)))
return NULL;
for (i = 0; i < n; i++) {
if (sub->sub_nodes[i].sua_data)
su_block_add(b2, sub->sub_nodes[i].sua_data)[0] = sub->sub_nodes[i];
}
if (sub) {
b2->sub_parent = sub->sub_parent;
b2->sub_ref = sub->sub_ref;
b2->sub_preload = sub->sub_preload;
b2->sub_prsize = sub->sub_prsize;
b2->sub_prused = sub->sub_prused;
b2->sub_hauto = sub->sub_hauto;
b2->sub_preauto = sub->sub_preauto;
b2->sub_destructor = sub->sub_destructor;
/* auto_all is not copied! */
b2->sub_stats = sub->sub_stats;
}
home->suh_blocks = b2;
if (sub && !sub->sub_auto)
free(sub);
sub = b2;
}
if (size && sub && zero < do_clone &&
sub->sub_preload && size <= sub->sub_prsize) {
/* Use preloaded memory */
size_t prused = sub->sub_prused + size + MEMCHECK_EXTRA;
prused = ALIGN(prused);
if (prused <= sub->sub_prsize) {
preload = (char *)sub->sub_preload + sub->sub_prused;
sub->sub_prused = (unsigned)prused;
}
}
if (preload && zero)
data = memset(preload, 0, size);
else if (preload)
data = preload;
else if (zero)
data = calloc(1, size + MEMCHECK_EXTRA);
else
data = malloc(size + MEMCHECK_EXTRA);
if (data) {
su_alloc_t *sua;
#if MEMCHECK_EXTRA
size_t term = 0 - size;
memcpy((char *)data + size, &term, sizeof (term));
#endif
if (!preload)
sub->sub_auto_all = 0;
if (zero >= do_clone) {
/* Prepare cloned home */
su_home_t *subhome = data;
assert(preload == 0);
subhome->suh_blocks = su_hash_alloc(SUB_N);
if (!subhome->suh_blocks)
return (void)safefree(data), NULL;
subhome->suh_size = (unsigned)size;
subhome->suh_blocks->sub_parent = home;
subhome->suh_blocks->sub_hauto = 0;
}
/* OK, add the block to the hash table. */
sua = su_block_add(sub, data); assert(sua);
sua->sua_size = (unsigned)size;
sua->sua_home = zero > 1;
if (sub->sub_stats)
su_home_stats_alloc(sub, data, preload, size, zero);
}
return data;
}
/**Create a new su_home_t object.
*
* Create a home object used to collect multiple memory allocations under
* one handle. The memory allocations made using this home object is freed
* either when this home is destroyed.
*
* The maximum @a size of a home object is INT_MAX (2 gigabytes).
*
* @param size size of home object
*
* The memory home object allocated with su_home_new() can be reclaimed with
* su_home_unref().
*
* @return
* This function returns a pointer to an su_home_t object, or NULL upon
* an error.
*/
void *su_home_new(isize_t size)
{
su_home_t *home;
assert(size >= sizeof (*home));
if (size < sizeof (*home))
return (void)(errno = EINVAL), NULL;
else if (size > INT_MAX)
return (void)(errno = ENOMEM), NULL;
home = calloc(1, size);
if (home) {
home->suh_size = (int)size;
home->suh_blocks = su_hash_alloc(SUB_N);
if (home->suh_blocks)
home->suh_blocks->sub_hauto = 0;
else
safefree(home), home = NULL;
}
return home;
}
/** Create a new reference to a home object. */
void *su_home_ref(su_home_t const *home)
{
if (home) {
su_block_t *sub = MEMLOCK(home);
if (sub == NULL || sub->sub_ref == 0) {
assert(sub && sub->sub_ref != 0);
UNLOCK(home);
return NULL;
}
if (sub->sub_ref != REF_MAX)
sub->sub_ref++;
UNLOCK(home);
}
else
su_seterrno(EFAULT);
return (void *)home;
}
/** Set destructor function.
*
* The destructor function is called after the reference count of a
* #su_home_t object reaches zero or a home object is deinitialized, but
* before any of the memory areas within the home object are freed.
*
* @since New in @VERSION_1_12_4.
* Earlier versions had su_home_desctructor() (spelling).
*/
int su_home_destructor(su_home_t *home, void (*destructor)(void *))
{
int retval = -1;
if (home) {
su_block_t *sub = MEMLOCK(home);
if (sub && sub->sub_destructor == NULL) {
sub->sub_destructor = destructor;
retval = 0;
}
UNLOCK(home);
}
else
su_seterrno(EFAULT);
return retval;
}
#undef su_home_desctructor
/** Set destructor function.
*
* @deprecated The su_home_destructor() was added in @VERSION_1_12_4. The
* su_home_desctructor() is now defined as a macro expanding as
* su_home_destructor(). If you want to compile an application as binary
* compatible with earlier versions, you have to define su_home_desctructor
* as itself, e.g.,
* @code
* #define su_home_desctructor su_home_desctructor
* #include <sofia-sip/su_alloc.h>
* @endcode
*/
int su_home_desctructor(su_home_t *home, void (*destructor)(void *))
{
return su_home_destructor(home, destructor);
}
/**Unreference a su_home_t object.
*
* Decrements the reference count on home object and destroys and frees it
* and the memory allocations using it if the reference count reaches 0.
*
* @param home memory pool object to be unreferenced
*
* @retval 1 if object was freed
* @retval 0 if object is still alive
*/
int su_home_unref(su_home_t *home)
{
su_block_t *sub;
if (home == NULL)
return 0;
sub = MEMLOCK(home);
if (sub == NULL) {
/* Xyzzy */
return 0;
}
else if (sub->sub_ref == REF_MAX) {
UNLOCK(home);
return 0;
}
else if (--sub->sub_ref > 0) {
UNLOCK(home);
return 0;
}
else if (sub->sub_parent) {
su_home_t *parent = sub->sub_parent;
UNLOCK(home);
su_free(parent, home);
return 1;
}
else {
int hauto = sub->sub_hauto;
_su_home_deinit(home);
if (!hauto)
safefree(home);
/* UNLOCK(home); */
return 1;
}
}
/** Return reference count of home. */
size_t su_home_refcount(su_home_t *home)
{
size_t count = 0;
if (home) {
su_block_t *sub = MEMLOCK(home);
if (sub)
count = sub->sub_ref;
UNLOCK(home);
}
return count;
}
/**Clone a su_home_t object.
*
* Clone a secondary home object used to collect multiple memoryf
* allocations under one handle. The memory is freed either when the cloned
* home is destroyed or when the parent home is destroyed.
*
* An independent
* home object is created if NULL is passed as @a parent argument.
*
* @param parent a parent object (may be NULL)
* @param size size of home object
*
* The memory home object allocated with su_home_clone() can be freed with
* su_home_unref().
*
* @return
* This function returns a pointer to an su_home_t object, or NULL upon
* an error.
*/
void *su_home_clone(su_home_t *parent, isize_t size)
{
su_home_t *home;
assert(size >= sizeof (*home));
if (size < sizeof (*home))
return (void)(errno = EINVAL), NULL;
else if (size > INT_MAX)
return (void)(errno = ENOMEM), NULL;
if (parent) {
su_block_t *sub = MEMLOCK(parent);
home = sub_alloc(parent, sub, size, 2);
UNLOCK(parent);
}
else {
home = su_home_new(size);
}
return home;
}
/** Return true if home is a clone. */
int su_home_has_parent(su_home_t const *home)
{
return home && !home->suh_lock &&
home->suh_blocks && home->suh_blocks->sub_parent;
}
/** Allocate a memory block.
*
* Allocates a memory block of a given @a size.
*
* If @a home is NULL, this function behaves exactly like malloc().
*
* @param home pointer to home object
* @param size size of the memory block to be allocated
*
* @return
* This function returns a pointer to the allocated memory block or
* NULL if an error occurred.
*/
void *su_alloc(su_home_t *home, isize_t size)
{
void *data;
if (home) {
data = sub_alloc(home, MEMLOCK(home), size, 0);
UNLOCK(home);
}
else
data = malloc(size);
return data;
}
/**Free a memory block.
*
* Frees a single memory block. The @a home must be the owner of the memory
* block (usually the memory home used to allocate the memory block, or NULL
* if no home was used).
*
* @param home pointer to home object
* @param data pointer to the memory block to be freed
*/
void su_free(su_home_t *home, void *data)
{
if (!data)
return;
if (home) {
su_alloc_t *allocation;
su_block_t *sub = MEMLOCK(home);
assert(sub);
allocation = su_block_find(sub, data);
assert(allocation);
if (su_alloc_check(sub, allocation)) {
void *preloaded = NULL;
/* Is this preloaded data? */
if (su_is_preloaded(sub, data))
preloaded = data;
if (sub->sub_stats)
su_home_stats_free(sub, data, preloaded, allocation->sua_size);
if (allocation->sua_home) {
su_home_t *subhome = data;
su_block_t *sub = MEMLOCK(subhome);
assert(sub->sub_ref != REF_MAX);
/* assert(sub->sub_ref > 0); */
sub->sub_ref = 0; /* Zap all references */
_su_home_deinit(subhome);
}
#if MEMCHECK != 0
memset(data, 0xaa, (size_t)allocation->sua_size);
#endif
memset(allocation, 0, sizeof (*allocation));
sub->sub_used--;
if (preloaded)
data = NULL;
}
UNLOCK(home);
}
safefree(data);
}
/** Check if pointer has been allocated through home.
*
* @param home pointer to a memory home
* @param data pointer to a memory area possibly allocated though home
*
* @NEW_1_12_9
*/
int su_home_check_alloc(su_home_t const *home, void const *data)
{
int retval = 0;
if (home && data) {
su_block_t const *sub = MEMLOCK(home);
su_alloc_t *allocation = su_block_find(sub, data);
retval = allocation != NULL;
UNLOCK(home);
}
return retval;
}
/** Check home consistency.
*
* Ensures that the home structure and all memory blocks allocated through
* it are consistent. It can be used to catch memory allocation and usage
* errors.
*
* @param home Pointer to a memory home.
*/
void su_home_check(su_home_t const *home)
{
#if MEMCHECK != 0
su_block_t const *b = MEMLOCK(home);
su_home_check_blocks(b);
UNLOCK(home);
#endif
}
/** Check home blocks. */
static
void su_home_check_blocks(su_block_t const *b)
{
#if MEMCHECK != 0
if (b) {
size_t i, used;
assert(b->sub_used <= b->sub_n);
for (i = 0, used = 0; i < b->sub_n; i++)
if (b->sub_nodes[i].sua_data) {
su_alloc_check(b, &b->sub_nodes[i]), used++;
if (b->sub_nodes[i].sua_home)
su_home_check((su_home_t *)b->sub_nodes[i].sua_data);
}
assert(used == b->sub_used);
}
#endif
}
/**
* Create an su_home_t object.
*
* Creates a home object. A home object is used to collect multiple memory
* allocations, so that they all can be freed by calling su_home_unref().
*
* @return This function returns a pointer to an #su_home_t object, or
* NULL upon an error.
*/
su_home_t *su_home_create(void)
{
return su_home_new(sizeof(su_home_t));
}
/** Destroy a home object
*
* Frees all memory blocks associated with a home object. Note that the home
* object structure is not freed.
*
* @param home pointer to a home object
*
* @deprecated
* su_home_destroy() is included for backwards compatibility only. Use
* su_home_unref() instead of su_home_destroy().
*/
void su_home_destroy(su_home_t *home)
{
if (MEMLOCK(home)) {
assert(home->suh_blocks);
assert(home->suh_blocks->sub_ref == 1);
if (!home->suh_blocks->sub_hauto)
/* should warn user */;
home->suh_blocks->sub_hauto = 1;
_su_home_deinit(home);
/* UNLOCK(home); */
}
}
/** Initialize an su_home_t struct.
*
* Initializes an su_home_t structure. It can be used when the home
* structure is allocated from stack or when the home structure is part of
* an another object.
*
* @param home pointer to home object
*
* @retval 0 when successful
* @retval -1 upon an error.
*
* @sa SU_HOME_INIT(), su_home_deinit(), su_home_new(), su_home_clone()
*
* @bug
* Prior to @VERSION_1_12_8 the su_home_t structure should have been
* initialized with SU_HOME_INIT() or otherwise zeroed before calling
* su_home_init().
*/
int su_home_init(su_home_t *home)
{
su_block_t *sub;
if (home == NULL)
return -1;
home->suh_blocks = sub = su_hash_alloc(SUB_N);
home->suh_lock = NULL;
if (!sub)
return -1;
return 0;
}
/** Internal deinitialization */
static
void _su_home_deinit(su_home_t *home)
{
if (home->suh_blocks) {
size_t i;
su_block_t *b;
if (home->suh_blocks->sub_destructor) {
void (*destructor)(void *) = home->suh_blocks->sub_destructor;
home->suh_blocks->sub_destructor = NULL;
destructor(home);
}
b = home->suh_blocks;
su_home_check_blocks(b);
for (i = 0; i < b->sub_n; i++) {
if (b->sub_nodes[i].sua_data) {
if (b->sub_nodes[i].sua_home) {
su_home_t *subhome = b->sub_nodes[i].sua_data;
su_block_t *subb = MEMLOCK(subhome);
assert(subb); assert(subb->sub_ref >= 1);
#if 0
if (subb->sub_ref > 0)
SU_DEBUG_7(("su_home_unref: subhome %p with destructor %p has still %u refs\n",
subhome, subb->sub_destructor, subb->sub_ref));
#endif
subb->sub_ref = 0; /* zap them all */
_su_home_deinit(subhome);
}
else if (su_is_preloaded(b, b->sub_nodes[i].sua_data))
continue;
safefree(b->sub_nodes[i].sua_data);
}
}
if (b->sub_preload && !b->sub_preauto)
free(b->sub_preload);
if (b->sub_stats)
free(b->sub_stats);
if (!b->sub_auto)
free(b);
home->suh_blocks = NULL;
if (home->suh_lock) {
void *suh_lock = home->suh_lock;
home->suh_lock = NULL;
/* Unlock, or risk assert() or leak handles on Windows */
_su_home_unlocker(suh_lock);
_su_home_destroy_mutexes(suh_lock);
}
}
}
/** Free memory blocks allocated through home.
*
* Frees the memory blocks associated with the home object allocated. It
* does not free the home object itself. Use su_home_unref() to free the
* home object.
*
* @param home pointer to home object
*
* @sa su_home_init()
*/
void su_home_deinit(su_home_t *home)
{
if (MEMLOCK(home)) {
assert(home->suh_blocks);
assert(home->suh_blocks->sub_ref == 1);
assert(home->suh_blocks->sub_hauto);
_su_home_deinit(home);
/* UNLOCK(home); */
}
}
/**Move allocations from a su_home_t object to another.
*
* Moves allocations made through the @a src home object under the @a dst
* home object. It is handy, for example, if an operation allocates some
* number of blocks that should be freed upon an error. It uses a temporary
* home and moves the blocks from temporary to a proper home when
* successful, but frees the temporary home upon an error.
*
* If @a src has destructor, it is called before starting to move.
*
* @param dst destination home
* @param src source home
*
* @retval 0 if succesful
* @retval -1 upon an error
*/
int su_home_move(su_home_t *dst, su_home_t *src)
{
size_t i, n, n2, used;
su_block_t *s, *d, *d2;
if (src == NULL || dst == src)
return 0;
if (dst) {
s = MEMLOCK(src); d = MEMLOCK(dst);
if (s && s->sub_n) {
if (s->sub_destructor) {
void (*destructor)(void *) = s->sub_destructor;
s->sub_destructor = NULL;
destructor(src);
}
if (d)
used = s->sub_used + d->sub_used;
else
used = s->sub_used;
if (used && (d == NULL || 3 * used > 2 * d->sub_n)) {
if (d)
for (n = n2 = d->sub_n; 3 * used > 2 * n2; n2 = 4 * n2 + 3)
;
else
n = 0, n2 = s->sub_n;
if (!(d2 = su_hash_alloc(n2))) {
UNLOCK(dst); UNLOCK(src);
return -1;
}
dst->suh_blocks = d2;
for (i = 0; i < n; i++)
if (d->sub_nodes[i].sua_data)
su_block_add(d2, d->sub_nodes[i].sua_data)[0] = d->sub_nodes[i];
if (d) {
d2->sub_parent = d->sub_parent;
d2->sub_ref = d->sub_ref;
d2->sub_preload = d->sub_preload;
d2->sub_prsize = d->sub_prsize;
d2->sub_prused = d->sub_prused;
d2->sub_preauto = d->sub_preauto;
d2->sub_stats = d->sub_stats;
}
if (d && !d->sub_auto)
free(d);
d = d2;
}
if (s->sub_used) {
n = s->sub_n;
for (i = 0; i < n; i++)
if (s->sub_nodes[i].sua_data) {
su_block_add(d, s->sub_nodes[i].sua_data)[0] = s->sub_nodes[i];
}
s->sub_used = 0;
memset(s->sub_nodes, 0, n * sizeof (s->sub_nodes[0]));
}
if (s->sub_stats) {
/* XXX */
}
}
UNLOCK(dst); UNLOCK(src);
}
else {
s = MEMLOCK(src);
if (s && s->sub_used) {
s->sub_used = 0;
memset(s->sub_nodes, 0, s->sub_n * sizeof (s->sub_nodes[0]));
}
UNLOCK(src);
}
return 0;
}
/** Preload a memory home.
*
* The function su_home_preload() preloads a memory home.
*/
void su_home_preload(su_home_t *home, isize_t n, isize_t isize)
{
su_block_t *sub;
if (home == NULL)
return;
if (home->suh_blocks == NULL)
su_home_init(home);
sub = MEMLOCK(home);
if (!sub->sub_preload) {
size_t size;
void *preload;
size = n * ALIGN(isize);
if (size > 65535) /* We have 16 bits... */
size = 65535 & (ALIGNMENT - 1);
preload = malloc(size);
home->suh_blocks->sub_preload = preload;
home->suh_blocks->sub_prsize = (unsigned)size;
}
UNLOCK(home);
}
/** Preload a memory home from stack.
*
* Initializes a memory home using an area allocated from stack. Poor man's
* alloca().
*/
su_home_t *su_home_auto(void *area, isize_t size)
{
su_home_t *home;
su_block_t *sub;
size_t homesize = ALIGN(sizeof *home);
size_t subsize = ALIGN(offsetof(su_block_t, sub_nodes[SUB_N_AUTO]));
size_t prepsize;
char *p = area;
prepsize = homesize + subsize + (ALIGN((intptr_t)p) - (intptr_t)p);
if (area == NULL || size < prepsize)
return NULL;
if (size > INT_MAX)
size = INT_MAX;
home = memset(p, 0, homesize);
home->suh_size = (int)size;
sub = memset(p + homesize, 0, subsize);
home->suh_blocks = sub;
if (size > prepsize + 65535)
size = prepsize + 65535;
sub->sub_n = SUB_N_AUTO;
sub->sub_ref = 1;
sub->sub_preload = p + prepsize;
sub->sub_prsize = (unsigned)(size - prepsize);
sub->sub_hauto = 1;
sub->sub_auto = 1;
sub->sub_preauto = 1;
sub->sub_auto_all = 1;
return home;
}
/** Reallocate a memory block.
*
* Allocates a memory block of @a size bytes.
* It copies the old block contents to the new block and frees the old
* block.
*
* If @a home is NULL, this function behaves exactly like realloc().
*
* @param home pointer to memory pool object
* @param data pointer to old memory block
* @param size size of the memory block to be allocated
*
* @return
* A pointer to the allocated memory block or
* NULL if an error occurred.
*/
void *su_realloc(su_home_t *home, void *data, isize_t size)
{
void *ndata;
su_alloc_t *sua;
su_block_t *sub;
size_t p;
size_t term = 0 - size;
if (!home)
return realloc(data, size);
if (size == 0) {
if (data)
su_free(home, data);
return NULL;
}
sub = MEMLOCK(home);
if (!data) {
data = sub_alloc(home, sub, size, 0);
UNLOCK(home);
return data;
}
sua = su_block_find(sub, data);
if (!su_alloc_check(sub, sua))
return UNLOCK(home);
assert(!sua->sua_home);
if (sua->sua_home)
return UNLOCK(home);
if (!su_is_preloaded(sub, data)) {
ndata = realloc(data, size + MEMCHECK_EXTRA);
if (ndata) {
if (sub->sub_stats) {
su_home_stats_free(sub, data, 0, sua->sua_size);
su_home_stats_alloc(sub, data, 0, size, 1);
}
#if MEMCHECK_EXTRA
memcpy((char *)ndata + size, &term, sizeof (term));
#else
(void)term;
#endif
memset(sua, 0, sizeof *sua);
sub->sub_used--;
su_block_add(sub, ndata)->sua_size = (unsigned)size;
}
UNLOCK(home);
return ndata;
}
p = (char *)data - home->suh_blocks->sub_preload;
p += sua->sua_size + MEMCHECK_EXTRA;
p = ALIGN(p);
if (p == sub->sub_prused) {
size_t p2 = (char *)data - sub->sub_preload + size + MEMCHECK_EXTRA;
p2 = ALIGN(p2);
if (p2 <= sub->sub_prsize) {
/* Extend/reduce existing preload */
if (sub->sub_stats) {
su_home_stats_free(sub, data, data, sua->sua_size);
su_home_stats_alloc(sub, data, data, size, 0);
}
sub->sub_prused = (unsigned)p2;
sua->sua_size = (unsigned)size;
#if MEMCHECK_EXTRA
memcpy((char *)data + size, &term, sizeof (term));
#endif
UNLOCK(home);
return data;
}
}
else if (size < (size_t)sua->sua_size) {
/* Reduce existing preload */
if (sub->sub_stats) {
su_home_stats_free(sub, data, data, sua->sua_size);
su_home_stats_alloc(sub, data, data, size, 0);
}
#if MEMCHECK_EXTRA
memcpy((char *)data + size, &term, sizeof (term));
#endif
sua->sua_size = (unsigned)size;
UNLOCK(home);
return data;
}
ndata = malloc(size + MEMCHECK_EXTRA);
if (ndata) {
if (p == sub->sub_prused) {
/* Free preload */
sub->sub_prused = (char *)data - home->suh_blocks->sub_preload;
if (sub->sub_stats)
su_home_stats_free(sub, data, data, sua->sua_size);
}
memcpy(ndata, data,
(size_t)sua->sua_size < size
? (size_t)sua->sua_size
: size);
#if MEMCHECK_EXTRA
memcpy((char *)ndata + size, &term, sizeof (term));
#endif
if (sub->sub_stats)
su_home_stats_alloc(sub, data, 0, size, 1);
memset(sua, 0, sizeof *sua); sub->sub_used--;
su_block_add(sub, ndata)->sua_size = (unsigned)size;
}
UNLOCK(home);
return ndata;
}
/**Check if a memory block has been allocated from the @a home.
*
* Check if the given memory block has been allocated from the home.
*
* @param home pointer to memory pool object
* @param memory ponter to memory block
*
* @retval 1 if @a memory has been allocated from @a home.
* @retval 0 otherwise
*
* @since New in @VERSION_1_12_4.
*/
int su_in_home(su_home_t *home, void const *memory)
{
su_alloc_t *sua;
su_block_t *sub;
int retval = 0;
if (!home || !memory)
return 0;
sub = MEMLOCK(home);
if (sub) {
sua = su_block_find(sub, memory);
retval = su_alloc_check(sub, sua);
UNLOCK(home);
}
return retval;
}
/**Allocate and zero a memory block.
*
* Allocates a memory block with a given size from
* given memory home @a home and zeroes the allocated block.
*
* @param home pointer to memory pool object
* @param size size of the memory block
*
* @note The memory home pointer @a home may be @c NULL. In that case, the
* allocated memory block is not associated with any memory home, and it
* must be freed by calling su_free() or free().
*
* @return
* The function su_zalloc() returns a pointer to the allocated memory block,
* or NULL upon an error.
*/
void *su_zalloc(su_home_t *home, isize_t size)
{
void *data;
assert (size >= 0);
if (home) {
data = sub_alloc(home, MEMLOCK(home), size, 1);
UNLOCK(home);
}
else
data = calloc(1, size);
return data;
}
/** Allocate a structure
*
* Allocates a structure with a given size, zeros
* it, and initializes the size field to the given size. The size field
* is an int at the beginning of the structure. Note that it has type of int.
*
* @param home pointer to memory pool object
* @param size size of the structure
*
* @par Example
* The structure is defined and allocated as follows:
* @code
* struct test {
* int tst_size;
* char *tst_name;
* void *tst_ptr[3];
* };
*
* struct test *t;
* ...
* t = su_salloc(home, sizeof (*t));
* assert(t && t->t_size == sizeof (*t));
*
* @endcode
* After calling su_salloc() we get a pointer t to a struct test,
* initialized to zero except the tst_size field, which is initialized to
* sizeof (*t).
*
* @return A pointer to the allocated structure, or NULL upon an error.
*/
void *su_salloc(su_home_t *home, isize_t size)
{
struct { int size; } *retval;
if (size < sizeof (*retval))
size = sizeof (*retval);
if (size > INT_MAX)
return (void)(errno = ENOMEM), NULL;
if (home) {
retval = sub_alloc(home, MEMLOCK(home), size, 1);
UNLOCK(home);
}
else
retval = calloc(1, size);
if (retval)
retval->size = (int)size;
return retval;
}
/** Check if a memory home is threadsafe */
int su_home_is_threadsafe(su_home_t const *home)
{
return home && home->suh_lock;
}
/** Increase refcount and obtain exclusive lock on home.
*
* @note The #su_home_t structure must be created with su_home_new() or
* su_home_clone(), or initialized with su_home_init() before using this
* function.
*
* In order to enable actual locking, use su_home_threadsafe(), too.
* Otherwise the su_home_mutex_lock() will just increase the reference
* count.
*/
int su_home_mutex_lock(su_home_t *home)
{
int error;
if (home == NULL)
return su_seterrno(EFAULT);
if (home->suh_blocks == NULL || !su_home_ref(home))
return su_seterrno(EINVAL); /* Uninitialized home */
if (!home->suh_lock)
return 0; /* No-op */
error = _su_home_mutex_locker(home->suh_lock);
if (error)
return su_seterrno(error);
return 0;
}
/** Release exclusive lock on home and decrease refcount (if home is threadsafe).
*
* @sa su_home_unlock().
*/
int su_home_mutex_unlock(su_home_t *home)
{
if (home == NULL)
return su_seterrno(EFAULT);
if (home->suh_lock) {
int error = _su_home_mutex_unlocker(home->suh_lock);
if (error)
return su_seterrno(error);
}
if (home->suh_blocks == NULL)
return su_seterrno(EINVAL), -1; /* Uninitialized home */
su_home_unref(home);
return 0;
}
/** Obtain exclusive lock on home without increasing refcount.
*
* Unless su_home_threadsafe() has been used to intialize locking on home
* object the function just returns -1.
*
* @return 0 if successful, -1 if not threadsafe, error code otherwise.
*
* @sa su_home_mutex_lock(), su_home_unlock(), su_home_trylock().
*
* @NEW_1_12_8
*/
int su_home_lock(su_home_t *home)
{
if (home == NULL)
return EFAULT;
if (home->suh_lock == NULL)
return -1; /* No-op */
return _su_home_mutex_locker(home->suh_lock);
}
/** Try to obtain exclusive lock on home without increasing refcount.
*
* @return 0 if successful, -1 if not threadsafe,
* EBUSY if already locked, error code otherwise.
*
* @sa su_home_lock(), su_home_unlock().
*
* @NEW_1_12_8
*/
int su_home_trylock(su_home_t *home)
{
if (home == NULL)
return EFAULT;
if (home->suh_lock == NULL)
return -1; /* No-op */
return _su_home_mutex_trylocker(home->suh_lock);
}
/** Release exclusive lock on home.
*
* Release lock without decreasing refcount.
*
* @return 0 if successful, -1 if not threadsafe, error code otherwise.
*
* @sa su_home_lock(), su_home_trylock(), su_home_mutex_unlock().
*
* @NEW_1_12_8
*/
int su_home_unlock(su_home_t *home)
{
if (home == NULL)
return EFAULT;
if (home->suh_lock == NULL)
return -1; /* No-op */
return _su_home_mutex_unlocker(home->suh_lock);
}
/** Initialize statistics structure */
void su_home_init_stats(su_home_t *home)
{
su_block_t *sub;
size_t size;
if (home == NULL)
return;
sub = home->suh_blocks;
if (!sub)
sub = home->suh_blocks = su_hash_alloc(SUB_N);
if (!sub)
return;
if (!sub->sub_stats) {
size = sizeof (*sub->sub_stats);
sub->sub_stats = malloc(size);
if (!sub->sub_stats)
return;
}
else
size = sub->sub_stats->hs_size;
memset(sub->sub_stats, 0, size);
sub->sub_stats->hs_size = (int)size;
sub->sub_stats->hs_blocksize = sub->sub_n;
}
/** Retrieve statistics from memory home.
*/
void su_home_get_stats(su_home_t *home, int include_clones,
su_home_stat_t *hs,
isize_t size)
{
su_block_t *sub;
if (hs == NULL || size < (sizeof hs->hs_size))
return;
memset((void *)hs, 0, size);
sub = MEMLOCK(home);
if (sub && sub->sub_stats) {
int sub_size = sub->sub_stats->hs_size;
if (sub_size > (int)size)
sub_size = (int)size;
sub->sub_stats->hs_preload.hsp_size = sub->sub_prsize;
sub->sub_stats->hs_preload.hsp_used = sub->sub_prused;
memcpy(hs, sub->sub_stats, sub_size);
hs->hs_size = sub_size;
}
UNLOCK(home);
}
static
void su_home_stats_alloc(su_block_t *sub, void *p, void *preload,
size_t size, int zero)
{
su_home_stat_t *hs = sub->sub_stats;
size_t rsize = ALIGN(size);
hs->hs_rehash += (sub->sub_n != hs->hs_blocksize);
hs->hs_blocksize = sub->sub_n;
hs->hs_clones += zero > 1;
if (preload) {
hs->hs_allocs.hsa_preload++;
return;
}
hs->hs_allocs.hsa_number++;
hs->hs_allocs.hsa_bytes += size;
hs->hs_allocs.hsa_rbytes += rsize;
if (hs->hs_allocs.hsa_rbytes > hs->hs_allocs.hsa_maxrbytes)
hs->hs_allocs.hsa_maxrbytes = hs->hs_allocs.hsa_rbytes;
hs->hs_blocks.hsb_number++;
hs->hs_blocks.hsb_bytes += size;
hs->hs_blocks.hsb_rbytes += rsize;
}
static
void su_home_stats_free(su_block_t *sub, void *p, void *preload,
unsigned size)
{
su_home_stat_t *hs = sub->sub_stats;
size_t rsize = ALIGN(size);
if (preload) {
hs->hs_frees.hsf_preload++;
return;
}
hs->hs_frees.hsf_number++;
hs->hs_frees.hsf_bytes += size;
hs->hs_frees.hsf_rbytes += rsize;
hs->hs_blocks.hsb_number--;
hs->hs_blocks.hsb_bytes -= size;
hs->hs_blocks.hsb_rbytes -= rsize;
}
void su_home_stat_add(su_home_stat_t total[1], su_home_stat_t const hs[1])
{
total->hs_clones += hs->hs_clones;
total->hs_rehash += hs->hs_rehash;
if (total->hs_blocksize < hs->hs_blocksize)
total->hs_blocksize = hs->hs_blocksize;
total->hs_allocs.hsa_number += hs->hs_allocs.hsa_number;
total->hs_allocs.hsa_bytes += hs->hs_allocs.hsa_bytes;
total->hs_allocs.hsa_rbytes += hs->hs_allocs.hsa_rbytes;
total->hs_allocs.hsa_maxrbytes += hs->hs_allocs.hsa_maxrbytes;
total->hs_frees.hsf_number += hs->hs_frees.hsf_number;
total->hs_frees.hsf_bytes += hs->hs_frees.hsf_bytes;
total->hs_frees.hsf_rbytes += hs->hs_frees.hsf_rbytes;
total->hs_blocks.hsb_number += hs->hs_blocks.hsb_number;
total->hs_blocks.hsb_bytes += hs->hs_blocks.hsb_bytes;
total->hs_blocks.hsb_rbytes += hs->hs_blocks.hsb_rbytes;
}