马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
×
放纵本身的欲望是最大的祸患; 评论别人的隐私是最大的罪过; 不知本身不对是最大的病痛。 --- 亚里士多德 ---
1 页缓存团体计划思绪
起首我们来看页缓存的计划思绪,明白思绪,代码就可以更加愉快的写出来,而且这个项目标调试比力困难,肯定肯定要过细明白计划思绪,把代码过细写好才气包管我们的开发服从!
页缓存是全部线程共享的,当线程缓存内部的_freelist中没有内存块时,会向中心缓存举行申请。中心缓存内部的_spanlist如果span中尚有内存块就可以举行返回,如果没有就要去页缓存举行Span的申请。页缓存全局只有一个!
页缓存内部与线程缓存和中心缓存不一样,内部不是通过内存巨细举行的映射,而是通过页的数目来举行映射。每次中心缓存申请是时间会标明申请的页数,进而找到对应页的链表,查察是否有可以利用的span,如果没有,就向页数大的链表查找,如果有就举行拆分!
- 页缓存内部存在以页数作为映射的链表数组!
- 页缓存内部的申请机制是先在对应页数的链表中举行查找,掷中就直接举行返回。没有有掷中就向页数大的链表中举行查找,掷中就举行拆分。如果没有掷中,就创建一个最大页数的span,挂载到对应链表上!
2 框架搭建
现在我们来通过单例模式搭建其根本的框架:
- 类内设置唯一静态对象,留意要在类外举行界说!
- 设置GetInstance(),可以获取唯一静态对象,在第一次获取时举行创建!
- 留意必要单例锁来举行掩护,由于第一次获取时很有大概会让两个线程同时进来,就会导致错误!
- class PageCache
- {
- public:
- //懒汉模式进行时间
- static PageCache* GetInstance()
- {
- if (_pinfo == nullptr)
- {
- std::unique_lock<std::mutex> lock(_single_mtx);
- if (_pinfo == nullptr)
- {
- _pinfo = new PageCache();
- }
- }
- return _pinfo;
- }
- //给中心缓存提供span的接口
- Span* NewSpan(size_t num);
- private:
- //根据span的页的个数进行分类
- SpanList _pageList[PAGENUM]; // 1 - 128
- //页锁
- std::mutex _page_mtx;
- //单例模式设计
- private:
- static PageCache* _pinfo; //需要类外声明
- static std::mutex _single_mtx; //单例锁
- //构造函数私有化
- PageCache() {};
- //拷贝构造 赋值重载 删除
- PageCache(const PageCache&) = delete;
- PageCache operator=(const PageCache&) = delete;
- };
复制代码 3 NewSpan函数
NewSpan函数通过中心缓存提供的页数来举行查找:
- 起首先在num对应的链表中查找是否有可以利用的span。
- 如果没有就向页数大的链表中举行查找,如果找到就举行拆分,分成两个span,一个用来返回,另一个再重新插入到对应链表中
- 如果没有就创建一个大span,挂载到最大页数对应的链表中,然后再次返回调用NewSpan举行递归即可!
- Span* PageCache::NewSpan(size_t num)
- {
- // 1 - 128
- assert(num > 0 && num < PAGENUM);
- //先在当前页对应的链表中进行寻找,没有就向后寻找
- if (!_pageList[num].Empty())
- {
- //不为空就直接返回一个span对象
- return _pageList->PopFront();
- }
- //如果没有 遍历后续的链表 查看有没有更大的span
- else
- {
- for (int i = num + 1; i < PAGENUM; i++)
- {
- if (!_pageList[i].Empty())
- {
- //不为空 说明存在页数为 i 的 span
- //进行拆分
- Span* kspan = new Span;
- Span* nspan = _pageList[i].PopFront();
- //kspan 的赋值
- kspan->_n = num;
- kspan->_pageid = nspan->_pageid;
- nspan->_n -= num;
- nspan->_pageid += num;
- _pageList[nspan->_n].PushFront(nspan);
- return kspan;
- }
- }
- }
- //到了这里就说明整个_pagelist中没有可以使用的span , 需要申请一个大span
- Span* bigspan = new Span;
- //申请大空间
- void* ptr = SystemAlloc(PAGENUM - 1);
- //通过ptr初始化newspan
- bigspan->_n = PAGENUM - 1;
- bigspan->_pageid = (PAGE_ID)ptr >> PAGE_SHIFT;
-
- //插入到_pagelist中
- _pageList[PAGENUM - 1].PushFront(bigspan);
- //递归调用
- return NewSpan(num);
- }
复制代码 留意NewSpan函数是对临界区的利用,必要举行加锁,但是这里计划到了递归调用函数,在函数内利用平凡的互斥锁会造成死锁,对于这个标题一样寻常有三种办理方案:
- 利用递归锁recursive_mutex,就可以克制出现递归造成的死锁标题!
- 设置子函数,我们函数NewSpan中举行上锁,然后调用子函数_NewSpan举行利用!
- 在进入NewSpan之前就上好锁,竣事退出之后再解锁,雷同子函数的办理方案!
这里尚有到场对应的内存开发函数,利用条件编译,使其适配全部的利用体系!
- #ifdef _WIN32
- #define NOMINMAX//避免和std中的min max冲突!
- #include<windows.h>
- #else
- // linux
- #endif
- // --- 直接去堆上按页申请空间 ---
- inline static void* SystemAlloc(size_t kpage)
- {
- #ifdef _WIN32
- void* ptr = VirtualAlloc(0, kpage << 13, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
- #else
- // linux下brk mmap等
- #endif
- if (ptr == nullptr)
- throw std::bad_alloc();
- return ptr;
- }
复制代码 4 哀求Span联动
我们回到中心缓存的GetOneSpan 函数中,线程缓存向这里索要span,中心缓存给不出就去页缓存申请Span:
- 起首先在中心缓存中的spanlist中举行查找,如果有就直接举行返回
- 没有span就去页缓存中举行申请,留意这里要把list桶锁解掉,由于利用会去到页缓存,离开list临界区。如果继续上锁会影响开释内存的利用!
- 从页缓存中获取到span就举行处置惩罚:
- 起首通过span的_pageid确定地点区间,然后通过start和end两个指针将span内部的大内存块拆分成小内存块尾插到span->_freelist中。尾插物理空间一连, 缓存利用率高!
- 末了将span插入到链表中,并将其返回!线程缓存会对span的_freelist中的内存块举行利用!
- Span* CentralCache::GetOneSpan(SpanList& list, size_t size)
- {
- //...等待联动...
- //向spanlist中寻找是否有span可以使用
- Span* it = list.Begin();
- //找到挂载着内存块的span!
- if (it != list.End())
- {
- if (it->_freelist != nullptr)
- {
- return it;
- }
- else
- {
- it = it->_next;
- }
- }
- //到这里说明spanlist中没有span对象
- //没有就向页缓存申请
- //1. 需要计算需要申请几个页! SizeClass::NumMovePage
- size_t num = SizeClass::NumMovePage(size);
- list.GetMutex().unlock();//将桶锁解开 避免影响该链表是内存的释放
- //根据页数申请span
- PageCache::GetInstance()->GetMuetx().lock(); // 上页锁
- Span* span = PageCache::GetInstance()->NewSpan(num);
- PageCache::GetInstance()->GetMuetx().unlock();
- //不需要立刻恢复桶锁 到临界区在上锁就可以
- //获取到span之后进行处理
- //通过获取的span内部的属性确定其映射的地址区间!
- //通过span的页号确定span的起始地址
- char* start = (char*)(span->_pageid << PAGE_SHIFT);
- char* end = start + (num << PAGE_SHIFT);//页数确定内存字节数大小
- //把大块内存切好挂接起来 --- 最好使用尾插 物理空间连续 缓存利用率高
- span->_freelist = start;
- start += size;
- void* tail = span->_freelist;
- while (start < end)
- {
- NextObj(tail) = start;
- tail = start; //tail = NextObj(tail)
- start += size;
- }
- //放入spanlist中 需要加锁
- list.GetMutex().lock();
- list.PushFront(span);
- return span;
- //return nullptr;
- }
复制代码 如许就形成闭环,我们的申请大抵的框架就已经完了,下一篇文章我们来举行申请联调的测试!!!
|