分类: LINUX
2009-05-06 11:00:45
1. 我们的简单分配程序的全局变量
int has_initialized = 0;
void *managed_memory_start;
void *last_valid_address;
|
2. 分配程序初始化函数
/* Include the sbrk function */
#include
void malloc_init()
{
/* grab the last valid address from the OS */ last_valid_address = sbrk(0); /* we don't have any memory to manage yet, so *just set the beginning to be last_valid_address */
managed_memory_start = last_valid_address; /* Okay, we're initialized and ready to go */ has_initialized = 1;
}
|
3. 内存控制块结构定义
struct mem_control_block {
int is_available; int size; };
|
在讨论分配内存之前,我们将先讨论释放,因为它更简单。为了释放内存,我们必须要做的惟一一件事情就是,获得我们给出的指针,回退 sizeof(struct mem_control_block) 个字节,并将其标记为可用的。这里是对应的代码:
4. 解除分配函数
void free(void *firstbyte) {
struct mem_control_block *mcb; /* Backup from the given pointer to find the * mem_control_block
*/
mcb = firstbyte - sizeof(struct mem_control_block); /* Mark the block as being available */ mcb->is_available = 1; /* That's It! We're done. */ return;
}
|
5. 主分配程序的伪代码
1. If our allocator has not been initialized, initialize it. 2. Add sizeof(struct mem_control_block) to the size requested. 3. start at managed_memory_start.
4. Are we at last_valid address?
5. If we are:
A. We didn't find any existing space that was large enough -- ask the operating system for more and return that. 6. Otherwise:
A. Is the current space available (check is_available from the mem_control_block)? B. If it is: i) Is it large enough (check "size" from the mem_control_block)?
ii) If so: a. Mark it as unavailable
b. Move past mem_control_block and return the pointer
iii) Otherwise: a. Move forward "size" bytes b. Go back go step 4
C. Otherwise: i) Move forward "size" bytes ii) Go back to step 4
|
6. 主分配程序
void *malloc(long numbytes) {
/* Holds where we are looking in memory */ void *current_location; /* This is the same as current_location, but cast to a * memory_control_block
*/
struct mem_control_block *current_location_mcb; /* This is the memory location we will return. It will * be set to 0 until we find something suitable */
void *memory_location; /* Initialize if we haven't already done so */ if(! has_initialized) { malloc_init();
}
/* The memory we search for has to include the memory * control block, but the users of malloc don't need * to know this, so we'll just add it in for them. */
numbytes = numbytes + sizeof(struct mem_control_block); /* Set memory_location to 0 until we find a suitable * location
*/
memory_location = 0; /* Begin searching at the start of managed memory */ current_location = managed_memory_start; /* Keep going until we have searched all allocated space */ while(current_location != last_valid_address) {
/* current_location and current_location_mcb point * to the same address. However, current_location_mcb
* is of the correct type, so we can use it as a struct. * current_location is a void pointer so we can use it * to calculate addresses.
*/
current_location_mcb = (struct mem_control_block *)current_location; if(current_location_mcb->is_available)
{
if(current_location_mcb->size >= numbytes) { /* Woohoo! We've found an open, * appropriately-size location. */ /* It is no longer available */ current_location_mcb->is_available = 0; /* We own it */ memory_location = current_location; /* Leave the loop */ break; } }
/* If we made it here, it's because the Current memory * block not suitable; move to the next one
*/
current_location = current_location + current_location_mcb->size; }
/* If we still don't have a valid location, we'll * have to ask the operating system for more memory */
if(! memory_location) {
/* Move the program break numbytes further */ sbrk(numbytes);
/* The new memory will be where the last valid * address left off
*/
memory_location = last_valid_address; /* We'll move the last valid address forward * numbytes
*/
last_valid_address = last_valid_address + numbytes; /* We need to initialize the mem_control_block */ current_location_mcb = memory_location; current_location_mcb->is_available = 0; current_location_mcb->size = numbytes; }
/* Now, no matter what (well, except for error conditions), * memory_location has the address of the memory, including * the mem_control_block
*/
/* Move the pointer past the mem_control_block */ memory_location = memory_location + sizeof(struct mem_control_block); /* Return the pointer */ return memory_location; }
|
7. 编译分配程序
gcc -shared -fpic malloc.c -o malloc.so
|
8. 替换您的标准的 malloc
LD_PRELOAD=/path/to/malloc.so
export LD_PRELOAD
|
众 多可用的分配程序中最有名的就是上述这些分配程序。如果您的程序有特别的分配需求,那么您可能更愿意编写一个定制的能匹配您的程序内存分配方式的分配程 序。不过,如果不熟悉分配程序的设计,那么定制分配程序通常会带来比它们解决的问题更多的问题。要获得关于该主题的适当的介绍,请参阅 Donald Knuth 撰写的 The Art of Computer Programming Volume 1: Fundamental Algorithms 中的第 2.5 节“Dynamic Storage Allocation”(请参阅 参考资料中的链接)。它有点过时,因为它没有考虑虚拟内存环境,不过大部分算法都是基于前面给出的函数。
在 C++ 中,通过重载 operator new(),您可以以每个类或者每个模板为单位实现自己的分配程序。在 Andrei Alexandrescu 撰写的 Modern C++ Design 的第 4 章(“Small Object Allocation”)中,描述了一个小对象分配程序(请参阅 参考资料中的链接)。
不只是我们的内存管理器有缺点,基于 malloc() 的内存管理器仍然也有很多缺点,不管您使用的是哪个分配程序。对于那些需要保持长期存储的程序使用 malloc() 来 管理内存可能会非常令人失望。如果您有大量的不固定的内存引用,经常难以知道它们何时被释放。生存期局限于当前函数的内存非常容易管理,但是对于生存期超 出该范围的内存来说,管理内存则困难得多。而且,关于内存管理是由进行调用的程序还是由被调用的函数来负责这一问题,很多 API 都不是很明确。
这 样做的好处是,您不必追踪程序中某个给定的数据结构可能会遵循的每一条路径。每次对其局部的引用,都将导致计数的适当增加或减少。这样可以防止在使用数据 结构时释放该结构。不过,当您使用某个采用引用计数的数据结构时,您必须记得运行引用计数函数。另外,内置函数和第三方的库不会知道或者可以使用您的引用 计数机制。引用计数也难以处理发生循环引用的数据结构。
9. 基本的引用计数函数
/* Structure Definitions*/
/* Base structure that holds a refcount */ struct refcountedstruct
{
int refcount; }
/* All refcounted structures must mirror struct * refcountedstruct for their first variables */
/* Refcount maintenance functions */
/* Increase reference count */
void REF(void *data)
{
struct refcountedstruct *rstruct; rstruct = (struct refcountedstruct *) data; rstruct->refcount++;
}
/* Decrease reference count */
void UNREF(void *data)
{
struct refcountedstruct *rstruct; rstruct = (struct refcountedstruct *) data; rstruct->refcount--;
/* Free the structure if there are no more users */ if(rstruct->refcount == 0) {
free(rstruct);
}
}
|
10. 使用引用计数的示例
/* EXAMPLES OF USAGE */
/* Data type to be refcounted */
struct mydata
{
int refcount; /* same as refcountedstruct */ int datafield1; /* Fields specific to this struct */ int datafield2; /* other declarations would go here as appropriate */ };
/* Use the functions in code */
void dosomething(struct mydata *data)
{
REF(data);
/* Process data */ /* when we are through */ UNREF(data);
}
struct mydata *globalvar1;
/* Note that in this one, we don't decrease the * refcount since we are maintaining the reference * past the end of the function call through the * global variable */
void storesomething(struct mydata *data)
{
REF(data); /* passed as a parameter */ globalvar1 = data; REF(data); /* ref because of Assignment */ UNREF(data); /* Function finished */ }
|
在池式内存管理中,每次内存分配都会指定内存池,从中分配内存。每个内存池都有不同的生存期限。在 Apache 中, 有一个持续时间为服务器存在期的内存池,还有一个持续时间为连接的存在期的内存池,以及一个持续时间为请求的存在期的池,另外还有其他一些内存池。因此, 如果我的一系列函数不会生成比连接持续时间更长的数据,那么我就可以完全从连接池中分配内存,并知道在连接结束时,这些内存会被自动释放。另外,有一些实 现允许注册 清除函数(cleanup functions),在清除内存池之前,恰好可以调用它,来完成在内存被清理前需要完成的其他所有任务(类似于面向对象中的析构函数)。
11. obstack 的示例代码
#include
#include
/* Example code listing for using obstacks */ /* Used for obstack macros (xmalloc is
a malloc function that exits if memory is exhausted */ #define obstack_chunk_alloc xmalloc
#define obstack_chunk_free free
/* Pools */
/* Only permanent allocations should go in this pool */ struct obstack *global_pool;
/* This pool is for per-connection data */ struct obstack *connection_pool;
/* This pool is for per-request data */
struct obstack *request_pool;
void allocation_failed()
{
exit(1);
}
int main()
{
/* Initialize Pools */ global_pool = (struct obstack *) xmalloc (sizeof (struct obstack)); obstack_init(global_pool);
connection_pool = (struct obstack *) xmalloc (sizeof (struct obstack)); obstack_init(connection_pool);
request_pool = (struct obstack *) xmalloc (sizeof (struct obstack)); obstack_init(request_pool);
/* Set the error handling function */ obstack_alloc_failed_handler = &allocation_failed; /* Server main loop */ while(1)
{
wait_for_connection();
/* We are in a connection */ while(more_requests_available())
{
/* Handle request */ handle_request(); /* Free all of the memory allocated * in the request pool */ obstack_free(request_pool, NULL); }
/* We're finished with the connection, time * to free that pool
*/
obstack_free(connection_pool, NULL); }
}
int handle_request()
{
/* Be sure that all object allocations are allocated * from the request pool
*/
int bytes_i_need = 400; void *data1 = obstack_alloc(request_pool, bytes_i_need); /* Do stuff to process the request */ /* return */ return 0; }
|
垃圾收集(Garbage collection)是全自动地检测并移除不再使用的数据对象。垃圾收集器通常会在当可用内存减少到少于一个具体的阈值时运行。通常,它们以程序所知的可用的一组“基本”数据 —— 栈数据、全局变量、寄存器 —— 作 为出发点。然后它们尝试去追踪通过这些数据连接到每一块数据。收集器找到的都是有用的数据;它没有找到的就是垃圾,可以被销毁并重新使用这些无用的数据。 为了有效地管理内存,很多类型的垃圾收集器都需要知道数据结构内部指针的规划,所以,为了正确运行垃圾收集器,它们必须是语言本身的一部分。
一 切都需要折衷:性能、易用、易于实现、支持线程的能力等,这里只列出了其中的一些。为了满足项目的要求,有很多内存管理模式可以供您使用。每种模式都有大 量的实现,各有其优缺点。对很多项目来说,使用编程环境默认的技术就足够了,不过,当您的项目有特殊的需要时,了解可用的选择将会有帮助。下表对比了本文 中涉及的内存管理策略。
策略
|
分配速度
|
回收速度
|
局部缓存
|
易用性
|
通用性
|
实时可用
|
SMP 线程友好
|
定制分配程序
|
取决于实现
|
取决于实现
|
取决于实现
|
很难
|
无
|
取决于实现
|
取决于实现
|
简单分配程序
|
内存使用少时较快
|
很快
|
差
|
容易
|
高
|
否
|
否
|
GNU malloc
|
中
|
快
|
中
|
容易
|
高
|
否
|
中
|
Hoard
|
中
|
中
|
中
|
容易
|
高
|
否
|
是
|
引用计数
|
N/A
|
N/A
|
非常好
|
中
|
中
|
是(取决于 malloc 实现)
|
取决于实现
|
池
|
中
|
非常快
|
极好
|
中
|
中
|
是(取决于 malloc 实现)
|
取决于实现
|
垃圾收集
|
中(进行收集时慢)
|
中
|
差
|
中
|
中
|
否
|
几乎不
|
增量垃圾收集
|
中
|
中
|
中
|
中
|
中
|
否
|
几乎不
|
增量保守垃圾收集
|
中
|
中
|
中
|
容易
|
高
|
否
|
几乎不
|
关于 malloc 的文章
来自 developerWorks