欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 财经 > 产业 > 第六章:Page Cache申请内存

第六章:Page Cache申请内存

2025/7/26 0:58:12 来源:https://blog.csdn.net/2303_78095330/article/details/145368221  浏览:    关键词:第六章:Page Cache申请内存

目录

第一节:Page Cache构建

        1-1.Common.h

        1-2.CentralCache.cpp

        1-3.PageCache.h

        1-4.PageCache.cpp

第二节:线程安全问题

第三节:完整代码

第四节:下期预告


 

第一节:Page Cache构建

        1-1.Common.h

        Page Cache会从系统申请一个span,故创建一个SpanList的数组来保存这些span,同时将数组大小设置成NPAGE=128+1,这样下标为1的位置存放大小为1页的span、下标为2的位置存放大小为2页的span,以此类推:

const static size_t NPAGE = 128+1; // pc的哈希桶数量

        其次,如果cc要从pc获取一个span, 那么还需要让pc知道给cc多少页的span比较合适,所以在SizeClass类中定义一个名为NumMovePage的函数,传入需要的内存字节数,得到所需页数:

// pc一次向系统申请的页数
static size_t NumMovePage(size_t alignSize) {size_t num = NumMoveSize(alignSize); // 一次申请上限所对应的页数size_t npage = num * alignSize;npage >>= PAGE_SHIFT;if (npage == 0) npage = 1;return npage;
}

        1-2.CentralCache.cpp

        因为该函数需要获取一个PageCache实例,所以需要包含文件PageCache.h。

        在该文件中完善GetOneSpan的逻辑,cc要获得一个span,可以从自己的缓存获得,也可以从pc获取。

        无论span从哪里获取,都是为切分的一整块span,需要进行切分后再给tc使用:

Span* GetOneSpan(SpanList& ls, size_t alignSize) {// 优先使用cc中已经缓存的spanSpan* cur = ls.Begin();while (cur != ls.End()) {if (cur->_freeList != nullptr) // 该span有内存块{return cur;}cur = cur->_next;}// 先解锁ls,允许其他线程释放内存,去访问pcls.UnLock();Span* span = PageCache::GetInst()->NewSpan(SizeClass::NumMovePage(alignSize));span->_objSize = alignSize;span->_isUse = true;// 将span切成小块(即大块内存->自由链表)size_t bytes = (span->_n) << PAGE_SHIFT; // 得到span的大小 _n记录页数char* start = (char*)((span->_pageId) << PAGE_SHIFT); // 得到span(未切分)的起始地址 _pageId记录页号char* end = start + bytes; // 得到span(未切分)的结尾//1.先切一块作头span->_freeList = start;start += alignSize;void* tail = span->_freeList;//2.一块一块切、尾插,直到切完(start==end)while (start < end) {NextObj(tail) = start;tail = start;start += alignSize;}NextObj(tail) = nullptr;//3.锁上cc的桶ls,将切好的span头插进ls中ls.Lock();ls.Insert(ls.Begin(), span);return span;
}

        1-3.PageCache.h

        新建一个名为PageCache.h的文件,存放pc类的实现。

        PageCache类也使用单例模式:

#pragma once#include"Common.h"class PageCache {
public:static PageCache* GetInst() {return &_pageCacheInst;}// 获取一个npage页的spanSpan* NewSpan(size_t npage);
private:PageCache() {};PageCache(const PageCache&) = delete;static PageCache _pageCacheInst;SpanList _spanLists[NPAGE];
};

        1-4.PageCache.cpp

        新建一个名为PageCache.cpp的文件,存放单例的定义和成员函数的声明:

#include"PageCache.h"PageCache PageCache::_pageCacheInst; // 单例定义Span* PageCache::NewSpan(size_t npage) {
}

        对于NewSpan函数,它的功能是获得一个符合要求的span,与tc、cc一样,先使用缓存中的span,否则就向下一层(系统)申请:

Span* PageCache::NewSpan(size_t npage) {assert(npage<=NPAGE-1);// 优先使用pc中缓存的spanif (!_spanLists[npage].Empty()) {Span* retSpan = _spanLists[npage].PopBegin();return retSpan;}
}

        如果没有npage的span,那么就可以找更大的span,假如它有m页,就将它切成npage页的span和m-npage页的span即可:

// 找更大的span将其切分
for (size_t i = npage + 1; i < NPAGE; i++) {if (!_spanLists[i].Empty()) { // 切分成npage页的span和m-npage页的spanSpan* fitSpan = _spanLists[i].PopBegin(); // 保存m-npage页的spanSpan* retSpan = new Span();				  // 保存npage页的spanretSpan->_pageId = fitSpan->_pageId;retSpan->_n = npage;fitSpan->_pageId += npage;fitSpan->_n -= npage;_spanLists[fitSpan->_n].Insert(_spanLists[fitSpan->_n].Begin(), fitSpan);  // 另一页挂到pc对应页的缓存中return retSpan; // npage页的span返回给cc}
}

        如果不能从pc的缓存获取符合条件的span,那么就去系统获得一个128页的span:

// pc没有符合条件的span缓存了,向系统(堆)申请一个NSPAN页的大块内存
Span* newSpan = new Span();
void* start = SystemAlloc(NPAGE - 1);newSpan->_pageId = (PAGE_ID)start >> PAGE_SHIFT;
newSpan->_n = NPAGE - 1;// 把该span插入到pc缓存中,再调用一次自己即可
_spanLists[newSpan->_n].Insert(_spanLists[newSpan->_n].Begin(), newSpan);
return PageCache::GetInst()->NewSpan(npage);

        SystemAlloc用于向堆区申请一个span,将其定义在Common.h中:

// 向堆申请空间
static void* SystemAlloc(size_t npage) {
#ifdef _WIN32void* start = VirtualAlloc(0, npage << PAGE_SHIFT, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
#elif// Linux的brk mmap等
#endifif (start == nullptr)throw std::bad_alloc();return start;
}

        当然,不同操作系统申请空间的函数不同,所以再使用条件编译包含不同操作系统所需的头文件,这里只使用windows做演示:

#ifdef _WIN32
#include<windows.h>
#elif
// Linux
#endif

 

第二节:线程安全问题

        每个线程都共享一个pc,而pc中的缓存_spanLists是共享资源,那么pc也要像cc一样让每个桶独有一把锁吗 ?如果这样做效率就很低了,因为当前位置找不到span,还需要遍历之后的位置去寻找span,这样就需要频繁的加锁+解锁,消耗是很大的,所以用一把大锁将整个pc锁起来:

class PageCache {
public:static PageCache* GetInst() {return &_pageCacheInst;}// 获取一个npage页的spanSpan* NewSpan(size_t npage);void Lock() {_mtx.lock();}void UnLock(){_mtx.unlock();}
private:PageCache() {};PageCache(const PageCache&) = delete;static PageCache _pageCacheInst;SpanList _spanLists[NPAGE];std::mutex _mtx;
};

        然后在CentralCache.cpp中的GetOneSpan函数调用NewSpan的地方进行加锁和解锁:

// 先锁着pc,去pc中获取span
PageCache::GetInst()->Lock();
Span* span = PageCache::GetInst()->NewSpan(SizeClass::NumMovePage(alignSize));
span->_objSize = alignSize;
span->_isUse = true;
// 解锁pc
PageCache::GetInst()->UnLock();

 

第三节:完整代码

        这里现在只展示修改过的类和函数。

        Common.h

const static size_t NPAGE = 128 + 1; // pc的哈希桶数量#ifdef _WIN32
#include<windows.h>
#elif
// Linux
#endif// 向堆申请空间
static void* SystemAlloc(size_t npage) {
#ifdef _WIN32void* start = VirtualAlloc(0, npage << PAGE_SHIFT, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
#elif// Linux的brk mmap等
#endifif (start == nullptr)throw std::bad_alloc();return start;
}

        Common.h->SizeClass

// pc一次向系统申请的页数
static size_t NumMovePage(size_t alignSize) {size_t num = NumMoveSize(alignSize); // 一次申请上限所对应的页数size_t npage = num * alignSize;npage >>= PAGE_SHIFT;if (npage == 0) npage = 1;return npage;
}

        CentralCache.cpp

#include"PageCache.h"Span* GetOneSpan(SpanList& ls, size_t alignSize) {// 优先使用cc中已经缓存的spanSpan* cur = ls.Begin();while (cur != ls.End()) {if (cur->_freeList != nullptr) // 该span有内存块{return cur;}cur = cur->_next;}// 先解锁ls,允许其他线程释放内存,去访问pcls.UnLock();Span* span = PageCache::GetInst()->NewSpan(SizeClass::NumMovePage(alignSize));span->_objSize = alignSize;span->_isUse = true;// 将span切成小块(即大块内存->自由链表)size_t bytes = (span->_n) << PAGE_SHIFT; // 得到span的大小 _n记录页数char* start = (char*)((span->_pageId) << PAGE_SHIFT); // 得到span(未切分)的起始地址 _pageId记录页号char* end = start + bytes; // 得到span(未切分)的结尾//1.先切一块作头span->_freeList = start;start += alignSize;void* tail = span->_freeList;//2.一块一块切、尾插,直到切完(start==end)while (start < end) {NextObj(tail) = start;tail = start;start += alignSize;}NextObj(tail) = nullptr;//3.锁上cc的桶ls,将切好的span头插进ls中ls.Lock();ls.Insert(ls.Begin(), span);return span;
}

        PageCache.h

#pragma once#include"Common.h"class PageCache {
public:static PageCache* GetInst() {return &_pageCacheInst;}// 获取一个npage页的spanSpan* NewSpan(size_t npage);
private:PageCache() {};PageCache(const PageCache&) = delete;static PageCache _pageCacheInst;SpanList _spanLists[NPAGE];
};

        PageCache.cpp

#include"PageCache.h"PageCache PageCache::_pageCacheInst; // 单例定义Span* PageCache::NewSpan(size_t npage) {assert(npage > 0);// 优先使用pc中缓存的spanif (!_spanLists[npage].Empty()) {Span* retSpan = _spanLists[npage].PopBegin();return retSpan;}// 找更大的span将其切分for (size_t i = npage + 1; i < NPAGE; i++) {if (!_spanLists[i].Empty()) { // 切分成npage页的span和m-npage页的spanSpan* fitSpan = _spanLists[i].PopBegin(); // 保存m-npage页的spanSpan* retSpan = new Span();				  // 保存npage页的spanretSpan->_pageId = fitSpan->_pageId;retSpan->_n = npage;fitSpan->_pageId += npage;fitSpan->_n -= npage;_spanLists[fitSpan->_n].Insert(_spanLists[fitSpan->_n].Begin(), fitSpan);  // 另一页挂到pc对应页的缓存中return retSpan; // npage页的span返回给cc}}// pc没有符合条件的span缓存了,向系统(堆)申请一个NSPAN页的大块内存Span* newSpan = new Span();void* start = SystemAlloc(NPAGE - 1);newSpan->_pageId = (PAGE_ID)start >> PAGE_SHIFT;newSpan->_n = NPAGE - 1;// 把该span插入到pc缓存中,再调用一次自己即可_spanLists[newSpan->_n].Insert(_spanLists[newSpan->_n].Begin(), newSpan);return PageCache::GetInst()->NewSpan(npage);
}

 

第四节:下期预告

        申请内存的流程说完了,之后将进行从tc释放内存到cc,从cc释放内存到pc的过程。

 

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词