Chinaunix首页 | 论坛 | 博客
  • 博客访问: 192430
  • 博文数量: 73
  • 博客积分: 5000
  • 博客等级: 大校
  • 技术积分: 1160
  • 用 户 组: 普通用户
  • 注册时间: 2009-04-23 15:53
文章分类

全部博文(73)

文章存档

2011年(1)

2009年(72)

我的朋友

分类: LINUX

2009-04-23 17:22:25

内存池是另一种半自动内存管理方法。内存池帮助某些程序进行自动内存管理,这些程序会经历一些特定的阶段,而且每个阶段中都有分配给进程的特定阶段的内存。例如,很多网络服务器进程都会分配很多针对每个连接的内存 —— 内存的最大生存期限为当前连接的存在期。Apache 使用了池式内存(pooled memory),将其连接拆分为各个阶段,每个阶段都有自己的内存池。在结束每个阶段时,会一次释放所有内存。

在池式内存管理中,每次内存分配都会指定内存池,从中分配内存。每个内存池都有不同的生存期限。在 Apache 中,有一个持续时间为服务器存在期的内存池,还有一个持续时间为连接的存在期的内存池,以及一个持续时间为请求的存在期的池,另外还有其他一些内存池。因此,如果我的一系列函数不会生成比连接持续时间更长的数据,那么我就可以完全从连接池中分配内存,并知道在连接结束时,这些内存会被自动释放。另外,有一些实现允许注册 清除函数(cleanup functions,在清除内存池之前,恰好可以调用它,来完成在内存被清理前需要完成的其他所有任务(类似于面向对象中的析构函数)。

要在自己的程序中使用池,您既可以使用 GNU libc obstack 实现,也可以使用 Apache Apache Portable RuntimeGNU 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 的全部内容。可以用其他的值,但是它们通常不怎么实用。

使用池式内存分配的益处如下所示:

  • 应用程序可以简单地管理内存。
  • 内存分配和回收更快,因为每次都是在一个池中完成的。分配可以在 O(1) 时间内完成,释放内存池所需时间也差不多(实际上是 O(n) 时间,不过在大部分情况下会除以一个大的因数,使其变成 O(1))。
  • 可以预先分配错误处理池(Error-handling pools),以便程序在常规内存被耗尽时仍可以恢复。
  • 有非常易于使用的标准实现。

池式内存的缺点是:

  • 内存池只适用于操作可以分阶段的程序。
  • 内存池通常不能与第三方库很好地合作。
  • 如果程序的结构发生变化,则不得不修改内存池,这可能会导致内存管理系统的重新设计。
  • 您必须记住需要从哪个池进行分配。另外,如果在这里出错,就很难捕获该内存池。

 

垃圾收集(Garbage collection是全自动地检测并移除不再使用的数据对象。垃圾收集器通常会在当可用内存减少到少于一个具体的阈值时运行。通常,它们以程序所知的可用的一组基本数据 —— 栈数据、全局变量、寄存器 —— 作为出发点。然后它们尝试去追踪通过这些数据连接到每一块数据。收集器找到的都是有用的数据;它没有找到的就是垃圾,可以被销毁并重新使用这些无用的数据。为了有效地管理内存,很多类型的垃圾收集器都需要知道数据结构内部指针的规划,所以,为了正确运行垃圾收集器,它们必须是语言本身的一部分。

  • 复制(copying): 这些收集器将内存存储器分为两部分,只允许数据驻留在其中一部分上。它们定时地从基本的元素开始将数据从一部分复制到另一部分。内存新近被占用的部分现在成为活动的,另一部分上的所有内容都认为是垃圾。另外,当进行这项复制操作时,所有指针都必须被更新为指向每个内存条目的新位置。因此,为使用这种垃圾收集方法,垃圾收集器必须与编程语言集成在一起。
  • 标记并清理(Mark and sweep):每一块数据都被加上一个标签。不定期的,所有标签都被设置为 0,收集器从基本的元素开始遍历数据。当它遇到内存时,就将标签标记为 1。最后没有被标记为 1 的所有内容都认为是垃圾,以后分配内存时会重新使用它们。
  • 增量的(Incremental):增量垃圾收集器不需要遍历全部数据对象。因为在收集期间的突然等待,也因为与访问所有当前数据相关的缓存问题(所有内容都不得不被页入(page-in)),遍历所有内存会引发问题。增量收集器避免了这些问题。
  • 保守的(Conservative):保守的垃圾收集器在管理内存时不需要知道与数据结构相关的任何信息。它们只查看所有数据类型,并假定它们 可以全部都是指针。所以,如果一个字节序列可以是一个指向一块被分配的内存的指针,那么收集器就将其标记为正在被引用。有时没有被引用的内存会被收集,这样会引发问题,例如,如果一个整数域中包含一个值,该值是已分配内存的地址。不过,这种情况极少发生,而且它只会浪费少量内存。保守的收集器的优势是,它们可以与任何编程语言相集成。

Hans Boehm 的保守垃圾收集器是可用的最流行的垃圾收集器之一,因为它是免费的,而且既是保守的又是增量的,可以使用 --enable-redirect-malloc 选项来构建它,并且可以将它用作系统分配程序的简易替代者(drop-in replacement)(用 malloc/ free 代替它自己的 API)。实际上,如果这样做,您就可以使用与我们在示例分配程序中所使用的相同的 LD_PRELOAD 技巧,在系统上的几乎任何程序中启用垃圾收集。如果您怀疑某个程序正在泄漏内存,那么您可以使用这个垃圾收集器来控制进程。在早期,当 Mozilla 严重地泄漏内存时,很多人在其中使用了这项技术。这种垃圾收集器既可以在 Windows® 下运行,也可以在 UNIX 下运行。

垃圾收集的一些优点:

  • 您永远不必担心内存的双重释放或者对象的生命周期。
  • 使用某些收集器,您可以使用与常规分配相同的 API

其缺点包括:

  • 使用大部分收集器时,您都无法干涉何时释放内存。
  • 在多数情况下,垃圾收集比其他形式的内存管理更慢。
  • 垃圾收集错误引发的缺陷难于调试。
  • 如果您忘记将不再使用的指针设置为 null,那么仍然会有内存泄漏。

一切都需要折衷:性能、易用、易于实现、支持线程的能力等,这里只列出了其中的一些。为了满足项目的要求,有很多内存管理模式可以供您使用。每种模式都有大量的实现,各有其优缺点。对很多项目来说,使用编程环境默认的技术就足够了,不过,当您的项目有特殊的需要时,了解可用的选择将会有帮助。下表对比了本文中涉及的内存管理策略。

1. 内存分配策略的对比

策略

分配速度

回收速度

局部缓存

易用性

通用性

实时可用

SMP 线程友好

定制分配程序

取决于实现

取决于实现

取决于实现

很难

取决于实现

取决于实现

简单分配程序

内存使用少时较快

很快

容易

GNU malloc

容易

Hoard

容易

引用计数

N/A

N/A

非常好

是(取决于 malloc 实现)

取决于实现

非常快

极好

是(取决于 malloc 实现)

取决于实现

垃圾收集

中(进行收集时慢)

几乎不

增量垃圾收集

几乎不

增量保守垃圾收集

容易

几乎不

 

  • 您可以参阅本文在 developerWorks 全球站点上的 英文原文

Web 上的文档

  • 提供了 obstacks 编程接口。
  • 描述了它们的池式分配程序的接口。

基本的分配程序

  • 是最流行的内存分配程序之一。
  • 用于大部分基于 BSD 的系统中。
  • 起源于 Doug Lea malloc,用于 GLIBC 之中。
  • 是一个为多线程应用程序优化的 malloc 实现。
  • 是一个基于 mmap() malloc 实现。

池式分配程序

  • GNU Libc 的组成部分)是安装最多的池式分配程序,因为在每一个基于 glibc 的系统中都有它。
  • 是应用最为广泛的池式分配程序。
  • 有其自己的池式分配程序。
  • 也有其自己的池式分配程序。
  • 是一个池式分配程序,是 Samba 的组成部分。

智能指针和定制分配程序

  • 有很多为 C++ 实现的通用模式,包括智能指针和一个定制的小对象分配程序。

垃圾收集器

  • 是最流行的开源垃圾收集器,它可以用于常规的 C/C++ 程序。

关于现代操作系统中的虚拟内存的文章

关于 malloc 的文章

  • Poul-Henning Kamp 撰写的 讨论的是 malloc 以及它如何与 BSD 虚拟内存交互。
  • BergerMcKinleyBlumofe Wilson 合著的 讨论了 Hoard 分配程序的实现。
  • Marshall Kirk McKusick Michael J. Karels 合著的 讨论了内核级的分配程序。
  • Doug Lea 撰写的 给出了一个关于设计和实现分配程序的概述,其中包括设计选择与折衷。
  • Emery D. Berger 撰写的 讨论的是定制内存管理以及它如何影响高性能应用程序。

关于定制分配程序的文章

  • Doug Lea 撰写的 描述的是为 C++ 类编写定制分配程序。
  • BergerZorn McKinley 合著的 讨论了如何编写定制分配程序来加快具体工作的速度。
  • BergerZorn McKinley 合著的 Reconsidering Custom Memory Allocation 再次提及了定制分配的主题,看是否真正值得为其费心。

关于垃圾收集的文章

  • Paul R. Wilson 撰写的 给出了垃圾收集的一个基本概述。
  • Benjamin Zorn 撰写的 给出了关于垃圾收集和性能的硬数据(hard data)。
  • Hans-Juergen Boehm 撰写的 给出了关于垃圾收集的神话(myths)。
  • Hans-Juergen Boehm 撰写的 是一篇描述他的用于 C/C++ 的垃圾收集器的文章。

Web 上的通用参考资料

  • 中有很多关于内存管理参考资料和技术文章的链接。
  • 是非常好的一组关于此主题的技术文章。
  • 讨论的是为 C++ 编写定制的分配程序。
  • 讨论了程序员进行内存管理时的一些选择。
  • 讨论了关于垃圾收集您需要了解的所有内容。
  • 有指向任何您想要的关于垃圾收集的文章的链接。

书籍

  • Michael Daconta 撰写的 介绍了关于内存管理的很多技术。
  • Frantisek Franek 撰写的 Memory as a Programming Concept in C and C++ 讨论了有效使用内存的技术与工具,并给出了在计算机编程中应当引起注意的内存相关错误的角色。
  • Richard Jones Rafael Lins 合著的 Garbage Collection: Algorithms for Automatic Dynamic Memory Management 描述了当前使用的最常见的垃圾收集算法。
  • Donald Knuth 撰写的 The Art of Computer Programming 1 Fundamental Algorithms 的第 2.5 “Dynamic Storage Allocation”中,描述了实现基本的分配程序的一些技术。
  • Donald Knuth 撰写的 The Art of Computer Programming 1 Fundamental Algorithms 的第 2.3.5 “Lists and Garbage Collection”中,讨论了用于列表的垃圾收集算法。
  • Andrei Alexandrescu 撰写的 Modern C++ Design 4 “Small Object Allocation”描述了一个比 C++ 标准分配程序效率高得多的一个高速小对象分配程序。
  • Andrei Alexandrescu 撰写的 Modern C++ Design 7 “Smart Pointers”描述了在 C++ 中智能指针的实现。
  • Jonathan 撰写的 8 “Intermediate Memory Topics”中有本文使用的简单分配程序的一个汇编语言版本。

来自 developerWorks

  • 自我管理数据缓冲区内存 developerWorks2004 1 月)略述了一个用于管理内存的自管理的抽象数据缓存器的伪 C pseudo-C)实现。
  • A framework for the user defined malloc replacement feature developerWorks2002 2 月)展示了如何利用 AIX 中的一个工具,使用自己设计的内存子系统取代原有的内存子系统。
  • 掌握 Linux 调试技术 developerWorks2002 8 月)描述了可以使用调试方法的 4 种不同情形:段错误、内存溢出、内存泄漏和挂起。
  • 处理 Java 程序中的内存漏洞 developerWorks2001 2 月)中,了解导致 Java 内存泄漏的原因,以及何时需要考虑它们。
  • developerWorks Linux 专区中,可以找到更多为 Linux 开发人员准备的参考资料。
  • developerWorks Speed-start your Linux app 专区中,可以下载运行于 Linux 之上的 IBM 中间件产品的免费测试版本,其中包括 WebSphere® Studio Application DeveloperWebSphere Application ServerDB2® Universal DatabaseTivoli® Access Manager Tivoli Directory Server,查找 how-to 文章和技术支持。
  • 通过参与 developerWorks blogs 加入到 developerWorks 社区。
  • 可以在 Developer Bookstore Linux 专栏中定购 打折出售的 Linux 书籍

 

Jonathan Bartlett 一书的作者,这本书介绍的是 Linux 汇编语言编程。Jonathan Bartlett New Media Worx 的总开发师,负责为客户开发 Web、视频、kiosk 和桌面应用程序。您可以通过 Jonathan 联系。

阅读(922) | 评论(0) | 转发(0) |
0

上一篇:线程安全

下一篇:学习shell的起步

给主人留下些什么吧!~~