1. 前言
本篇文章在上一篇文章的基础
上,对中心缓存的释放内存做补充
上一篇:
中心缓存具体实现(上)
本章重点:
本篇文章着重讲解中心缓存这一层
释放内存的全部过程,包括如何在线程
缓存中回收小块儿内存,以及如何将
自己的大块span内存还给页缓存
2. 中心缓存回收/还回内存的细节
第一步:
当线程缓存中的哈希桶中小块儿内存的个数大于了该线程缓存一次性向中心缓存中申请的小块儿内存的个数,此时小块儿内存会从线程缓存中还回到中心缓存的span中
细节问题一:
从线程缓存中还回来的小块儿内存是多个,然而中心缓存的桶中可能不止一个span,我们怎么知道哪个小块儿内存对应到哪个span?很明显随意将在小块儿内存还回到任意span肯定是不对的!在上一篇文章中我们提到过如何通过指针得到这块儿空间在程序地址空间上的页号,所以我们可以使用一个unordered_map来存储页号和span的映射关系,当线程缓存还回小块儿内存时,可以通过计算小块儿内存在地址空间的页号来从这个哈希表中找到对应的span,这就是解决了我们的问题!
第二步:
当中心缓存中的span结构体中的成员变量:use_count等于0时,代表这个span被分配出去的小块儿内存都已经还回来了,所以此时将这个span整体还给上一级,也就是页缓存
3. 中心缓存回收内存的代码实现
根据上面的讲解,每还回来一个小块儿
内存都要检查一下它对应的span结构
的use_count是否为0,如果为0就要将
整个span结构还给页缓存!
centralcache.h文件:
void CentralCache::ReleaseListToSpans(void* start, size_t size)//一个桶中有多个span,这些内存块儿属于哪个span是不确定的 { size_t index = AlignmentRule::Index(size);//计算在哪个桶 _spanlist[index]._mtx.lock(); //要想知道这些内存块分别在哪个span,将内存块的地址除8*1024,得到这个内存块属于第几页 while (start != nullptr) { void* next = *(void**)start; SpanData* span = PageCache::GetInstance()->MapObjectToSpan(start);//利用地址与span的映射函数,找到这个内存块对应的span *(void**)start = span->_freeList;//将内存块start头插到span中 span->_freeList = start; span->_useCount--;//还回来一次内存块,就将usecount--,减到0后就又把内存还给pagecache if (span->_useCount == 0)//此时说明span切分出去的所有小块儿内存都被还回来了,直接将整个span还给pagecache,pagecache再进行前后页的合并 { _spanlist[index].Erase(span); span->_freeList = nullptr;//span中的小块内存已经打乱了,但我知道起始地址和结束地址,可直接置空 span->_next = nullptr; span->_prev = nullptr; //还回来内存时不用加桶锁了,但是pagecache的整体大锁需要加上 _spanlist[index]._mtx.unlock(); PageCache::GetInstance()->_mtx.lock(); PageCache::GetInstance()->ReleaseSpanToPageCache(span); PageCache::GetInstance()->_mtx.unlock(); _spanlist[index]._mtx.lock(); } start = next; } _spanlist[index]._mtx.unlock(); }
注:对于代码的解释都在注释中
并且ReleaseSpanToPageCache函数
是页缓存需要实现的,这里暂时放一放
4. 对于页号与span映射的代码补充
由于这份代码是在pagecache中存放的并且页缓存还没有具体解释,所以看不懂没关系,把页缓存部分学完就都明白了.再一个,存储页号和span的映射关系的哈希表是存储在页缓存中的!因为不止在中心缓存中会使用到这种映射关系,在页缓存时同样页面临相同的问题,所以将它放在了最上层的页缓存中
pagecache.h文件中:
std::unordered_map<PAGE_ID, SpanData*> _idSpanMap;//存储页号和桶中对应的span的映射,解决换回来的内存对应哪个span的问题 //给我一个地址,返回这个地址对应的span SpanData* PageCache::MapObjectToSpan(void* obj) { PAGE_ID pageId = (PAGE_ID)obj >> PAGE_SHIFT;//将地址右移13位就算出了页号 std::unique_lock<std::mutex> lock(_mtx);//加锁 auto ret = _idSpanMap.find(pageId); if (ret != _idSpanMap.end())//若找到了对应的页号,就返回对应的span return _idSpanMap[pageId]; else assert(false);//没找到,证明前面的代码有问题 assert(ret != nullptr); return ret; }
5. 总结
中心缓存这一层的所有内容已经讲解完毕,很巧妙的是,中心缓存使用的是桶锁,只有两个不同的线程同时进入到同一个桶中才会有锁竞争问题,这也是这个项目比较快的原因之一.总的来说,中心缓存的作用是承上启下,负责给线程缓存分配切分好的小块儿内存,以及从线程缓存中回收内存.并且它也会向页缓存申请大块儿内存,并且会在合适的时候将大块儿内存还回去,方便页缓存结构进行内存合并!
🔎 下期预告:页缓存的具体实现🔍