Skip to content

Commit 206add9

Browse files
authored
Merge pull request #4 from Yufccode/dev
dev to main: finish page_cache part
2 parents 6633951 + 2e1a132 commit 206add9

File tree

14 files changed

+492
-41
lines changed

14 files changed

+492
-41
lines changed

README.md

Lines changed: 302 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@
1414
- [central\_cache的核心逻辑](#central_cache的核心逻辑)
1515
- [central\_cache里面fetch\_range\_obj的逻辑](#central_cache里面fetch_range_obj的逻辑)
1616
- [page\_cache整体框架](#page_cache整体框架)
17+
- [获取span详解](#获取span详解)
18+
- [关于 new\_span 如何加锁的文字(重要/容易出bug)](#关于-new_span-如何加锁的文字重要容易出bug)
19+
- [内存申请流程联调](#内存申请流程联调)
1720

1821
***
1922

@@ -593,4 +596,302 @@ size_t central_cache::fetch_range_obj(void*& start, void*& end, size_t batch_num
593596
594597
当然cc到这里还没有完全写完的,但是我们要继续先写pc,才能来完善这里的部分。
595598
596-
## page_cache整体框架
599+
## page_cache整体框架
600+
601+
![](./assets/4.png)
602+
603+
**申请内存:**
604+
1. 当central cache向page cache申请内存时,page cache先检查对应位置有没有span,如果没有 则向更大页寻找一个span,如果找到则分裂成两个。比如:申请的是4页page,4页page后面没 有挂span,则向后面寻找更大的span,假设在10页page位置找到一个span,则将10页page span分裂为一个4页page span和一个6页page span。
605+
2. 如果找到_spanList[128]都没有合适的span,则向系统使用mmap、brk或者是VirtualAlloc等方式 申请128页page span挂在自由链表中,再重复1中的过程。
606+
3. 需要注意的是central cache和page cache 的核心结构都是spanlist的哈希桶,但是他们是有本质 区别的,central cache中哈希桶,是按跟thread cache一样的大小对齐关系映射的,他的spanlist 中挂的span中的内存都被按映射关系切好链接成小块内存的自由链表。而page cache 中的 spanlist则是按下标桶号映射的,也就是说第i号桶中挂的span都是i页内存。
607+
608+
609+
**释放内存:**
610+
1. 如果central cache释放回一个span,则依次寻找span的前后page id的没有在使用的空闲span,看是否可以合并,如果合并继续向前寻找。这样就可以将切小的内存合并收缩成大的span,减少内存碎片。
611+
612+
**这里的映射和之前的不一样,这里一共是128个桶,第一个是一页,第二个是两页!**
613+
614+
**pc只关注cc要多少页!注意,单位是页!**
615+
616+
page_cache.hpp
617+
```cpp
618+
class page_cache {
619+
private:
620+
span_list __span_lists[PAGES_NUM];
621+
std::mutex __page_mtx;
622+
static page_cache __s_inst;
623+
page_cache() = default;
624+
page_cache(const page_cache&) = delete;
625+
626+
public:
627+
static page_cache* get_instance() { return &__s_inst; }
628+
public:
629+
// 获取一个K页的span
630+
};
631+
```
632+
633+
也是要设计成单例模式。
634+
635+
然后怎么初始化呢?
636+
637+
一开始全部设置为空,然后向OS(heap)要128页的span,然后后面要(假设要两页),那就把这个128页的的切分成126页的和2页的。然后2页的给cc,126页的就挂到126的桶上。
638+
639+
然后当cc有内存不要的时候,就还到对应的span里面去。然后pc通过页号,查看前后相邻页是否空闲,是的话就合并,和病除更大的页,解决内存碎片问题。
640+
641+
## 获取span详解
642+
643+
我们要遍历cc中的span,我们可以在common.hpp里面写一些遍历链表的组建。
644+
645+
common.hpp
646+
```cpp
647+
public:
648+
// 遍历相关
649+
span* begin() { return __head->__next; }
650+
span* end() { return __head; }
651+
```
652+
653+
central_cache.cc
654+
```cpp
655+
span* central_cache::get_non_empty_span(span_list& list, size_t size) {
656+
// 先查看当前的spanlist中是否还有非空的span
657+
span* it = list.begin();
658+
while (it != list.end()) {
659+
if (it->__free_list != nullptr) // 找到非空的了
660+
return it;
661+
it = it->__next;
662+
}
663+
//如果走到这里,说明没有空闲的span了,就要找pc了
664+
page_cache::get_instance()->new_span();
665+
return nullptr;
666+
}
667+
```
668+
669+
问题是,要多少页呢?也是有一个计算方法的,放到size_class里面去!
670+
671+
common.hpp
672+
```cpp
673+
static inline size_t num_move_page(size_t size) {
674+
size_t num = num_move_size(size);
675+
size_t npage = num * size;
676+
npage >>= PAGE_SHIFT; // 相当于 /= 8kb
677+
if (npage == 0)
678+
npage = 1;
679+
return npage;
680+
}
681+
```
682+
683+
所以。
684+
central_cache.cc
685+
```cpp
686+
span* central_cache::get_non_empty_span(span_list& list, size_t size) {
687+
// 先查看当前的spanlist中是否还有非空的span
688+
span* it = list.begin();
689+
while (it != list.end()) {
690+
if (it->__free_list != nullptr) // 找到非空的了
691+
return it;
692+
it = it->__next;
693+
}
694+
//如果走到这里,说明没有空闲的span了,就要找pc了
695+
span* cur_span = page_cache::get_instance()->new_span(size_class::num_move_page(size));
696+
// 切分的逻辑
697+
return nullptr;
698+
}
699+
```
700+
701+
下面就是切分的逻辑了。
702+
703+
怎么找到这个内存的地址呢?
704+
705+
如果页号是100。那么页的起始地址就是 `100 << PAGE_SHIFT`
706+
707+
central_cache.cc
708+
```cpp
709+
span* central_cache::get_non_empty_span(span_list& list, size_t size) {
710+
// 先查看当前的spanlist中是否还有非空的span
711+
span* it = list.begin();
712+
while (it != list.end()) {
713+
if (it->__free_list != nullptr) // 找到非空的了
714+
return it;
715+
it = it->__next;
716+
}
717+
// 如果走到这里,说明没有空闲的span了,就要找pc了
718+
span* cur_span = page_cache::get_instance()->new_span(size_class::num_move_page(size));
719+
// 切分的逻辑
720+
// 1. 计算span的大块内存的起始地址和大块内存的大小(字节数)
721+
char* addr_start = (char*)(cur_span->__page_id << PAGE_SHIFT);
722+
size_t bytes = cur_span->__n << PAGE_SHIFT; // << PAGE_SHIFT 就是乘8kb的意思
723+
char* addr_end = addr_start + bytes;
724+
// 2. 把大块内存切成自由链表链接起来
725+
cur_span->__free_list = addr_start; // 先切一块下来做头
726+
addr_start += size;
727+
void* tail = cur_span->__free_list;
728+
while(addr_start < addr_end) {
729+
free_list::__next_obj(tail) = addr_start;
730+
tail = free_list::__next_obj(tail);
731+
addr_start += size;
732+
}
733+
list.push_front(cur_span);
734+
return cur_span;
735+
}
736+
```
737+
738+
注意,这里的切分是指把大块内存切成自由链表。不是pc里面的把大页切成小页。
739+
740+
然后写完上面那个,我们既要去写 `span* page_cache::new_span(size_t k)` 了。这里就要把大页切成小页了。
741+
742+
page_cache.cc
743+
```cpp
744+
// cc向pc获取k页的span
745+
span* page_cache::new_span(size_t k) {
746+
assert(k > 0 && k < PAGES_NUM);
747+
// 先检查第k个桶是否有span
748+
if (!__span_lists[k].empty())
749+
return __span_lists->pop_front();
750+
// 第k个桶是空的->去检查后面的桶里面有无span,如果有,可以把它进行切分
751+
for (size_t i = k + 1; i < PAGES_NUM; i++) {
752+
if (!__span_lists[i].empty()) {
753+
// 可以开始切了
754+
// 假设这个页是n页的,需要的是k页的
755+
// 1. 从__span_lists中拿下来 2. 切开 3. 一个返回给cc 4. 另一个挂到 n-k 号桶里面去
756+
span* n_span = __span_lists[i].pop_front();
757+
span* k_span = new span;
758+
// 在n_span头部切除k页下来
759+
k_span->__page_id = n_span->__page_id; // <1>
760+
k_span->__n = k; // <2>
761+
n_span->__page_id += k; // <3>
762+
n_span->__n -= k; // <4>
763+
/**
764+
* 这里要好好理解一下 100 ------ 101 ------- 102 ------
765+
* 假设n_span从100开始,大小是3
766+
* 切出来之后k_span就是从100开始了,所以<1>
767+
* 切出来之后k_span就有k页了,所以 <2>
768+
* 切出来之后n_span就是从102开始了,所以 <3>
769+
* 切出来之后n_span就变成__n-k页了,所以 <4>
770+
*/
771+
// 剩下的挂到相应位置
772+
__span_lists[n_span->__n].push_front(n_span);
773+
return k_span;
774+
}
775+
}
776+
// 走到这里,说明找不到span了:找os要
777+
span* big_span = new span;
778+
big_span =
779+
}
780+
```
781+
782+
这里切分的逻辑,代码注释里面写的很清楚了!
783+
784+
然后如果找到128页的都没找到,直接向系统申请!
785+
786+
这里要区分windows和linux。
787+
788+
common.hpp
789+
```cpp
790+
inline static void* system_alloc(size_t kpage) {
791+
void* ptr = nullptr;
792+
#if defined(_WIN32) || defined(_WIN64)
793+
#include <windows.h>
794+
*ptr = VirtualAlloc(0, kpage * (1 << 12), MEM_COMMIT | MEM_RESERVE,
795+
PAGE_READWRITE);
796+
#elif defined(__aarch64__) // ...
797+
#include <sys/mman.h>
798+
void* ptr = mmap(NULL, kpage << 13, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
799+
#else
800+
#include <iostream>
801+
std::cerr << "unknown system" << std::endl;
802+
throw std::bad_alloc();
803+
#endif
804+
if (ptr == nullptr)
805+
throw std::bad_alloc();
806+
return ptr;
807+
}
808+
```
809+
810+
然后new_span最后:
811+
812+
```cpp
813+
// 走到这里,说明找不到span了:找os要
814+
span* big_span = new span;
815+
void* ptr = system_alloc(PAGES_NUM - 1);
816+
big_span->__page_id = (PAGE_ID)ptr >> PAGE_SHIFT;
817+
big_span->__n = PAGES_NUM - 1;
818+
// 挂到上面去
819+
__span_lists[PAGES_NUM - 1].push_front(big_span);
820+
return new_span(k);
821+
```
822+
823+
插入之后,不要重复写切分的逻辑了,递归调用一次自己就行了!
824+
825+
### 关于 new_span 如何加锁的文字(重要/容易出bug)
826+
827+
然后这里还有最关键的一步。这里整个方法是要加锁的!
828+
829+
这里有个关键问题需要思考。
830+
831+
get_non_empty_span是被fetch_range_obj调用的(在cc.cc)里面。
832+
833+
但是get_non_empty_span会去调用pc的new_span。
834+
现在有个关键问题了,此时,如果我们不做处理,在pc的new_span里面,其实是有cc的桶锁的。
835+
这个是很不好的。因为这个桶可能有内存需要释放啊!你锁住了,别人就进不去了。
836+
(其实这里我也是一知半解,要再去理解一下)
837+
838+
所以,在`span* central_cache::get_non_empty_span(span_list& list, size_t size) {`里面这个`span* cur_span = page_cache::get_instance()->new_span(size_class::num_move_page(size));`这句话前面。我们先把桶锁解掉。
839+
840+
然后pc的new_span的全局锁,我们在cc.cc里面的`span* central_cache::get_non_empty_span(span_list& list, size_t size) {` 这里加。
841+
842+
cc.cc
843+
```cpp
844+
// 如果走到这里,说明没有空闲的span了,就要找pc了
845+
page_cache::get_instance()->__page_mtx.lock();
846+
span* cur_span = page_cache::get_instance()->new_span(size_class::num_move_page(size));
847+
page_cache::get_instance()->__page_mtx.unlock();
848+
```
849+
850+
**现在问题来了,我们cc拿到这个新的span,后面还要切分的。刚刚在拿new_span之前解锁了,现在需要加上吗?**
851+
852+
不需要!
853+
854+
因为这个span是从pc拿来的,新的,也还没挂到cc上面去,所以别的线程拿不到这个span!所以不用加锁!
855+
856+
但是最后一步 `list.push_front(span)` 要访问cc对象了!就要加锁,我们把锁恢复一下。
857+
858+
central_cache.cc
859+
```cpp
860+
span* central_cache::get_non_empty_span(span_list& list, size_t size) {
861+
// 先查看当前的spanlist中是否还有非空的span
862+
span* it = list.begin();
863+
while (it != list.end()) {
864+
if (it->__free_list != nullptr) // 找到非空的了
865+
return it;
866+
it = it->__next;
867+
}
868+
// 这里先解开桶锁
869+
list.__bucket_mtx.unlock();
870+
871+
// 如果走到这里,说明没有空闲的span了,就要找pc了
872+
page_cache::get_instance()->__page_mtx.lock();
873+
span* cur_span = page_cache::get_instance()->new_span(size_class::num_move_page(size));
874+
page_cache::get_instance()->__page_mtx.unlock();
875+
876+
// 切分的逻辑
877+
// 1. 计算span的大块内存的起始地址和大块内存的大小(字节数)
878+
char* addr_start = (char*)(cur_span->__page_id << PAGE_SHIFT);
879+
size_t bytes = cur_span->__n << PAGE_SHIFT; // << PAGE_SHIFT 就是乘8kb的意思
880+
char* addr_end = addr_start + bytes;
881+
// 2. 把大块内存切成自由链表链接起来
882+
cur_span->__free_list = addr_start; // 先切一块下来做头
883+
addr_start += size;
884+
void* tail = cur_span->__free_list;
885+
while(addr_start < addr_end) {
886+
free_list::__next_obj(tail) = addr_start;
887+
tail = free_list::__next_obj(tail);
888+
addr_start += size;
889+
}
890+
// 恢复锁
891+
list.__bucket_mtx.lock();
892+
list.push_front(cur_span);
893+
return cur_span;
894+
}
895+
```
896+
897+
## 内存申请流程联调

assets/0.png

135 KB
Loading

assets/4.png

183 KB
Loading

central_cache/central_cache.cc

Lines changed: 0 additions & 33 deletions
This file was deleted.

central_cache/central_cache.hpp renamed to include/central_cache.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
#ifndef __YUFC_CENTRAL_CACHE_HPP__
33
#define __YUFC_CENTRAL_CACHE_HPP__
44

5-
#include "../common.hpp"
5+
#include "./common.hpp"
66

77
class central_cache {
88
private:

0 commit comments

Comments
 (0)