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

2023-12-16 18:12:49

一.整体框架

中心cache,用于threadcache的桶节点没有自由链表时,向下寻找内存的下一层数据结构。起到均衡调度的作用。同样采取和线程缓存一样的哈希桶映射规则。这样方便能直接找到一下层的桶节点,不同点是中央内存区是所有线程共享的所以就有加锁问题。这里用的是桶锁。这样的优点是不同桶之间不会有竞争问题。不同线程申请不同大小的内存空间,会去不同的桶低下找空间。

另一个不同点,线程内存区下挂的是大当前桶节点,同样大小的小内存。中央内存区挂的是span,是以页为单位的大块内存,同时每块span都被切分成了线程内存区对应的内存小块。这样就可以直接拿给线程内存区用。中央内存区挂的是由span构成的自由链表。

不同桶节点下的span大小是不一样的。跟线程内存区桶越靠后申请的内存越大一样,中央内存区桶越靠后span越大。 可以通过一个变量来控制当前节点每个span块 的页大小。同时因为每次拿的线程小内存块是固定的,span本身大小可以确定,就能计算出一共能拿出多少块当前桶节点对应的小内存。可以用来记录当前span块的使用情况。 可以用来处理向下一层传递合并页,同时处理内存碎片问题,这是pagecache的功能。

centralcache主要是承上启下

二.核心细节

中央内存区的桶节点是可以有多个span的同时每个span剩余的小块内存数量是不确定的,因为申请和释放可能同时进行。可能会有线程释放内存回到中央内存区。

同时这里的自由链表是带头双向的,因为不确定哪个位置的span可能要被释放回下一个层次,pagecache。双向链表删除更好操作一些。

因为所有线程公用中央内存区,所以应该将中央内存区设计成单例模式。

三.截取桶节点一段自由链表

获取当前自己桶节点下的一段链表给线程内存区

size_t CentralCache::FetchRangeObj(void*& start, void*& end, size_t fetchNum, size_t size )//链表区间的头尾指针,获取个数和获取大小
{
	size_t index = SizeRules::Index(size);		
	_spanList[index].Getmtx().lock();//多线程下访问同一桶有竞争问题
	
	Span* span = GetOneSpan(_spanList[index],size);  //获取一个非空桶块,得先找到一个有自由链表的桶块
	assert(span);
	assert(span->_freeList);
							// 从span中获取fetchNum个对象
							// 如果不够fetchNum个,有多少拿多少
	start = span->_freeList;//让范围指针取到新的内存块范围
	end = start;
	size_t i = 0;
	size_t actulNum = 1;	//实际取到的数量默认就有一块
														//遍历想要的范围,同时防止span内的内存块不够
	//两种遍历方法可以创建一个新变量cur去跑直到为空并且拿到了足够的小内存块,不过这种方法不好因为我们需要断开自由链表的链接,这样找不到之前的指针
	while (i < fetchNum - 1 && NextObj(end) != nullptr)//指针少走一次这样左右指针都可取,有利于断开
	{
		end = NextObj(end);
		i++;
		actulNum++;
	}
									//将这段内存块从span里拿出来
	span->_freeList = NextObj(end);//span指向end后面剩下的内存块
	NextObj(end) = nullptr;			//end和后面的内存块断开
	span->_useCount += actulNum;

	
	_spanList[index].Getmtx().unlock();
	
	return actulNum;
}

四.找一个不是空的桶(和pagecache联动)

主要负责去下一层拿一个桶节点上来后将桶节点切分成小块内存挂在当前位置的自由链表上

关于centralcache 和pagecache联动是桶锁和整体锁切换的问题。最好把桶锁解开在加上整体锁,在申请的情况下没什么影响,就算桶锁解开不同线程访问到了同一个桶但是因为pagechache有锁所以也会阻塞住。但是释放的时候如果不解开桶锁就没有办法合并自由链表向span里还内存

//获取一个非空span
Span* CentralCache:: GetOneSpan(SpanList &list,size_t byte_size)
{
	//寻找当前桶节点内部是否还有没使用的span内存块
	Span* it = list.Begin(); 
	//遍历当前桶节点下的挂着的所有内存块
	int i = 0;
	while (it != list.End())
	{
		i++;
		if (it->_freeList != nullptr)  //若有span未被使用则直接返回
		{
			return it;
		}
		else
		{
			it = it->_next;
		}
	}

	//走到这里说明当前位置已经没用span内存了需要去申请span(去更大块的span去切)
	// 先把central cache的桶锁解掉,这样如果其他线程释放内存对象回来,不会阻塞

	list.Getmtx().unlock();
	//进入pagecache获取页块,需要进行加锁
	PageCache::GetInstance()->Getmtx().lock();
	Span* span = PageCache::GetInstance()->NewSpan(SizeRules::NumMovePage(byte_size));	//获取新的span块
	span->_isUse = true;			//该内存块即将被使用。
	span->_objSize = byte_size;
	PageCache::GetInstance()->Getmtx().unlock();

	// 对获取span进行切分,不需要加锁,因为这会其他线程访问不到这个span,拿出来还没有连接上,其他线程看不见
	//需要计算出当前申请到的span的物理地址和内存大小(通过页号计算)

	char* start = (char*)(span->_pageid << PAGE_SHIFT);  //页号转换为物理地址
	size_t bytes = span->_n << PAGE_SHIFT;	//当前span的字节数
	char* end = start + bytes;

	//将当前span切割成自由链表并悬挂进桶节点
	// 1、先切一块下来去做头节点,方便尾插(尾插好一些,能保证物理地址连续,缓存利用率高)

	span->_freeList = start;  //给自由链表初始化起始地址
	start += byte_size;		//找到自由链表下一个起始位置
	void* next = span->_freeList; //用于存放下一个自由链表的地址
	while (start < end)
	{
		NextObj(next) = start; //将下一个位置地址保存在当前位置(相当于链接指针)
		next = start;			//寻找下一个位置重复这个流程(相当于指针后移)
		start += byte_size;		//物理上是连续的,逻辑上是链表
	}

	NextObj(next) = nullptr;


	//最后将切割好的内存挂给桶节点,要加锁
	list.Getmtx().lock();
	list.PushFront(span);
	//在这一层不用解锁,在外层函数中已经包含解锁
	return span;
}

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