百度收录较好的网站站长工具seo综合查询 分析
目录
一、Span和Spanlist
二、CentralCache
一、Span和Spanlist
CentralCache其实也是哈希桶结构,只不过他是一个个的Span(Span是管理一定数量的页的结构),而且Span会管理一个freelist,用来挂起一个个的小内存块给ThreadCache使用。
同一个SpanList桶中的Span结构必须相同,除了freelist可以有不同的挂载情况。
SpanList的结构
这其中有一个字段叫做ustCount,他记录的就是该Span中的小内存块的使用情况,每给一个到ThreadCache,该字段就++,当ThreadCache调用ListTooLong归还内存块到CentralCache的时候再--。
一旦useCount被减到0了,则把该Span返还给PageCache。从而缓解内存碎片的问题。
当然CentralCache刚刚获取到Span的时候,useCount一定是0,那么万一此时刚好处于归还逻辑呢?岂不是又还回去了,白申请了。其实在Span中还有一个isuse表示是否正在被使用,就解决了这个问题。
//Span管理以页为单位的大块内存
struct Span
{PAGE_ID _pageId = 0; //起始页的页号size_t _pageN=0; //页的数量size_t _useCount=0; //Span中有多少个小内存块被分配给ThreadCache了void* _freelist=nullptr; //切好的小块内存块的自由链表,方便下一次分配给ThreadCache,不用在分配的时候切了,就好像定长池的memorybool _isuse=false; //是否正在使用size_t _objSize = 0; //小内存块的大小,即freelist中挂的一个个小对象节点的大小Span* _prev = nullptr;Span* _next = nullptr;
};//带头双向循环链表
class SpanList
{
public:SpanList(){_head = new Span();_head->_next = _head;_head->_prev = _head;}bool Empty(){return _head->_next == _head;}Span* PopFront(){Span* front = _head->_next;Erase(front);return front;}void PushFront(Span* span){Insert(Begin(),span);}Span* Begin(){return _head->_next;}Span* End(){return _head;}void Insert(Span* pos,Span* newSpan){assert(pos);assert(newSpan);//前SpanSpan* prev = pos->_prev;prev->_next = newSpan;newSpan->_prev = prev;newSpan->_next = pos;pos->_prev = newSpan;}void Erase(Span* pos){assert(pos);assert(pos!=_head);//前后SpanSpan* next = pos->_next;Span* prev = pos->_prev;prev->_next = next;next->_prev = prev;}std::mutex& GetMutex(){return _mutex;}private:Span* _head=nullptr;std::mutex _mutex; //桶锁
};
二、CentralCache
#pragma once
#include"Common.hpp"
#include"PageCache.hpp"//单例模式---饿汉
class CentralCache
{
public:static CentralCache* GetInstance(){return &_sInstance;}// 从中心缓存获取一定数量的对象给thread cachesize_t FetchRangeObj(void*& start, void*& end, size_t batchNum, size_t size);// 获取一个非空的spanSpan* GetOneSpan(SpanList& list, size_t byte_size);// 将一定数量的对象释放到span跨度void ReleaseListToSpans(void* start, size_t byte_size);private:static CentralCache _sInstance;CentralCache() {}CentralCache(const CentralCache&) = delete;
private:SpanList _spanlists[NFREELISTS];};//inline防止多个源文件包含导致重复定义
inline CentralCache CentralCache::_sInstance;void CentralCache::ReleaseListToSpans(void* start, size_t byte_size)
{//通过index可以知道该内存块是属于哪一个SpanList的size_t index = SizeClass::Index(byte_size);_spanlists[index].GetMutex().lock();while (start!=nullptr){void* next = NextObj(start);//1.找到该内存块是属于哪一个Span//当然可以通过内存块的地址,计算出页号,然后和SpanList中的Span的页号一个个比较,但是时间复杂度较高,采取哈希表映射Span* span = PageCache::GetInstance()->MapObjectToSpan(start);//2.把该内存块头插到Span的freelist中NextObj(start) = span->_freelist;span->_freelist = start;span->_useCount--;if (span->_useCount==0){//该Span已经没有人用了,归还到PageCache中_spanlists[index].Erase(span);span->_freelist = nullptr;span->_next = span->_prev = nullptr;_spanlists[index].GetMutex().unlock();PageCache::GetInstance()->GetMutex().lock();PageCache::GetInstance()->ReleaseSpanToPageCache(span);PageCache::GetInstance()->GetMutex().unlock();_spanlists[index].GetMutex().lock();}//一个内存块还回去了,往后走一步start = next;}_spanlists[index].GetMutex().unlock();
}Span* CentralCache::GetOneSpan(SpanList& list, size_t byte_size)
{//1.CentralCache有非空Span,直接取Span* it = list.Begin();while (it!=list.End()){if (it->_freelist!=nullptr){return it;}else{it = it->_next;}}//把CentralCache的桶锁解开,其他线程释放内存的时候,就不会阻塞了list.GetMutex().unlock();//2.如果CentralCache中没有,找PageCache要PageCache::GetInstance()->GetMutex().lock();Span* span=PageCache::GetInstance()->NewSpan(SizeClass::NumMovePage(byte_size));span->_objSize = byte_size;span->_isuse = true;PageCache::GetInstance()->GetMutex().unlock();//3.把从PageCache中要到的Span,切分成小的内存块,挂到Span的freelist中char* start = (char*)((span->_pageId) << PAGE_SHIFT); //起始地址(为什么pageId就是系统中的页号呢?为什么系统调用堆是从0页开始呢?)size_t bytes = (span->_pageN) << PAGE_SHIFT; //总字节数char* end = start + bytes;//先头插一个span->_freelist = start;start += byte_size;void* tail = span->_freelist;//再把剩下的插入while (start<end){//尾插NextObj(tail) = start;tail = NextObj(tail);start += byte_size;}NextObj(tail) = nullptr;//插入到SpanList的时候,需要加锁,因为插入的动作是临界资源list.GetMutex().lock();list.PushFront(span);return span;
}size_t CentralCache::FetchRangeObj(void*& start, void*& end, size_t batchNum, size_t size)
{size_t index = SizeClass::Index(size);//加锁_spanlists[index].GetMutex().lock();//1.获取从非空的Span,从这个Span中获取自由链表中的内存块节点Span* span = GetOneSpan(_spanlists[index],size);assert(span);assert(span->_freelist);start = span->_freelist;end = start;size_t i = 0;size_t actualNum = 1;while(i<batchNum-1 && NextObj(end)!=nullptr){end = NextObj(end);i++;actualNum++;}span->_freelist = NextObj(end);NextObj(end)=nullptr;//2.修改Span中的使用情况span->_useCount += actualNum;//解锁_spanlists[index].GetMutex().unlock();return actualNum;
}
值得注意的是:获取到span后我们要通过这个span的页数来知道这个span有多少内存,并且要通过这个span在程序地址空间的页号来判断这份内存的起始地址是多少!第0页的地址是0000 0000,第一页的地址是8KB,以此类推。
为什么从PageCache拿出Span给CentralCache的时候,需要建立一个页号和Span的映射关系呢?
因为PageCache的桶结构仍然是SpanList,但是和CentralCache不同的是:PageCache的桶中的Span是可以被修改的,即可以被切分成小的Span;而CentralCache的Span你开始的时候拿到的是多大的页,就永远是那么大了。
ThreadCache的内存块不被使用后,归还的时候可以计算出页号(页号通过小内存块的ptr指针计算)到了CentralCache,发现这个Span在不被使用后,归还给PageCache。而PageCache又会检查我的前后Span有没有归还,如果归还了,则可以把这两个Span合并成一个大的Span,从PageCache的若干个小桶移动到一个大桶中,从而缓解外碎片的问题。