04 页缓存资源层的初步设计

一、总体结构

页缓存资源层的结构与前两层不同了:

  • 线程资源层ThreadCache的结构是一个数组里放很多个桶,每个桶里放大小依次递增的内存块
  • 中心资源曾CentreCache的结构是一个数组里放很多个桶,每个桶里放着很多个Span包,每个Span包里放着很多个内存块,内存块的大小取决于Span包在哪个桶里,依次递增
  • 页缓存资源层PageCache的结构也是一个数组里放很多个桶,每个桶里也放着很多个Span包,但是每个Span包里存放的不是一个个小内存块了,而是一个完整的,占着X页的大号内存块(X为桶的编号),每页的大小是由程序里的PAGE_SHIFT常量指定的。

如图所示:

                                                                设定最大页桶为128页

 二、分配机理

       (1) 当中心资源层向页缓存资源层申请Span块时,首先应把所需的内存块大小转换为页数,然后向PageCache申请合适的页Span块。

  1.  例如1页的大小为8KB,中心资源层缺少64KB大小的Span块,经计算决定向PageCache申请4*64KB=256KB大小的空间
  2. 页缓存资源层计算256KB/8KB=32页,因此将分配一个包含着32页内存的页Span块给中心资源层
  3. 中心资源层得到包含着32页内存得页Span块后,对该页Span块进行切分,把页Span块内的连续内存,分割成一个个64KB的内存块,这样页Span块就转化成了中心资源层的普通Span块了
  4. 中心资源层将该Span块放入到对应的桶(SpanList)里去,就获得了一个新的Span块。

如图所示:

        (2)如果,页缓存资源层的32页桶中没有页Span块了呢?

        这就是页缓存资源层与前面两层最不同的地方了,前两层此时将向下层申请资源,但页缓存资源层已经是最低层了!此时必须加全锁(中心资源层是桶锁,因为桶与桶之间没有交互,桶锁即可),因为页缓存资源层一旦某个桶里没有页Span块了,立刻就会向更大的桶里去寻找页Span块,先是去33页桶去找,还没有就去34页桶去找......一旦找到,那么就从这个桶里取出页Span块,并对其进行切割!

        比如从40页桶里找到了页Span块,那么就将它切割为32页Span块和8页Span块,将32页Span块返回给中心资源层,8页Span块插入到8页桶里去。在这个过程中一旦有其它线程干扰,就会产生线程安全问题,因此在页缓存资源层必须加全锁。

(3)如果,直到找到最大的128页页桶还是没有找到页Span块呢?

        事实上,这一步正是向整个并发内存池中添加内存资源的一步!

        当128号页桶仍然没有页Span时,就该向系统申请资源了。通过调用Windows下的VirtualAlloc函数(Linux下为mmap函数),以页为单位像系统申请空间,一次申请128页。

#include "tool.hpp"
#include <iostream>
#if _WIN32
#include <Windows.h>
#else
#include <unistd.h>
#include <sys/mman.h>
#endif // _WIN32
using std::bad_alloc;
static const size_t SIZEofPAGE = 1 << PAGE_SHIFT;
inline static void* systemalloc(size_t kpage)
{
	void* ptr = nullptr;
#ifdef _WIN32
	ptr = VirtualAlloc(NULL, kpage * SIZEofPAGE, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
#else
	ptr = mmap(NULL, kpage * SIZEofPAGE, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_SHARED, -1, 0);//Linux下采用mmap接口,由操作系统在空闲堆区中选择一块合适的空间返回
#endif 
	if (ptr == NULL)
	{
		throw bad_alloc();
	}
	else
		return ptr;
}
//注意,无论是VirtualAlloc还是mmap函数,接口返回的地址一定是程序中定义的页大小(4K或8K)的倍数
注意:参考这篇文章VirtualAlloc函数与mmap函数所申请空间内存对齐的问题可知,系统返回的地址一定能是被程序定义的页的大小所整除的,无论是4KB还是8KB大小。

重新回顾Span块的构成:


//内存包span,里面包含了连续页的内存,是中心资源层和页缓存资源层的重要结构
struct Span
{
	PAGE_ID pageID = 0;//包中起始页在内存所有页中的编号(把内存视为一个个页,页编号计算方法为页起始地址/每页的大小)
	size_t counts = 0;//包中页的数量
	Span* prev = nullptr;
	Span* next = nullptr;

	void* freelist = nullptr;//里面存放着span内存包中被切好的内存块的“桶”
	size_t usedBlocks = 0;//已经被分配出去的内存块数量
};

那么现在可以明白了,这个pageID就是从堆申请来的内存空间的起始页的页号:

         这个从堆中申请来的占128页的超大内存块被与Span结构体绑定后,就插入到了页缓存资源层,这样一来,页缓存资源层就有资源给中心资源层调用了。比如中心资源层的请求是一个10页大小的页Span块,那么过程切割过程如下图所示:

 

         到此,并发内存池三层的申请内存这条路线已经大致介绍完毕,接下来便是回收内存与性能测试和调优了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值