目录
一、PageCache的具体过程
页缓存主要解决的是内存外碎片问题,并且直接和系统调用打交道。
申请过程如下:
当中心缓存中没有内存时,会去页缓存申请一个span结构,要经过下面几步:(1)根据中心缓存的桶号来确定申请的span是几页的
(2)根据中心缓存想要申请的页数,找到页缓存中对应的桶(k页对应k号桶)
情况一: 页缓存的K号桶中存在span结构,直接将这块儿内存返回给中心缓存
情况二: 页缓存的K号桶没有span结构,但是K+1到128号桶中存在span结构,假设n号桶有span,则将这个大页的span切分为一个k页的span和一个n-k页的span,k页的span返回给中心缓存去使用,而将n-k页的span重新挂在n-k号桶中
情况三: k到128号桶都没有span,此时页缓存会向系统申请一份128页大小的内存,并挂在128号桶中,再将这个128页的span切分为k页的span和128-k页的span,也就转换为了情况二!
二、具体实现代码
#pragma once
#include"Common.hpp"
#include"ObjectPool.hpp"
#include<unordered_map>
#include"PageMap.hpp"
class PageCache
{
public:
static PageCache* GetInstance()
{
return &_sInstance;
}
//获取k页的Span
Span* NewSpan(size_t k);
std::mutex& GetMutex()
{
return _pageMutex;
}
// 获取从对象到span的映射
Span* MapObjectToSpan(void* obj);
// 释放空闲span回到Pagecache,并合并相邻的span
void ReleaseSpanToPageCache(Span* span);
private:
static PageCache _sInstance;
PageCache() {}
PageCache(const PageCache&) = delete;
private:
//页号和Span的映射关系,方便Span知道自己是属于哪个页的
std::unordered_map<PAGE_ID, Span*> _idSpanMap;
//TCMalloc_PageMap1<32 - PAGE_SHIFT> _idSpanMap;
SpanList _spanlists[NPAGES];
std::mutex _pageMutex;
ObjectPool<Span> _spanPool;
};
//单例模式---饿汉模式
/*inline */PageCache PageCache::_sInstance; //这个是什么呢
void PageCache::ReleaseSpanToPageCache(Span* span)
{
//如果大于128页,说明是找堆要的(如果直接找堆要的,但是在32页---128页之间,则不走这里,直接和之前的逻辑一样,让内存池管理)
if (span->_pageN>NPAGES-1)
{
void* ptr = (void*)(span->_pageId << PAGE_SHIFT);
SystemFree(ptr);
//delete span;
_spanPool.Delete(span);
return;
}
//1.看该Span的前能否合并,缓解内存碎片问题
while (1)
{
PAGE_ID prevId = span->_pageId - 1;
auto ret = _idSpanMap.find(prevId);
if (ret == _idSpanMap.end())
{
break;
}
Span* prevSpan = ret->second;
/*auto ret = (Span*)_idSpanMap.get(prevId);
if (ret==nullptr)
{
break;
}*/
if (prevSpan->_isuse==true)
{
break;
}
//不能合并出超过128页的Span
if (prevSpan->_pageN+span->_pageN>NPAGES-1)
{
break;
}
//合并
span->_pageId = prevSpan->_pageId;
span->_pageN += prevSpan->_pageN;
_spanlists[prevSpan->_pageN].Erase(prevSpan);
//delete prevSpan;
_spanPool.Delete(prevSpan);
}
//2.看该Span的后能否合并,缓解内存碎片问题
while (1)
{
PAGE_ID nextId = span->_pageId + span->_pageN;
auto ret = _idSpanMap.find(nextId);
if (ret == _idSpanMap.end())
{
break;
}
Span* nextSpan = ret->second;
/*auto ret = (Span*)_idSpanMap.get(nextId);
if (ret == nullptr)
{
break;
}
Span* nextSpan = ret;*/
if (nextSpan->_isuse == true)
{
break;
}
//不能合并出超过128页的Span
if (nextSpan->_pageN + span->_pageN > NPAGES - 1)
{
break;
}
//合并
span->_pageN += nextSpan->_pageN;
_spanlists[nextSpan->_pageN].Erase(nextSpan);
//delete nextSpan;
_spanPool.Delete(nextSpan);
}
//把合并出来的挂起来
_spanlists[span->_pageN].PushFront(span);
span->_isuse = false;
_idSpanMap[span->_pageId] = span;
_idSpanMap[span->_pageId+span->_pageN-1] = span;
//_idSpanMap.set(span->_pageId, span);
//_idSpanMap.set(span->_pageId + span->_pageN - 1, span);
}
Span* PageCache::MapObjectToSpan(void* obj)
{
//1.求出页号id
PAGE_ID id = ((PAGE_ID)obj >> PAGE_SHIFT);
//加锁raii
std::unique_lock<std::mutex> lock(_pageMutex);
//2.查找该内存块属于哪一个Span
auto ret = _idSpanMap.find(id);
if (ret!=_idSpanMap.end())
{
return ret->second;
}
else
{
assert(false);//在这里崩溃了,说明没有找到该Span--->说明有Span映射错误了-->找哪里有建立映射
return nullptr;
}
//auto ret=(Span*)_idSpanMap.get(id);
//assert(ret!=nullptr);
//return ret;
}
Span* PageCache::NewSpan(size_t k)
{
assert(k>0&&k<NPAGES);
//大于128页,直接找堆要
if (k>NPAGES-1)
{
void* ptr = SystemAlloc(k);
//创建一个Span对象,管理这个大块内存
//Span* span = new Span();
Span* span = _spanPool.New();
//计算该大块内存的页号
span->_pageId = (PAGE_ID)ptr >> PAGE_SHIFT;
span->_pageN = k;
//把该Span放入哈希映射表(因为PageCache最大管理128页,我直接向堆要的可能仍然小于128页,可以被内存池管理起来)
_idSpanMap[span->_pageId] = span;
//_idSpanMap.set(span->_pageId,span);
return span;
}
//0.看自己的桶中有没有,有则直接给
if (!_spanlists[k].Empty())
{
//return _spanlists[k].PopFront();
Span* kSpan = _spanlists[k].PopFront();
//只要从PageCache给出去的Span,都要在map中建立映射
for (PAGE_ID i = 0;i < kSpan->_pageN;i++)
{
_idSpanMap[kSpan->_pageId + i] = kSpan;
//_idSpanMap.set(kSpan->_pageId+i, kSpan);
}
return kSpan;
}
else
{
//1.看后面的桶有没有比k页大的span,有则切分
for (size_t i=k+1;i<NPAGES;i++)
{
if (!_spanlists[i].Empty())
{
Span* nSpan = _spanlists[i].PopFront();
Span* kSpan= _spanPool.New();
//切分
kSpan->_pageId = nSpan->_pageId;
kSpan->_pageN = k;
nSpan->_pageId += k;
nSpan->_pageN -= k;
//把nSpan挂到合适的位置
_spanlists[nSpan->_pageN].PushFront(nSpan);
//仍然留在PageCache中的SPan只需要记录最开始和最后的映射,因为他没有被使用,只会在合并的时候使用到页号
//存储n-kSpan的首尾页号跟Span的映射
_idSpanMap[nSpan->_pageId] = nSpan;
_idSpanMap[nSpan->_pageId + nSpan->_pageN - 1]=nSpan;
//_idSpanMap.set(nSpan->_pageId, nSpan);
//_idSpanMap.set(nSpan->_pageId + nSpan->_pageN - 1, nSpan);
//给CentralCache的都要建立映射,因为他会把该Span切分成小内存块给ThreadCache使用
//建立ID和SPan的映射,方便CentralCache回收小块内存时,查找对应的Span
for (PAGE_ID i=0;i<kSpan->_pageN;i++)
{
_idSpanMap[kSpan->_pageId + i] = kSpan;
//_idSpanMap.set(kSpan->_pageId + i, kSpan);
}
return kSpan;
}
}
//2.都没有,向堆申请128页的Span,挂到最后一个桶
Span* bigSpan = _spanPool.New();
void* ptr = SystemAlloc(NPAGES-1);
bigSpan->_pageId=(uint64_t)ptr>>PAGE_SHIFT;//这里要注意32位和64位的区别
bigSpan->_pageN = NPAGES - 1;
//挂起
_spanlists[NPAGES - 1].PushFront(bigSpan);
//3.对128页的Span进行切分成k页和128-k页的Span(这是一段重复逻辑,直接调用自己即可)
return NewSpan(k);
}
}