目录
- 前言
- 1. 中心缓存回收/还回内存的细节
- 2. 中心缓存回收内存的代码实现
- 3. 对于页号与span映射的代码补充
- 4. 总结
前言
本篇文章在上一篇文章的基础上,对中心缓存的释放内存做补充。
本章重点:
本篇文章着重讲解中心缓存这一层释放内存的全部过程,包括如何在线程缓存中回收小块儿内存,以及如何将自己的大块span内存还给页缓存。
1. 中心缓存回收/还回内存的细节
第一步:
当线程缓存中的哈希桶中小块儿内存的个数大于了该线程缓存一次性向中心缓存中申请的小块儿内存的个数,此时小块儿内存会从线程缓存中还回到中心缓存的span中。
细节问题:
从线程缓存中还回来的小块儿内存是多个,然而中心缓存的桶中可能不止一个span,我们怎么知道哪个小块儿内存对应到哪个span?很明显随意将在小块儿内存还回到任意span肯定是不对的!在上一篇文章中我们提到过如何通过指针得到这块儿空间在程序地址空间上的页号,所以我们可以使用一个unordered_map来存储页号和span的映射关系,当线程缓存还回小块儿内存时,可以通过计算小块儿内存在地址空间的页号来从这个哈希表中找到对应的span,这就是解决了我们的问题!
第二步:
当中心缓存中的span结构体中的成员变量:_useCount等于0时,代表这个span被分配出去的小块儿内存都已经还回来了,所以此时将这个span整体还给上一级,也就是页缓存。
2. 中心缓存回收内存的代码实现
根据上面的讲解,每还回来一个小块儿内存都要检查一下它对应的span结构的_useCount是否为0,如果为0就要将整个span结构还给页缓存!
centralcache.h文件:
// 将一定数量的对象释放到span跨度
void CentralCache::ReleaseListToSpans(void* start, size_t size)
{// thread cache 和 central cache的映射规则是一样的,先计算还回哪个桶size_t index = SizeClass::Index(size);_spanList[index]._mtx.lock();// 那如何确定链表里的每个小块内存对象是属于哪个span的呢?// 通过页数和起始地址之间的关系进行计算。while (start){void* next = NextObj(start);// 找到对应的spanSpan* span = PageCache::GetInstance()->MapObjectToSpan(start);// 使用头插,把小对象挂回到对应的span里NextObj(start) = span->_freeList;span->_freeList = start;span->_useCount--;// 说明span切分出去的所有小块内存都回来了// 这个span就可以再回收给page cache,page cache可以再尝试去做前后页的合并if (span->_useCount == 0){_spanList[index].Erase(span); // 把这个span从链表里解开span->_freeList = nullptr; // span下面挂的小块内存不用特殊处理span->_next = nullptr;span->_prev = nullptr;// 释放span给page cache时,使用page cache的锁就可以了// 这时把桶锁解掉_spanList[index]._mtx.unlock();PageCache::GetInstance()->_pageMtx.lock();PageCache::GetInstance()->ReleaseSpanToPageCache(span);PageCache::GetInstance()->_pageMtx.unlock();_spanList[index]._mtx.lock();}start = next;}_spanList[index]._mtx.unlock();
}
注:对于代码的解释都在注释中
并且ReleaseSpanToPageCache函数
是页缓存需要实现的,这里暂时放一放。
3. 对于页号与span映射的代码补充
由于这份代码是在pagecache中存放的并且页缓存还没有具体解释,所以看不懂没关系,把页缓存部分学完就都明白了。再一个,存储页号和span的映射关系的哈希表是存储在页缓存中的!因为不止在中心缓存中会使用到这种映射关系,在页缓存时同样页面临相同的问题,所以将它放在了最上层的页缓存中。
pagecache.cpp文件中:
Span* PageCache::MapObjectToSpan(void* obj)
{// 计算页号PAGE_ID id = ((PAGE_ID)obj >> PAGE_SHIFT); 使用RAII的锁,保证使用STL的线程安全问题std::unique_lock<std::mutex> lock(_pageMtx); 在映射关系里找到对应的spanauto ret = _idSpanMap.find(id);if (ret != _idSpanMap.end()){return ret->second;}else{assert(false);return nullptr;}
}
4. 总结
中心缓存这一层的所有内容已经讲解完毕,很巧妙的是,中心缓存使用的是桶锁,只有两个不同的线程同时进入到同一个桶中才会有锁竞争问题,这也是这个项目比较快的原因之一。总的来说,中心缓存的作用是承上启下,负责给线程缓存分配切分好的小块儿内存,以及从线程缓存中回收内存。并且它也会向页缓存申请大块儿内存,并且会在合适的时候将大块儿内存还回去,方便页缓存结构进行内存合并!