热门标签 | HotTags
当前位置:  开发笔记 > 数据库 > 正文

PostgreSQL堆分配器mmgr详解

mmgr是postgresql的内存管理模块,其代码分布在aset.c,mctx.c和portalmem.c这三个文件之中。这里主要分析它的堆内存的管理机制,

mmgr是postgresql的内存管理模块,其代码分布在aset.c, mctx.c和portalmem.c这三个文件之中。这里主要分析它的堆内存的管理机制,

mmgr是postgresql的内存管理模块,其代码分布在aset.c, mctx.c和portalmem.c这三个文件之中。这里主要分析它的堆内存的管理机制,也就是aset.c文件中的内容。

AllocSetContext是堆内存管理的主要结构,其定义如下:

typedef struct AllocSetContext { AllocBlock blocks; AllocChunk freelist[ALLOCSET_NUM_FREELISTS]; Size initBlockSize; Size maxBlockSize; Size nextBlockSize; Size allocChunkLimit; AllocBlock keeper; } AllocSetContext;

其中的成员可以分为三个部分,我用三种不同的颜色标记了出来:

1) 黄色部分是AllocSetContext的头部,如果用面向对象的观点来看,可以说AllocSetContext是继承自MemoryContextData的,这里我们不关心MemoryContextData的定义,它提供了对AllocSetContext更高层次的管理。

2)粉色部分是一些配置参数。

3) 紫色部分是我们这篇博客的主角。blocks和freelist对应于堆内存管理的两个方面。下面详细描述。

AllocSetContext作为malloc/free与更上层应用的中间层,向下需要管理使用malloc从操作系统申请到的内存,对上则需要提供内存的获取与释放的接口。我们先说第一个方面,也就是它是如何管理malloc来的内存的。

AllocSetContext将从操作系统中申请到的内存放在一下链表中进行管理,这个链表就是紫色部分的blocks。blocks中的每一项的结构叫做AllocBlockData,它的定义如下:

typedef

AllocBlockData是向操作系统申请和释放内存的基本单位,为了简便起见,下面就直接叫它Block了。在接收到内存分配请求的时候,AllocSetContext如果发现blocks头部的那个Block没有足够的空间,就再从操作系统中申请一个更大的Block作为blocks的头。以后的分配出的内存就是从这个更大的Block上割出来的。这个更大是多大呢,pg将它设为前一个的2倍,也就是说blocks这个链表从尾部到头部,每向前一个Block的大小就会增长一倍。这里说的是通常情况,也会有非通常情况的,比如这个Block是不可能无限制地增大的,它有一个最大的可以分配的大小AllocSetContext::maxBlockSize,也还有来自其它方面的限制,要了解这个限制就得先说一说 AllocSetContext的另外一个主要功能了--对外提供获取内存和释放内存的接口。

AllocSetContext提供给外部使用的主要接口为:

static void *AllocSetAlloc(MemoryContext context, Size size);

static void AllocSetFree(MemoryContext context, void *pointer);

static void *AllocSetRealloc(MemoryContext context, void *pointer, Size size);

  你可以调用AllocSetAlloc(cxt,sizeof(XX)) 来获得一块可用的特定大小的内存,但是如果你天真地以为你需要的是15个字节的空间,它就会精确地给你15个字节,你就错了,实际上,为了你的这次内在请求,它给了你16个字节的空间,外加一个信息头。也就是说它给你返回的内存有统一的结构,大小也有特定地限制。

这个统一的内存结构的定义如下:

typedef struct AllocChunkData

{

/* aset is the owning aset if allocated, or the freelist link if free */

void *aset;

/* size is always the size of the usable space in the chunk */

Size size;

#ifdef MEMORY_CONTEXT_CHECKING

/* when debugging memory usage, also store actual requested size */

/* this is zero in a free chunk */

Size requested_size;

#endif

} AllocChunkData;

   AllocChunkData是返回内存的基本单位,,下面叫它Chunk。如果你申请到一个大小为16字节的内存,你实际拿到是像下图的这么个东西,返回给你的地址前面是有一个Chunk头的:

为了加快分配的速度和方便管理,每个Chunk里的可用空间都是2的整数次幂。同时还维护了一个叫做freelist的数组,这个数组的每一项都是一个Chunk的队列,每个队列内的Chunk大小都是一样的。下面是某时刻free list的内存布局图:

这个free list的Chunk的来源有两个方面:

1) 调用AllocSetFree回收来的Chunk;

2) 调用AllocSetAlloc时,如果发现blocks链表的第一个Block没有足够的空间,这时候会分配一个更大的Block,但是在做这样事之前,首先要将头上那个Block中还剩下的空间酌情格式化为一些Chunk加入到free list这中。

由于这个free list数组的大小是固定的,所以在free list之中最大的Chunk的可用空间也是有限的,如果申请比最大的Chunk所能提供的空间还要大的内存时,就会被认为这是在申请一块大内存,会直接从操作系统中malloc出来给你,这种内存对这个堆管理器来说只是个过客,free时是不会加入到free list中的,也就是说这种不会被堆管理器直接重利用。

好了, 堆内存的管理的基本结构数据结构就介绍这么多,是时候看看几个主要方法的实现了,这里只说一下几个最常用 的接口: AllocSetAlloc , AllocSetFree, AllocSetRealloc

下面这个图解释了AllocSetAlloc的流程:

1) 首先判断是不是在申请大内存?如果是转入2),否则转入3);

2) 直接malloc出大小为: size 加上Block和Chunk头的大小,调整好指针位置,返回;

3) 计算出实际分配的内存大小,这个大小是比size大的最小的2^k;

4) free list中是否有这种大小的Chunk? 若有转入5),否则转入6);

5) 从free list中取出一个满足条件的Chunk, 返回;

6) blocks链表的第一个Block是不是足够的空间,若有转入9),否则继续

7) 将第一个Block中剩余的空间格式化为一些Chunk,加入到free list中;

8) malloc出一个更大的Block,加入到blocks中,

9) 从Block中格式化一个满足条件的Chunk,返回。

AllocSetFree的操作比较的简单,如下图,其步骤为:

1) 根据传入的指针算出Chunk头的位置 ( p - sizeof(AllocChunkData));

2) 根据Chunk中的size信息判断是不是大内存,若不是,转入4),否则继续;

3) 直接free掉(是个过客,不再利用)返回。

4) 将Chunk入到合适的free list之中。

AllocSetRealloc的步骤如下:

1) 调整指针p的位置得到Chunk头的地址;

2) 如果再分配的大小比Chunk的要小,直接返回;

3) 如果是大内存,则直接调用realloc分配一个更大的Block,并更新blocks中对应的项(所有的Block在使用期间都被blocks管理着), 然后返回.

4) 否则则先调用 AllocSetFree,再调用 AllocSetAlloc.

这个堆管理器的最主要的功能和实现就叙述完了,最后看一下它提供的更高级的一些调试功能:

1) CLOBBER_FREED_MEMORY 野指针对任何一个C/C++程序员来说都是豺狼虎豹,如果定义了这个宏,在面对野指针的时候你会从容不少,因为这个宏启用了一项功能:AllocSetFree的内存都会被置为0x7f, 这样在你第一次使用野指针的时候就会立马发现它。

2) MEMORY_CONTEXT_CHECKING 由于在大多数情况下,mmgr返回的内存大小大于申请大小的最小的2次幂。所以内存或多或少总是有浪费的。但这也是提高内存分配所带来必然的损失,为了心理平衡一下,我们可以利用这多出来的内存,做其他的事,比如这个宏就会启用这样一项功能:在实际大小后面填充一0x7e,这样就可以检测是否有内存越界的事情发生了。


推荐阅读
  • 本文详细介绍如何使用Python进行配置文件的读写操作,涵盖常见的配置文件格式(如INI、JSON、TOML和YAML),并提供具体的代码示例。 ... [详细]
  • 利用存储过程构建年度日历表的详细指南
    本文将介绍如何使用SQL存储过程创建一个完整的年度日历表。通过实例演示,帮助读者掌握存储过程的应用技巧,并提供详细的代码解析和执行步骤。 ... [详细]
  • SQLite 动态创建多个表的需求在网络上有不少讨论,但很少有详细的解决方案。本文将介绍如何在 Qt 环境中使用 QString 类轻松实现 SQLite 表的动态创建,并提供详细的步骤和示例代码。 ... [详细]
  • MySQL 数据库迁移指南:从本地到远程及磁盘间迁移
    本文详细介绍了如何在不同场景下进行 MySQL 数据库的迁移,包括从一个硬盘迁移到另一个硬盘、从一台计算机迁移到另一台计算机,以及解决迁移过程中可能遇到的问题。 ... [详细]
  • 探讨如何真正掌握Java EE,包括所需技能、工具和实践经验。资深软件教学总监李刚分享了对毕业生简历中常见问题的看法,并提供了详尽的标准。 ... [详细]
  • 自学编程与计算机专业背景者的差异分析
    本文探讨了自学编程者和计算机专业毕业生在技能、知识结构及职业发展上的不同之处,结合实际案例分析两者的优势与劣势。 ... [详细]
  • 深入理解Java泛型:JDK 5的新特性
    本文详细介绍了Java泛型的概念及其在JDK 5中的应用,通过具体代码示例解释了泛型的引入、作用和优势。同时,探讨了泛型类、泛型方法和泛型接口的实现,并深入讲解了通配符的使用。 ... [详细]
  • 作为一名程序员,从大学步入职场后,常常感受到一种难以言喻的空虚感。这种感觉或许源于对生活的不满、职业发展的瓶颈,或是日常琐事带来的压力。本文将深入探讨这种复杂的情感,并尝试寻找解决之道。 ... [详细]
  • 深入解析:阿里实战 SpringCloud 微服务架构与应用
    本文将详细介绍 SpringCloud 在微服务架构中的应用,涵盖入门、实战和案例分析。通过丰富的代码示例和实际项目经验,帮助读者全面掌握 SpringCloud 的核心技术和最佳实践。 ... [详细]
  • 并发编程:深入理解设计原理与优化
    本文探讨了并发编程中的关键设计原则,特别是Java内存模型(JMM)的happens-before规则及其对多线程编程的影响。文章详细介绍了DCL双重检查锁定模式的问题及解决方案,并总结了不同处理器和内存模型之间的关系,旨在为程序员提供更深入的理解和最佳实践。 ... [详细]
  • 本文深入探讨了C++对象模型中的一些细节问题,特别是虚拟继承和析构函数的处理。通过具体代码示例和详细分析,揭示了书中某些观点的不足之处,并提供了更合理的解释。 ... [详细]
  • 随着网络安全威胁的不断演变,电子邮件系统成为攻击者频繁利用的目标。本文详细探讨了电子邮件系统中的常见漏洞及其潜在风险,并提供了专业的防护建议。 ... [详细]
  • 微软Exchange服务器遭遇2022年版“千年虫”漏洞
    微软Exchange服务器在新年伊始遭遇了一个类似于‘千年虫’的日期处理漏洞,导致邮件传输受阻。该问题主要影响配置了FIP-FS恶意软件引擎的Exchange 2016和2019版本。 ... [详细]
  • 深入理解Spring:Aware接口、异步编程与计划任务
    本文将带你深入了解Spring框架中的 Aware 接口、异步编程以及计划任务。通过具体示例和详细解释,帮助你掌握这些核心功能的实现原理和应用场景。 ... [详细]
  • TechStride 网站
    TechStride 成立于2014年初,致力于互联网前沿技术、产品创意及创业内容的聚合、搜索、学习与展示。我们旨在为互联网从业者提供更高效的新技术搜索、学习、分享和产品推广平台。 ... [详细]
author-avatar
开着宝马X6去赶集_692
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有