全部博文(73)
分类: LINUX
2009-04-23 17:22:25
内存池是另一种半自动内存管理方法。内存池帮助某些程序进行自动内存管理,这些程序会经历一些特定的阶段,而且每个阶段中都有分配给进程的特定阶段的内存。例如,很多网络服务器进程都会分配很多针对每个连接的内存 —— 内存的最大生存期限为当前连接的存在期。Apache 使用了池式内存(pooled memory),将其连接拆分为各个阶段,每个阶段都有自己的内存池。在结束每个阶段时,会一次释放所有内存。
在池式内存管理中,每次内存分配都会指定内存池,从中分配内存。每个内存池都有不同的生存期限。在 Apache 中,有一个持续时间为服务器存在期的内存池,还有一个持续时间为连接的存在期的内存池,以及一个持续时间为请求的存在期的池,另外还有其他一些内存池。因此,如果我的一系列函数不会生成比连接持续时间更长的数据,那么我就可以完全从连接池中分配内存,并知道在连接结束时,这些内存会被自动释放。另外,有一些实现允许注册 清除函数(cleanup functions),在清除内存池之前,恰好可以调用它,来完成在内存被清理前需要完成的其他所有任务(类似于面向对象中的析构函数)。
要在自己的程序中使用池,您既可以使用 GNU libc 的 obstack 实现,也可以使用 Apache 的 Apache Portable Runtime。GNU obstack 的好处在于,基于 GNU 的 Linux 发行版本中默认会包括它们。Apache Portable Runtime 的好处在于它有很多其他工具,可以处理编写多平台服务器软件所有方面的事情。要深入了解 GNU obstack 和 Apache 的池式内存实现,请参阅 参考资料部分中指向这些实现的文档的链接。
下面的假想代码列表展示了如何使用 obstack:
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; }
|
基本上,在操作的每一个主要阶段结束之后,这个阶段的 obstack 会被释放。不过,要注意的是,如果一个过程需要分配持续时间比当前阶段更长的内存,那么它也可以使用更长期限的 obstack,比如连接或者全局内存。传递给 obstack_free() 的 NULL 指出它应该释放 obstack 的全部内容。可以用其他的值,但是它们通常不怎么实用。
使用池式内存分配的益处如下所示:
池式内存的缺点是:
垃圾收集(Garbage collection)是全自动地检测并移除不再使用的数据对象。垃圾收集器通常会在当可用内存减少到少于一个具体的阈值时运行。通常,它们以程序所知的可用的一组“基本”数据 —— 栈数据、全局变量、寄存器 —— 作为出发点。然后它们尝试去追踪通过这些数据连接到每一块数据。收集器找到的都是有用的数据;它没有找到的就是垃圾,可以被销毁并重新使用这些无用的数据。为了有效地管理内存,很多类型的垃圾收集器都需要知道数据结构内部指针的规划,所以,为了正确运行垃圾收集器,它们必须是语言本身的一部分。
Hans Boehm 的保守垃圾收集器是可用的最流行的垃圾收集器之一,因为它是免费的,而且既是保守的又是增量的,可以使用 --enable-redirect-malloc 选项来构建它,并且可以将它用作系统分配程序的简易替代者(drop-in replacement)(用 malloc/ free 代替它自己的 API)。实际上,如果这样做,您就可以使用与我们在示例分配程序中所使用的相同的 LD_PRELOAD 技巧,在系统上的几乎任何程序中启用垃圾收集。如果您怀疑某个程序正在泄漏内存,那么您可以使用这个垃圾收集器来控制进程。在早期,当 Mozilla 严重地泄漏内存时,很多人在其中使用了这项技术。这种垃圾收集器既可以在 Windows® 下运行,也可以在 UNIX 下运行。
垃圾收集的一些优点:
其缺点包括:
一切都需要折衷:性能、易用、易于实现、支持线程的能力等,这里只列出了其中的一些。为了满足项目的要求,有很多内存管理模式可以供您使用。每种模式都有大量的实现,各有其优缺点。对很多项目来说,使用编程环境默认的技术就足够了,不过,当您的项目有特殊的需要时,了解可用的选择将会有帮助。下表对比了本文中涉及的内存管理策略。
1. 内存分配策略的对比
策略 |
分配速度 |
回收速度 |
局部缓存 |
易用性 |
通用性 |
实时可用 |
SMP 线程友好 |
定制分配程序 |
取决于实现 |
取决于实现 |
取决于实现 |
很难 |
无 |
取决于实现 |
取决于实现 |
简单分配程序 |
内存使用少时较快 |
很快 |
差 |
容易 |
高 |
否 |
否 |
GNU malloc |
中 |
快 |
中 |
容易 |
高 |
否 |
中 |
Hoard |
中 |
中 |
中 |
容易 |
高 |
否 |
是 |
引用计数 |
N/A |
N/A |
非常好 |
中 |
中 |
是(取决于 malloc 实现) |
取决于实现 |
池 |
中 |
非常快 |
极好 |
中 |
中 |
是(取决于 malloc 实现) |
取决于实现 |
垃圾收集 |
中(进行收集时慢) |
中 |
差 |
中 |
中 |
否 |
几乎不 |
增量垃圾收集 |
中 |
中 |
中 |
中 |
中 |
否 |
几乎不 |
增量保守垃圾收集 |
中 |
中 |
中 |
容易 |
高 |
否 |
几乎不 |
Web 上的文档
基本的分配程序
池式分配程序
智能指针和定制分配程序
垃圾收集器
关于现代操作系统中的虚拟内存的文章
关于
malloc 的文章
关于定制分配程序的文章
关于垃圾收集的文章
Web 上的通用参考资料
书籍
来自
developerWorks
Jonathan Bartlett 是 一书的作者,这本书介绍的是 Linux 汇编语言编程。Jonathan Bartlett 是 New Media Worx 的总开发师,负责为客户开发 Web、视频、kiosk 和桌面应用程序。您可以通过 与 Jonathan 联系。