这次学15-445,就是填之前不好好学习的坑,都是泪。把lab的思路记录下来方便以后查阅,也是跟大家交流思想。言归正传,直接进入主题。
这次题目要求实现一个Buffer Pool,作用是为了减少磁盘访问次数。其中,Task 1
完成Clock Replacer
替换算法,Task 2
利用1来完成buffer pool
。
先说说Clock Replacer算法。思想很简单,就像钟表的指针一样一圈一圈遍历,直到找到符合替换的位置。文字描述过于复杂,直接利用图说明。
先做出如下假设,buffer的大小是3,访问的序列是1,2,3,1,4,3。
最初的三个元素1,2,3会直接加入到buffer中,如下表所示
num | ref_bit | clock_hand |
---|---|---|
1 | 1 | <- |
2 | 1 | |
3 | 1 |
当访问到第二个1时,因为1在buffer中,直接读取。下面访问4,这时发现buffer中并没有4,那么就需要找一个位置替换并存入4。首先会将1处的ref_bit
置0,并且指针下移一位,如下表。
num | ref_bit | clock_hand |
---|---|---|
1 | 0 | |
2 | 1 | <- |
3 | 1 |
现在,clock_hand
指向的2,而ref_bit
仍然为1,则这一位也不能替换,那么就把对应的ref_bit
置0,指针继续下移,如下表。
num | ref_bit | clock_hand |
---|---|---|
1 | 0 | |
2 | 0 | |
3 | 1 | <- |
同理,指针指向3遇到的情况与上次相同,因此继续执行相同操作。但是clock_hand
到达边界,需返回到初始位置。便得到如下状态
num | ref_bit | clock_hand |
---|---|---|
1 | 0 | <- |
2 | 0 | |
3 | 0 |
此时clock_hand
指向的ref_bit
为0,该位置能够被替换,替换1加入4,并把ref_bit
置1,遍得到如下状态
num | ref_bit | clock_hand |
---|---|---|
4 | 1 | <- |
2 | 0 | |
3 | 0 |
最后访问元素3,发现buffer中缓存有3,直接输出即可。
题目要求完成四个函数,分别是Victim()
,Pin()
,Unpin()
,Size()
。这四个函数各有分工:
Victim()
:用来负责替换buffer pool中的元素。具体做法如下,从当前指针开始计算,如果需要读取的值在replacer
中且指针指向的ref_bit
为True
,就把它置为False
,否则,就替换当前frame
;Size()
:返回当前replacer
中能被替换frame
的数量。Pin()
和Unpin()
这两个函数单独拿出来仔细阐述一下,因为实在是困扰了我很久(真是技不如人,看得我云里雾里)。
实质上,执行Pin()
和Unpin()
的都是buffer pool
,如果buffer pool
把page
给pin了,说明这个page有进程访问,不能将它写回磁盘。而Unpin()
则是有进程结束访问page
的标志。执行Unpin()
函数后,会发生以下两种情况:
page
;page
,即pin_count
不为0。对于情况1,该frame
就可以通过调用Unpin()
添加到Clock Replacer
中,并将ref_bit
置为False
。而对于情况2,存有page
的frame
就不能被替换。
Pin()
和Victim()
这两个函数非常相似,很多地方容易混淆。这里要说下Size()
的具体含义,他表示能被替换的frame
数量,Pin()
和Victim()
都能让size减少。具体来说,Pin()
就是buffer pool
将某个page
给pin
住。如果这个page
被Unpin()
后还没来及替换又Pin()
了,那这个page
就不能被替换,所以size减少了。而Victim()
就是选择一个frame
进行替换。他把能够替换的frame
替换掉了,那么可替换的frame
数量当然会由此减少。
还有一点需要注意,只有Victim()
才能更改clock_hand
指针。
Buffer Pool
缓存磁盘中的page
数据,其中每一个数据块叫做frame
,如果frame
被进程修改了,则称之为dirty page
,必须将其写回磁盘后才能替换。这里列出具体函数的实现思路以供以后查阅复习,肯定有考虑不周全的地方,请各位大佬留言指正。
这个函数是创建一个可用的page
,下面是程序要求,这里不做赘述。
// 0. Make sure you call DiskManager::AllocatePage!
// 1. If all the pages in the buffer pool are pinned, return nullptr.
// 2. Pick a victim page P from either the free list or the replacer. Always pick from the free list first.
// 3. Update P's metadata, zero out memory and add P to the page table.
// 4. Set the page ID output parameter. Return a pointer to P.
在实现的过程中,我把上述要求调换了顺序,先进行1,2然后到0。直接判断是否全pin,否则从freelist
中选择页面,如果freelist
中没有可用的再使用Victim()
找到需要替换的页。最后就是更新元数据等。
如果该page
的pin_count
在Unpin
后为0才能加入到replacer
中准备替换并返回true
。其他如该页不存在、pin_count
<=0等情况都返回false
。
该函数的参数为page_id
,即buffer pool
要使用本函数来寻找对应id的内容。那么如果在缓存区中国呢找到了,就直接读取且pin_count++
,否则替换掉某一位置。如果buffer pool
中没有能够替换的位置,则返回空指针。
剩余的函数由于没有测试用例,这里就不把它贴出来,免得今后误导自己。总之实现的方法都不难,搞清楚page
和frame
问题就能迎刃而解。
这里还有很多不足的地方,比如没有考虑多线程。
在调试过程中发现unordered_map
对一个空表查询或者删除映射,查找被删除的元素依然会返回0,这不得不让我把删除元素的 value 值设置为-1,但在其他程序中并不存在这样的问题。望各位能为我解惑。