当前位置: 代码网 > 科技>电脑产品>内存 > 【项目日记(七)】第三层: 页缓存的具体实现(上)

【项目日记(七)】第三层: 页缓存的具体实现(上)

2024年07月31日 内存 我要评论
本篇文章着重讲解了项目内存池中第三层结构: 页缓存的具体构架,以及页缓存向中心缓存分配内存的具体实现.

在这里插入图片描述

1. 前言

在页缓存这一层中,负责给中心缓存
分配大块儿的内存,以及合并前后空
闲的内存,这一层为整体加锁!

本章重点:


2. 页缓存的具体结构

页缓存也是一个哈希桶结构,但它的映射
规则和前两层不同,它的规则是:

并且它总共是有128号桶,申请小于
128页的内存都会在内存池中申请

在这里插入图片描述

//单例模式
class pagecache
{
public:
	static pagecache* getinstance()
	{
		return &_singleton;
	}
	//获取一个k页的span
	spandata* newspan(size_t k);
	std::mutex _mtx;//pagecache不能用桶锁,只能用全局锁,因为后面可能会有大页被切割为小页

	// 获取从对象到span的映射,给我一个地址,返回这个地址对应的span
	spandata* mapobjecttospan(void* obj);

	// 释放空闲span回到pagecache,并合并相邻的span
	void releasespantopagecache(spandata* span);
private:
	pagecache(){}
	pagecache(const pagecache& obj) = delete;
private:
	std::unordered_map<page_id, spandata*> _idspanmap;//存储页号和桶中对应的span的映射,解决换回来的内存对应哪个span的问题
	spanlist _spanlist[n_pages];
	static pagecache _singleton;
};

3. 页缓存分配内存的全过程

当中心缓存中没有内存时,会去页缓存
申请一个span结构,要经过下面几步:

  1. 根据中心缓存的桶号来确定申请的span是几页的
  2. 根据中心缓存想要申请的页数,找到页缓存中对应的桶(k页对应k号桶)
  3. 情况一: 页缓存的k号桶中存在span结构,直接将这块儿内存返回给中心缓存
  4. 情况二: 页缓存的k号桶没有span结构,但是k+1到128号桶中存在span结构,假设n号桶有span,则将这个大页的span切分为一个k页的span和一个n-k页的span,k页的span返回给中心缓存去使用,而将n-k页的span重新挂在n-k号桶中
  5. 情况三: k到128号桶都没有span,此时页缓存会向系统申请一份128页大小的内存,并挂在128号桶中,再将这个128页的span切分为k页的span和128-k页的span,也就转换为了情况二!

在这里插入图片描述


4. 页缓存分配内存的代码实现

在pagecache.h文件中:

spandata* pagecache::newspan(size_t k)//去第k个桶中找span给central,此i号桶中挂的span都是i页内存
{
	//若k桶中有,直接返回,k桶没有span就往后找去分裂大span
	assert(k > 0);
	if (k > n_pages - 1)//如果申请的页数大于了128页,pagecache只能向堆申请了
	{
		void* ptr = systemalloc(k);
		spandata* span = new spandata();
		span->_pageid = (page_id)ptr >> page_shift;
		span->_n = k;
		_idspanmap[span->_pageid] = span;
		return span;
	}
	//先检查k号桶有无span,有直接返回一个
	if (!_spanlist[k].empty())
	{
		spandata* kspan = _spanlist[k].popfront();
		for (page_id i = 0; i < kspan->_n; i++)
			_idspanmap[kspan->_pageid + i] = kspan;
		return kspan;
	}
	//走到这儿代表k号桶为空,检查后面的桶有没有span,拿出来分裂成两个小span
	for (int i = k + 1; i < n_pages; i++)
	{
		if (!_spanlist[i].empty())//k页的span返回给centralcache,i-k页的span挂到i-k号桶中
		{
			spandata* ispan = _spanlist[i].popfront();
			spandata* kspan = new spandata;
			kspan->_pageid = ispan->_pageid;
			kspan->_n = k;
			ispan->_pageid += k;//把头k页切分给kspan
			ispan->_n -= k; //页数从i变为i-k
			_spanlist[ispan->_n].pushfront(ispan);//再将后i-k页分配给i-k号桶
			//存储ispan的首尾页号跟ispan的映射关系
			// 这里只需要映射首尾页而不需要像下面一样全部页都映射,因为下面切分出去的span会被切分为小块儿内存
			// 这些小块儿内存都有可能被使用,所以当它们还回来时这些小块儿内存可能映射的是不同的页,但这些页都属于这个kspan
			// 然而ispan中不会被切分为小块儿内存,它只需要关心是否和它的前后页合并,所以这里只需要映射首尾页号与ispan的关系
			// ispan作为要合并页的前面,如1000页要合并ispan是1001页,那么1001到1001+n都是空闲的!ispan作为要合并页的后面,如100页要合并ispan是999页,那么999-n都是空闲的!
			//_idspanmap[ispan->_pageid] = ispan;
			//_idspanmap[ispan->_pageid + ispan->_n - 1] = ispan;
			_idspanmap.set(ispan->_pageid, ispan);
			_idspanmap.set(ispan->_pageid + ispan->_n - 1, ispan);
			//建立id和span的映射关系,方便centralcache回收小块内存时查看哪块内存在哪块span
			for (page_id i = 0; i < kspan->_n; i++)//返回的kspan中一共有n页,并且每一页的页号都对应kspan这个地址
				_idspanmap[kspan->_pageid + i] = kspan;
			return kspan;
		}
	}
	//走到这里说明后面所有的桶都没有span了
	//这时需要向堆申请一个128页的span再拿来做切分
	spandata* bigspan = _spanpool.new();
	void* ptr = systemalloc(n_pages - 1);
	bigspan->_pageid = (page_id)ptr >> page_shift;
	bigspan->_n = n_pages - 1;
	_spanlist[bigspan->_n].pushfront(bigspan);//将这个128页的span插入到桶中
	return newspan(k);//再次调用自己,这次一定会在前面的for循环处返回
}

5. 优化代码,并完全脱离malloc

细心的同学会发现,在这个函数中使用到了new操作符,然而了解new底层原理的同学应该知道,new的底层实际上是用的malloc来申请的空间,但是我们这个项目就是为了完全脱离malloc函数来实现一个多线程下高效的内存池,所以这里一定不能使用new!

如果你不知道或忘记了定长池是什么
请看这篇文章:

首先, 在页缓存类中添加上一个成员变量: 定长池类, 然后在使用new的地方,把new全部替换为用定长池申请空间!

在这里插入图片描述
在这里插入图片描述


6. 总结以及代码拓展

页缓存分配内存的一环设计的是
非常的巧妙,但是页缓存真正巧妙
的地方是在合并空闲内存的一环!

对代码的拓展:
我们会发现页缓存结构中调用了
好几次向系统申请内存的函数,
这个地方只做了解,会用接口就行

inline static void* systemalloc(size_t kpage)//申请kpage页内存
{
#ifdef _win32
	void* ptr = virtualalloc(0, kpage << page_shift, mem_commit | mem_reserve, page_readwrite);
#else
	// linux下brk mmap等直接向系统申请内存的方式
#endif

	if (ptr == nullptr)
		throw std::bad_alloc();
	return ptr;
}

🔎 下期预告:页缓存的具体实现(下)🔍
(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com