【tcmalloc】(五)pagecache设计(申请)

发布时间:2023年12月18日

一.整体框架

用于给centralcache分配内存,也采取哈希桶结构。重点有两个区别,桶的划分规则跟上两层不一致。这一层没有采取区间划分,一页page是第一个桶两页是第二个,以此类推。第二个区别是自由链表同样悬挂的是span,但这里的span不用切分小块内存。在centralcache那一层直接计算出要申请多少页,直接定值法找到pagechace的桶节点进行申请。每一个桶节点的页表页数不相等

二.核心细节

最大的桶里的自由链表的页的单位是128,因为最大的申请内存是256kb,128页,一页8k的情况下能切出4份出来。足够使用。

pagecache有加锁问题同时不能使用桶锁。因为涉及到span的切分,会找其他的桶。有可能会其他span块有其人切分分合并操作。?

同时和centralcache一样应该成为单例。

为了有利于内存合并,我们要保证所使用k页span块之间最好是连续的。这样导致了当当前审判块不够时,不是直接去系统申请,而是去找比当前位置大的span去切分。切出来的两块span,就可以挂在对应span的位置上。同时也保证了每次去系统申请内存的时候都会申请桶结构对应的最大的内存。?

在centralcach模块通过对span块的usecount的检测,可以在一块span块的小块内存都还回来的情况下还给pagecache ,这时通过页号判断相邻位置是否空闲,合并出更大的页,解决内存碎片问题

页号和真实物理地址之间的转化,因为起始地址是0,一页8k,所以页号乘上8k就是物理地址。或者用左移

三.获取一个k页大小的页表

四种情况1.申请内存大于128页,直接去系统内存区申请2.当前桶节点有剩余的span没有被使用3.当前桶节点没有span,但是靠后的桶节点有span,切分出当前大小4.所有的桶节点都是空的,去系统申请然后切分

//获取一个k页的内存
Span* PageCache::NewSpan(size_t k)
{
	assert(k > 0);
	//若申请内存大于128页则直接向堆申请
	if (k > NPAGES - 1)
	{
		void* ptr = Memreq::SystemAlloc(k);
		//为了能让释放的时候利用上内存池要借用span结构来管理这块内存
		Span* span = _spanPool.New();
		//Span* span = new Span;
		span->_n = k;
		span->_pageid = (PAGE_ID)ptr >> PAGE_SHIFT;

		//为了能合并内存减少内存外碎片将当span添加进哈希表里
		//_idSpanMap[span->_pageid] = span;
		_idSpanMap.set(span->_pageid, span);
		return span;
	}
	 
	//先检查当前桶节点是否还有没有使用的span
	if (!_spanlists[k].Empty())
	{
		Span* kSpan = _spanlists[k].PopFront();

		// 建立id和span的映射,方便central cache回收小块内存时,查找对应的span
		for (PAGE_ID i = 0; i < kSpan->_n; ++i)
		{
			_idSpanMap.set(kSpan->_pageid + i, kSpan);
		}

		return kSpan;
	}
	 //没有桶空余的span了,可以去剩下的页数更多的span里切分出当前span的页数
	for (size_t i = k + 1; i < NPAGES; i++)//遍历后序所有桶节点看是否存在span
	{
		if (!_spanlists[i].Empty())//找到可以使用的span,切分出需要的页数。
		{
			Span* nSpan = _spanlists[i].PopFront();	//最后要返回切割后剩下的span
			Span* kSpan = _spanPool.New();//最后要返回的切分好的所要的k页span
			
			//在nspand的头部切分一个kspand下来
			//再把nspand且剩下的返还给对应的桶位置
			kSpan->_pageid = nSpan->_pageid;//因为在开头切,所以页号就是没切分之前的nspan的页号
			kSpan->_n = k;
			//更新出切分后两个span新的页号和页的数量
			nSpan->_n -= k;
			nSpan->_pageid +=k;
			//将剩下的连接回桶内
			_spanlists[nSpan->_n].PushFront(nSpan);

			// 建立id和span的映射,方便central cache回收小块内存时,查找对应的span,这里只储存了要使用的kspan每一页和span的映射关系
			//没有保存切分出来但没使用的nspan的pageid和span的映射关系
			for (PAGE_ID i = 0; i < kSpan->_n; ++i)
			{
				_idSpanMap.set(kSpan->_pageid + i, kSpan);//让当前使用的这块区域的所有页号都映射到当前span上
			}

			//因为pagecache在合并的时候只可能向前合并或者向后合并,所以只需要保存未使用内存块的起始和结束页号即可
			/*_idSpanMap[nSpan->_pageid] = nSpan;
			_idSpanMap[nSpan->_pageid + nSpan->_n - 1] = nSpan;*/
			_idSpanMap.set(nSpan->_pageid, nSpan);
			_idSpanMap.set(nSpan->_pageid + nSpan->_n - 1, nSpan);

			return kSpan;
		}
	}
	//走到这里说明整个span数组里都没有Span,需要去内存进行申请。根据不同系统采取条件编译
	//默认无系统申请一个最大的128页的span
 	Span* bigSpan = _spanPool.New();
	void* ptr = Memreq::SystemAlloc(NPAGES - 1);//调用不同的内存申请函数

	bigSpan->_n = NPAGES - 1;
	bigSpan->_pageid = (PAGE_ID)ptr >> PAGE_SHIFT;		//物理地址除以8转换成页号

	_spanlists[bigSpan->_n].PushFront(bigSpan);
						//注意因为递归调用所以不能整体加锁,不然会死锁
	return NewSpan(k);//创建好大块内存后可以复用当前逻辑继续完成申请。只会进入一次,因为下一次进入128位置的桶是有数据的,会在上面的逻辑结束掉
}

文章来源:https://blog.csdn.net/m0_62572672/article/details/135028461
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。