在众多资料和文章中多次注意到出现了home based memory management。这应该是Sofia软件包的亮点之一。现在总算开始分析它了。
struct su_home_s {
int suh_size;
su_block_t *suh_blocks;
su_alock_t *suh_lock;
};
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 */
};
从结构体类型来看,整个Home based memory management包括三个层级。最上层是su_home_s,中间一层是su_block_s,最下那层是su_alloc_t。su_home_s结构体保留一个指向su_block_s结构体的指针,su_block_s包含一个su_alloc_t类型的数组。
SU_HOME_INIT宏定义就是给一个su_home_s结构体变量赋初值。
#define SU_HOME_INIT(obj) { 0, NULL, NULL }
su.c源文件中的前部包含了一大段有关如何使用home based memory management的说明。先看使用说明再看内部细节实现,也许对于理解实现更有帮助。先将这段内容摘录如下:
*
* 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.
*/
使用方式分几步实现:
1、先申明一个包含有su_home_s结构体对象的结构体,类似于这样
struct context {
su_home_t ctx_home[1];
other_t *ctx_stuff;
...
}
context结构体内第一个就是su_home_s结构体对象。然后context结构体内包含了其他的对象。要求就是必须确保context结构体内第一个对象一定时su_home_s对象。
2、然后就是调用创建函数创建、初始化context结构体对象。
struct context *ctx = su_home_new(sizeof (struct context));
3、接着使用su_zalloc函数处理内存分配事宜。文字中没明确指出zeroblock变量的类型,但我猜这应该是su_block_s结构体类型。
zeroblock = su_zalloc(ctx->ctx_home, sizeof (*zeroblock));
同时指出,调用su_home_unref后会将su_home_s结构体以及所有由su_home_s结构体分配的su_block_s结构体都释放掉。
su_home_new函数还算简单。我只是对于输入参数size的值有些疑问。首先,它必须大于su_home_t结构体的大小。然而,又将申请到的空间地址赋给home指针。
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;
}
如果这个size值是4096,那么让home指向这么一个4K大小的空间是何用意。