CSAPP-cachelab(1)

  本项目大体上就是要求用C\C++来模拟cpu对cache的访问,然后统计hits、misses和eviction的次数。其实并没有想象中的那么难,感觉完全可以当成一道acm里面的大模拟题。。下面就对这个题目涉及到的一些知识点做下总结:

(一)linux命令行处理

  由于题目要求是在linux下以附加命令参数的方式来执行程序的,所以要对命令行的参数进行一下处理。这里要用到getopt函数,函数原型为:

int getopt(int argc,char** argv,const char* opstring);

我们都知道linux的命令参数分长参数和短参数,而这个函数是用来且只能用来处理短参数的,函数的返回值即为当前调用它所读取到的那个参数(int对应其ASCII码值),其中的opstring是一个短参数集合的字符串,形如:

const char* optstring = "hvs:t:b:E:";

其中每一个字母后面如果加一个冒号代表其必须带有一个附加的参数,不带就是必须没有附加的参数,而如果有两个冒号则是可带可不带参数,还有一点很重要,这些参数也就是字母的顺序是无所谓前后的,只要在这个字符串里就可以。

这个函数的调用要引用<getopt.h>库,该库还为我们提供了几个比较用帮助的全局变量,比较常见的有:optarg、opterror、optind,分别表示当前解析的参数的附加参数、解析是否出错(存在不能识别的opt即不在我们的optstring中,或者该加附带参数的opt没加不该加的加了)从而打印错误信息、下一个要解析的参数的index。我们可以根据自己的需要来利用并且手动的更改这些变量。

比如opterror=1的如果出错程序就会打印错误信息而当其等于0的时候程序就不会打印错误信息。我们通过optarg来获取每次解析参数所得到的附加参数,以字符串的形式返回!

关于命令行就总结这些,还有长参数的解析函数getopt_long(),详情可以去查看linux man page: http://linux.die.net/man/3/getopt_long

实现代码:

    char* trace_file;
    const char* optstring = "hvs:E:b:t";
    char opt;
    //deal with the short-option
    while((opt=getopt(argc,argv,optstring))!=-1) {
        switch (opt) {
            case ‘h‘:
                printusage(argv);
                break;
            case ‘v‘:
                flag = 1;
                break;
            case ‘s‘:
                state.s = atoi(optarg);
                break;
            case ‘E‘:
                state.E = atoi(optarg);
                break;
            case ‘b‘:
                state.b = atoi(optarg);
                break;
            case ‘t‘:
                trace_file = optarg;
                break;
            default :
                printusage(argv);
                break;
        }
    }    


(二)Cache的初始化

  这部分其实就是通过编程语言的形式来“模拟”出cache的简单模型,即S=2^s个分组(set),每一组有E行,每行有B=2^b个存储单元,还有tag标记位和valid有效位,具体的图片如下图:

(来源:CSAPP.2E P305)

第一看这张图的时候一直看不懂,后来明白了上面得灰色部分是cache,而最下面哪一行长条是内存的一个地址,这个原理就是我们对内存的地址按照给定的参数s,E,b(其实还有个m,不过这题默认m=64)来划分成不同的块,从而借此来在cache中查找其是否存在,如果存在就是一个hit,直接在cache中提取从而节省访存的时间,反之就要将其载入cache然后在提取。

至于语言上的实现,就是设置几个结构体然后用malloc分配空间,或者用3维数组来实现应该也可以(不过数据量太大应该就不行了)

代码实现:

Cache init(int s,int E,int b)
{
    int i,j;
    int S = 1<<s;
    int B = 1<<b;
    Cache cache_t;
    Set set_t;
    Line line_t;
    cache_t.s = (Set*)malloc(sizeof(Set)*S);
    for(i = 0;i < S;i++) {
        set_t.l = (Line*)malloc(sizeof(Line)*E);
        cache_t.s[i] = set_t;
        for(j = 0;j < E;j++) {
            line_t.block = (int*)malloc(sizeof(int)*B);
            cache_t.s[i].l[j] = line_t;
            cache_t.s[i].l[j].flag = 0;
            cache_t.s[i].l[j].tag = 0;
            cache_t.s[i].l[j].used_time = 0;
        }
    }
    return cache_t;
}


(三)核心部分:对内存的访问

关于从trace文件中获取的数据,每条有两个要注意,一是开头的命令字母,这点cmu在一开始的提示文档里也给我们指出来了:

注意这个‘M’是相当于两次访存!

第二个就是提供的address了,其实我们每次更新cache只要将address给传入到state_fresh函数就可以,然后根据给定的b、E、s来确定miss和hit的情况:

void state_fresh(Cache* cache,State* state,int ad,int cflag)
{
    int i,j;
    Set set_t;
    Line line_t;
    int cnt = getbi(ad);
    int m = 0;
    int set = 0,tag = 0;
    //get set
    for(i = cnt-state->b;i > cnt-state->b-state->s;i--)
        set += bi[i]*(1<<(m++));
    //get tag
    m = 0;
    for(i = cnt-state->b-state->s;i > 0;i--)
        tag += bi[i]*(1<<(m++));

首先对于给定的address我们确定他如果在cache中则他必须在的set的序号和他所固有的tag值,计算出这两个值后剩下的就是在cache中的相应位置去查找即可:

首先给出hit的情况:

//search in cache
    set_t = cache->s[set];
    for(i = 0;i < state->E;i++) {
        line_t = set_t.l[i];
        if(line_t.flag==1 && line_t.tag==tag) {
            state->hit++;
            cache->s[set].l[i].used_time++;
            if(cflag) printf("hit\n");
            return;
        }
    }

如果set匹配,行匹配(flag=1且tag匹配),则标记位hit做好相应的处理工作,这题的关键点就是miss的情况,因为我们要分别讨论其是否要进行evict以及如何进行evict

还是先给出有空位的情况:

//miss-could be flag = 0 or tag not equal
    state->miss++;
    //get the max used number
    int* used_num = (int*)malloc(2*sizeof(int));
    int Min = getnum(&set_t,state,used_num);
    //1.search for empty seats
    for(i = 0;i < state->E;i++) {
        line_t = set_t.l[i];
        if(line_t.flag == 0) {
            cache->s[set].l[i].flag = 1;
            cache->s[set].l[i].tag = tag;
            cache->s[set].l[i].used_time = used_num[1]+1;
            if(cflag) printf("miss\n");
            return;
        }
    }

getnum函数就是找到当前ad所必须在的set的所有行的最小值和最大值,分别存储在used_num[0]和used_num[1]中,最小值很好理解就是为了稍后的找不到空行时替换用的,而最大值则是为了将miss的ad载入cache时标记其优先级用的。

LRU(Least-Recently used)算法就是在evict时主要用到的,这里只是给出了他的低级实现,简单的记录一下优先级,更全的大家可以参考:http://flychao88.iteye.com/blog/1977653 里面从低级到高级的LRU都有讲解,还有java的实现

        //2.evict
    if(cflag) {
        if(cache->s[set].l[Min].flag==1)
            printf("miss ");
        else
            printf("miss\n");
    }
    if(cache->s[set].l[Min].flag==1) {
        state->evict++;
        if(cflag)
            printf("eviction\n");
    }
    cache->s[set].l[Min].flag = 1;
    cache->s[set].l[Min].tag = tag;
    cache->s[set].l[i].used_time = used_num[1]+1;
    free(used_num);    
时间: 2024-08-10 23:29:36

CSAPP-cachelab(1)的相关文章

链接器(linker)的作用——CSAPP第7章读书笔记

首先说说我为什么要去读这一章.这个学期开OS的课,在Morden Operating System上读到和Process有关的内容时看到这样一句话:“Process is fundamentally a container that holds all the information needed to run a program.”当时瞬间就想到了之前在csapp上看的模棱两可的“目标可执行文件”这个概念,于是重新又把它的第7章给读了一遍. 要理解linker的作用,首先要搞明白他在整个计算机

CSAPP(4):存储器层次结构

存储器系统(memory system)是一个具有不同容量.成本和访问时间的存储设备的层次结构. (一)存储设备的种类 (二)访问主存 读写操作由CPU上的总线接口电路发起. 根据上图中的数据流,对于读操作: 1.CPU将地址A放到系统总线上,然后I/O桥将信号传递给存储器总线: 2.主存感觉到存储器总线上的地址信号,从存储器总线读地址,从DRAM取出数据字,并将数据写到存储器总线.I/O桥将信号翻译为系统总线信号传递. 3.CPU感觉到系统总线上的数据,从总线上读数据. 对于写操作: 1.CP

CSAPP(3):处理器如何执行指令

前一章叙述了c语言如何转化为汇编程序,如何使用汇编程序.但是,汇编程序具体是如何执行的呢?例如(add %eax %edx)这条指令,我们知道它的功能,处理器是何如执行指令来获得想要的结果?--这是本章的主题. (一)Y86指令集体系结构 为了简化问题,我们不使用Intel和ATT的指令集体系结构,抽象简化一个Y86.Y86的定义了各种状态元素.指令集及其编码.编程规范.异常事件处理. (二)存储器和时钟 存储设备都是由同一个时钟控制的.时钟是一个周期性的信号,决定什么时候把新值加载到设备中.

CSAPP缓冲区溢出实验记录(三)

Level 5 Nitroglycerin (10 分) 题目说明:这一关是一道加分题.在bufbomb程序中还有一个'-n'的选项,使用这个选项时,bufbomb会运行Nitro模式,此时程序不会调用getbuf,而是调用getbufn: int getbufn() {     char buf[512];     Gets(buf);     return 1; } 这个函数与getbuf所不同的是,分配了512字节的字符数组,而调用getbufn的函数会在栈中随机分配一段存储区,这导致ge

(转)C++学习建议

原文:http://www.cnblogs.com/xilentz/archive/2010/05/01/1725460.html 博主传达了大量的去其糟粕的思想,所以,我只取了他对如何学习C++的建议,我还标记那些我觉得重要的话,方便以后提醒自己. C++是一门强大的语言,我们没有任何理由不学习他,领略其中的风采. 建议1:有辨别力地阅读(包括那些被广泛称为"经典"的)C++书籍. 如果书中介绍的某块内容你认为在日常编程中基本不会用到(属于20%场景),那么也许最好的做法是非常大概的

第八章 用户界面(三)

在 F# 中使用 Visual Studio 窗体设计器 到现在,F# 还没有自己的窗体设计器:然而,由于 .NET 具有很强的互操作性,因此,很容易在 F# 中使用由 Visual Studio 设计器创建的窗体.有两种选择:第一,创建一个 F# 库,然后,在自己的 Windows 窗体中调用这个库中的函数:第二:创建一个窗体库,然后,在 F# 应用程序中使用.下面分别讨论这两种方法,并对比它们的优势与不足:示例都是计算斐波那契数列(Fibonacci),如图 8-4. 警告 本书是有关 F#

程序员必读书单(转)

作者:Lucida 微博:@peng_gong 豆瓣:@figure9 原文链接:http://lucida.me/blog/developer-reading-list/ 关于 本文把程序员所需掌握的关键知识总结为三大类19个关键概念,然后给出了掌握每个关键概念所需的入门书籍,必读书籍,以及延伸阅读.旨在成为最好最全面的程序员必读书单. 前言 Reading makes a full man; conference a ready man; and writing an exact man.

推荐我看过的几本好书给大家(1)

推荐几本我看过的好书给大家! 首先必须对亚马逊偷图向亚马逊表示歉意 :) Bryant R E, 布赖恩特, O'Hallaron D, et al. 深入理解计算机系统[M]. 中国电力出版社, 2004. 这本书讲的相当的好,CMU两位老师写的,建议那些学习编程感到遇到瓶颈的同学来看一看这本书,另外翻译的也很不错!里面对很多你必须掌握的编译技术讲的很透彻.另外对计算机流水线部分讲的也非常的透彻.看完这本书你写代码时,将能够学会刻意去优化一些细节部分,而往往这些细节部分是决定问题的关键.另外这

【经典数据结构】B树与B+树(转)

本文转载自:http://www.cnblogs.com/yangecnu/p/Introduce-B-Tree-and-B-Plus-Tree.html 维基百科对B树的定义为"在计算机科学中,B树(B-tree)是一种树状数据结构,它能够存储数据.对其进行排序并允许以O(log n)的时间复杂度运行进行查找.顺序读取.插入和删除的数据结构.B树,概括来说是一个节点可以拥有多于2个子节点的二叉查找树.与自平衡二叉查找树不同,B-树为系统最优化大块数据的读和写操作.B-tree算法减少定位记录时