实验准备
这里有几份资料可以学习 cache
的工作原理:
- CMU 课程 slide
- 现代操作系统—原理与实现 page 18
- CS: APP page 424
对于实验来说,请仔细阅读 Write up
文档,如果需要,请安装下载 Valgrind
(安装方式请自行 STFW
)
我更换了我的实验环境,从 WSL2
迁移到了真实的 Linux
平台上,但对于这份实验来说并没有什么影响
Part A
要求
实现一个命令行工具 csim
,用于模拟 cache
的工作原理,其官方版本的功能如下:
其中,参数 s E b
的含义如图:
写命令行工具时,我们可以通过 #include <unistd.h>
提供的 getopt()
函数来解析命令行参数,这在前文的 slide 中有提到
Data Structure
首先,我们通过上图来设计 cache
的数据结构,显然这是一个矩阵,每一个条目视为一个 struct
,其中包括 valid bit
, tag
, block
,当然,我们还需要实现 LRU
算法,因此这里我们还需要一个时间戳来记录出现的时间。
于是,可以设计数据结构如下:
注意到这里的 block
我用了数组,实际上甚至可以不需要用这个成员,因为模拟器并不会存储任何数据,只是模拟 hit
, missing
, evict
而已。
Control Flow
我们可以设计如下控制流:
分治后,就可以开始解决各个子问题了。
parsing_cmd
实际上我们可以在 main
中实现这部分逻辑:
如果不会使用 getopt
,可以看 slide 或者 man 3 getopt
看手册学
大概的骨架并不需要太多解释,我们的重点应该放在 eval
函数中。
eval
由于我们不需要处理除 S
, L
, M
外的其他字符开头的行(注意你可能会读到 \r\n
等字符),并且对每次 S
L
M
的操作是类似的,都是 get_page()
,于是,代码是显然的:
注意,这里的 M
代表修改,所以我们需要先 L
一次,然后 S
一次,这样就需要更新(或者是 get
)两次 page
而对于 get_page
,逻辑也是显然的:
- 通过地址获取
set_index
, tag
, offset
三个值
- 查询是否为
cold miss
,即 cache[set_index] == NULL
(cache 中一条数据都没有)
- 如果是,则直接从
disk
中调入页面,在这里,我们只是把 cache[set_index]
中的一行的 tag
设置为该地址的 tag
,并将其设置为有效
- 如果不是,那么我们查看是否已经被调入到
cache
中来了
- 如果是,则已经找到,将时间戳设置为最新后,更新所有条目的时间戳,然后返回
- 否则,我们需要从
disk
中调入页面
而在调入页面时,我们需要判断此时的 cache
中是否已经满了(检查是否所有有效位均为 1 即可),如果是,那么我们必须驱除出去一页,才能进行调入。
代码如下:
注意这里的 offset
其实一点用都没有,当然为了模拟还是让他有点作用了。
我们在 evict_page
的最后,直接将 tag
修改为需要调入页面的 tag
,这样省去了再次 get_page
的痛苦。
结果
Part B
这部分想要做出来是简单的,但想要做满分是很难的,后面会详细写一份调优的博客。
最简单的方法就是使用分块,考虑到这里的 cache
是 32 字节的,一个 int
为 4 字节,所以我们可以八个八个值的来复制,于是代码应运而生:
当然,这份代码是没办法达到最好效果的,32×32
矩阵的理论 miss 值为 256,而我们的结果为 288
如果用这个代码去考虑 64×64
的矩阵,那么 miss 会达到惊人的 4612,显然是无法接受的,这里的做法可以是减少分块的大小,例如我们用 4×4
的分块,那么就可以减少 miss 的值: