PageCache

目录

一、PageCache的具体过程

二、具体实现代码


一、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);
	}
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值