ACE框架提供了一个内存分配器模板,并且提供了(仅且)一个模板实例,基于共存内存的内存分配器。这个共存内存分配器模板实例在ACE框架应用于,基于内存映射的进程通讯,以及进程间同步等。
ACE内存分配器模板ACE_Malloc_T定义了这样一个分配器,它使用了三个组件,同步锁<ACE_LOCK>,内存(池)管理器<ACE_CB>以及内存服务<ACE_MEM_POOL_1>。
ACE框架用于IPC的分配器模板实例ACE_MEM_SAP::MALLOC_TYPE,绑定了这三个组件为:进程锁ACE_Process_Mutex,内存(池的)块管理器ACE_PI_Control_Block,以及内存映射服务ACE_MMAP_Memory_Pool。
虽然ACE_MMAP_Memory_Pool的名字包含了内存池一词,但是它只负责于如何使用系统内存管理服务进行整体内存索取,分配器只有一小部分接口对内存服务进行代理,分配器其余接口全部由ACE_PI_Control_Block提供功能服务。分配器ACE_Malloc_T使用ACE_MMAP_Memory_Pool向系统索取整体内存,在这一大块内存上使用ACE_PI_Control_Block提供内存块分配服务。
下面是ACE_MMAP_Memory_Pool的主要功能的类图
分配器的主要功能组件为ACE_PI_Control_Block,它实现了分配器的内存块管理功能,它将由ACE_MMAP_Memory_Pool索取到的内存页面进行统一的块管理服务,也就是我们所理解的分配器内存管理服务。分配器接口提供两种管理服务,一种是分配和释放(calloc,malloc,release),另一种是通过名字绑定(bind,unbind)向外公布。
ACE_PI_Control_Block使用了两个链表分别支持分配器的两种功能服务,分别对应为空闲块链表和命名块链表,其中空闲块链表是环链表,链表结尾指向链表头。内存块的分配和释放管理,应用了ACE_Malloc_Header头,将所有内存块进行管理,空闲的块被链入到空闲块链表。分配时将大于需要尺寸的空闲块进行拆分使用。命名块管理,将分配出去的内存,以名字绑定的方式,链入到共同可以访问的链表,以使其它进程可以从链表中搜索出来并访问。使用相同映射内存池的分配器,从空闲链表中分配内存块,但是分配出去的内存块离开了共同访问的区域,要共享到其它进程,必须使用名称绑定并放入到一个共同可以访问的区域,这就是命名块链表。这里必须注意的是,被绑定的块一定是相同分配器分配出来的块。绑定的名称,分配器会在绑定函数中,为名称跟随命名绑定节点一起分配空间,位置紧接在命名绑定节点后面,命名绑定节点的名字用位置无关引用指向这个空间的位置,并将名字复制到这里。这时被绑定的块重新回到了分配器的管理之中,必须要解除绑定之后才可以释放。否则会带来跨进程的灾难。
ACE_PI_Control_Block提供链表服务是基于位置无关的,P(osition)I(ndepent)。由于分配器模板实例是一个进程间共享的分配器,分配的内存块映射在各自进程的地址空间中,并不可以使用直接指针来进行进程共享访问,必须通过一种手段来达到目的。ACE框架使用了这样一种指针代理引用,ACE_Based_Pointer,用于位置无关引用。这个指针代理引用并不储存直接指针,而通过偏移量来间接存储内存块的地址。内存块位于映射内存的相对位置(偏移量)不会因为内存映射在不同的地址空间而发生变化。同样,这个指针代理引用必须是由相同的分配器分配出来的,不能够是栈上的对象或其它堆分配器分配出来的对象。同理,分配器被进程共享访问的所有的一切都必须存放在相同的映射内存中,我们的分配器通过ACE_MMAP_Memory_Pool打开相同的映射内存。而分配器中的核心部件ACE_PI_Control_Block,它的所有包括它自身都必须构建在映射内存上。
在位置无关的ACE_Based_Pointer计算地址时,即计算出目标的地址时,这里隐含了一点,this指针是存在着变化的,在不同的映射空间中,this指针发生了平移delta。但是与原点(映射的基地址)的距离是不变的,所以不论在那一个进程上如何映射,只要用this减去距离base_offset_就可以得到映射的基地址。
base_offset_ = this - base;
代入进程1的this1与base1得到base_offset = this1 - base1。
内存在进程2的映射基地址为base2,可能与进程1映射基地址不一致,存在一个变化delta = base1 - base2。
代入进程2的base2后有 base_offset_ = this - (base1 - delta),可以得到this = this1 - delta,就是进程2的this2,也就是平移后的值。
尽管我们看到的代码为base = this - base_offset_,感觉不到this在不同的进程映射空间中发生了平移,但实际运行时我们用平移后的this求值得出也就是平移后的映射基地址。