深入理解计算机系统CSAPP: Cachelab使用LRU策略的模拟缓存命中

Writing a Cache Simulator任务简介

使用LRU策略,编写csim.c,模拟cache的运行。cache为多路组相联,组数、每组行数、每行块数由调用参数给出,具体看WriteUp。材料中给出了一个名为csim-ref的可执行文件,要求我们程序最后执行的结果和它一样。

valgrind分析缓存操作

valgrind --log-fd=1 --tool=lackey -v --trace-mem=yes ls -l
该指令会跟踪地址访问情况
 L 10,1
 M 20,1
 L 22,1
 S 18,1
 L 110,1
 L 210,1
 M 12,1
 L 数据读取 M读取后写入 S数据写入

csim-ref需要达到的效果如下:

csim-ref的指令

./csim-ref -s 4 -E 1 -b 4 -t traces/yi.trace 
./csim-ref -v -s 4 -E 1 -b 4 -t traces/yi.trace 

运行效果:

./csim-ref -v -s 4 -E 1 -b 4 -t traces/yi.trace

L 10,1 miss   

M 20,1 miss hit

L 22,1 hit

S 18,1 hit

L 110,1 miss eviction

L 210,1 miss eviction

M 12,1 miss eviction hit

hits:4 misses:5 evictions:3
注意地址使用16进制表示的

提示: 使用getopt来解析指令:

./csim -v -s 4 -E 1 -b 4 -t traces/yi.trace

不用着急确定tag位的长度和地址位数,不影响操作。

b=4 ,即低4位是偏移位。s=4 组索引位是中间4位。

为了提取索引位和tag位,使用DataLab中的操作方法:

unsigned mask = 0xffffffff;
unsigned mask_set = mask >> (32-s); //只有s个1的maks 这里用到了Lab1 datalab的基础知识
unsigned index_set = (address >> b) & mask_set;
unsigned curtag = address >> (b + s);

解题思路

大的思路是,模拟一个LRU,建立一个SET的数组sets(模拟了连续的多个cache set),每个set中记录一个双向链表,该链表中每个Node表示一个cache line,链表的最大长度为E,超过E时从尾部删除节点,cache miss时从头结点插入,每次访问一个数据hit时先删除后把其加入到头结点。

数据结构构造

其数据结构如下

typedef struct _Node
{
    unsigned tag;
    struct _Node* next;
    struct _Node* pre;  //struct _Node这里必须使用这种方式,因为编译器还不认识Node
    
}Node; //每个Node表示一个行,行里面可以有多个block,但是本程序不关心block的具体操作。切不关心size超出当前block的情况

typedef struct _SET
{
    Node* head; //记录头,但是头节点不存放东西,
    Node* tail; //用于快捷访问set中的尾行,同样,本节点也不放东西
    int* size_E;  
    /*
    试试直接分配, 好像不大行必须让里面全是。  这个原因目前还没找到,
    为什么呢。不能字节设置成int,因为在函数里面传送的SET的地址,
    是一个SET的复制体,只有通过对这个复制体中的地址进行操作才能修改这个值
    */
}SET;  //每个set记录当前set的头尾节点,便于做头插和尾删操作

static SET* sets; //创建一个set的数组,因为不知道开多大,创建一个指针之后malloc分配

对应的,我们需要sets的初始化,双向链表的尾删,头插等函数。

void initializeSET(int i){
    // SET curset = sets[i]; //这样好像不大行,不能这样赋值
    //因为这样的话,set是重新创建的一个set,未对原来的set做修改
    // curset.head = malloc(sizeof(Node));
    // curset.tail = malloc(sizeof(Node));
    // curset.head->next = curset.tail;
    // curset.tail->pre = curset.head;
    // curset.size_E = malloc(sizeof(int));
    // *(curset.size_E) = 0; //初试时刻尚未被占用
    sets[i].head = malloc(sizeof(Node));
    sets[i].tail = malloc(sizeof(Node));
    sets[i].head->next = sets[i].tail;
    sets[i].tail->pre = sets[i].head;
    (sets[i].size_E) = (int* )malloc(sizeof(int));
    *(sets[i].size_E) = 0; //初试时刻尚未被占用

}
void remove_node(SET set, Node* node){
    node->next->pre = node->pre;
    node->pre->next = node->next;
    free(node);
    *(set.size_E) = *(set.size_E) - 1;
}

void add_head(SET set, int tag){
    Node* node = malloc(sizeof(Node));
    node->tag = tag;
    node->pre = set.head;
    node->next = set.head->next;
    //还未完成连接
    set.head->next->pre = node;
    set.head->next = node;
    *(set.size_E) = *(set.size_E) +1;
}

参数读入与解析getopt

为了解析输入参数,我们使用getopt函数

void phase_opt(int argc, char * const argv[], char *filename){
    int option;
    while((option = getopt(argc, argv, "s:E:b:t:")) !=-1){
        //opt会被转换成char到整数
        switch(option){
            case 's':
                s = atoi(optarg);
                break;
            case 'E':
                E = atoi(optarg);
                break;
            case 'b':
                b = atoi(optarg);
                break;
            case 't':
                strcpy(filename,optarg);
        }
    }
    Total_set = 1<<s; //总组数
}

分行解析cache_simulation

对每一行数据,我们要读取一行后进行解析

void cache_simulation(char* filename){
    //初始化sets
    sets = (SET*)malloc(Total_set*sizeof(SET)); //内容在堆上
    //对sets内的每个元素进行初始化
    for(int i = 0; i < Total_set; i++)initializeSET(i);

    FILE* file = fopen(filename, "r");
    char op;
    unsigned address;
    int size;
    //地址默认是十六进制输入的
    while(fscanf(file, " %c %x,%d", &op, &address, &size) > 0){
        printf("\n%c %x, %d", op, address, size);
        switch (op)
        {
        case 'L':
            update(address, size);
            break;
        case 'M':
            update(address, size);
            //注意这里很巧妙地没有break,因为M需要update两次
        case 'S':
            update(address, size);
            break;
        }
    }
}

利用了switch,对M很巧妙地没有break,因为M需要update两次

模拟对一个地址的访问update

void update(unsigned address, int size){
    //需要通过一个链表来实现 模拟LRU 刚用过的就放在链表头部,需要踢除的话从链表尾部踢除
    //现在想办法获得address的tag位和set位
    unsigned mask = 0xffffffff;
    unsigned mask_set = mask >> (32-s); //只有s个1的maks 这里用到了Lab1 datalab的基础知识
    unsigned index_set = (address >> b) & mask_set;
    unsigned curtag = address >> (b + s);
    //完成了索引提取,之后需要进行模拟了
    
    SET curset = sets[index_set];
    Node* curNode = curset.head->next;  //这里引发了段错误
    //开始从头结点之后遍历
    while(curNode != curset.tail){
        if (curNode->tag == curtag)
        {
            hit++;
            printf(" hit");
            //找到节点后需要把当前节点放到最前面;可以通过先删除后增加的方法
            remove_node(curset, curNode);
            add_head(curset, curtag);
            break;
        }
        curNode = curNode->next;        
    }
    if(curNode == curset.tail){
        //未找到
        misses++;
        printf(" misses");
        if(*(curset.size_E) == E){
            evictions++;
            printf(" evictions");
            remove_node(curset, curset.tail->pre);
            add_head(curset, curtag);
        }
        else {
            add_head(curset, curtag);
        }
    }   
}

完整代码如下

#include <unistd.h>
#include <getopt.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h> 
#include "cachelab.h"
#define addrlen 8

static int s, E, b, Total_set; //静态全局变量,仅仅在当前函数中可用的全局变量
static int hit, misses, evictions;

typedef struct _Node
{
    unsigned tag;
    struct _Node* next;
    struct _Node* pre;  //struct _Node这里必须使用这种方式,因为编译器还不认识Node
    
}Node; //每个Node表示一个行,行里面可以有多个block,但是本程序不关心block的具体操作。切不关心size超出当前block的情况

typedef struct _SET
{
    Node* head; //记录头,但是头节点不存放东西,
    Node* tail; //用于快捷访问set中的尾行,同样,本节点也不放东西
    int* size_E;  
    /*
    试试直接分配, 好像不大行必须让里面全是。  这个原因目前还没找到,
    为什么呢。不能字节设置成int,因为在函数里面传送的SET的地址,
    是一个SET的复制体,只有通过对这个复制体中的地址进行操作才能修改这个值
    */
}SET;  //每个set记录当前set的头尾节点,便于做头插和尾删操作

static SET* sets; //创建一个set的数组,因为不知道开多大,创建一个指针之后malloc分配

void phase_opt(int argc, char * const argv[], char *filename);
void cache_simulation(char* filename);
void update(unsigned address, int size);
void initializeSET(int i);
void remove_node(SET set, Node* node);
void add_head(SET set, int tag);

int main(int argc, char *argv[])
{
    char *filename = malloc(100*sizeof(char));
    //存放filename,这里其实不用传**也可以
    phase_opt(argc, argv, filename);
    //printf("%d, %d, %d, %s", s, E, b, filename);
    cache_simulation(filename);
    printf("\n");
    printSummary(hit, misses, evictions);
    return 0;
}

void phase_opt(int argc, char * const argv[], char *filename){
    int option;
    while((option = getopt(argc, argv, "s:E:b:t:")) !=-1){
        //opt会被转换成char到整数
        switch(option){
            case 's':
                s = atoi(optarg);
                break;
            case 'E':
                E = atoi(optarg);
                break;
            case 'b':
                b = atoi(optarg);
                break;
            case 't':
                strcpy(filename,optarg);
        }
    }
    Total_set = 1<<s; //总组数
}

void update(unsigned address, int size){
    //需要通过一个链表来实现 模拟LRU 刚用过的就放在链表头部,需要踢除的话从链表尾部踢除
    //现在想办法获得address的tag位和set位
    unsigned mask = 0xffffffff;
    unsigned mask_set = mask >> (32-s); //只有s个1的maks 这里用到了Lab1 datalab的基础知识
    unsigned index_set = (address >> b) & mask_set;
    unsigned curtag = address >> (b + s);
    //完成了索引提取,之后需要进行模拟了
    
    SET curset = sets[index_set];
    Node* curNode = curset.head->next;  //这里引发了段错误
    //开始从头结点之后遍历
    while(curNode != curset.tail){
        if (curNode->tag == curtag)
        {
            hit++;
            printf(" hit");
            //找到节点后需要把当前节点放到最前面;可以通过先删除后增加的方法
            remove_node(curset, curNode);
            add_head(curset, curtag);
            break;
        }
        curNode = curNode->next;        
    }
    if(curNode == curset.tail){
        //未找到
        misses++;
        printf(" misses");
        if(*(curset.size_E) == E){
            evictions++;
            printf(" evictions");
            remove_node(curset, curset.tail->pre);
            add_head(curset, curtag);
        }
        else {
            add_head(curset, curtag);
        }
    }   
}

void cache_simulation(char* filename){
    //初始化sets
    sets = (SET*)malloc(Total_set*sizeof(SET)); //内容在堆上
    //对sets内的每个元素进行初始化
    for(int i = 0; i < Total_set; i++)initializeSET(i);

    FILE* file = fopen(filename, "r");
    char op;
    unsigned address;
    int size;
    //地址默认是十六进制输入的
    while(fscanf(file, " %c %x,%d", &op, &address, &size) > 0){
        printf("\n%c %x, %d", op, address, size);
        switch (op)
        {
        case 'L':
            update(address, size);
            break;
        case 'M':
            update(address, size);
            //注意这里很巧妙地没有break,因为M需要update两次
        case 'S':
            update(address, size);
            break;
        }
    }
}

void initializeSET(int i){
    // SET curset = sets[i]; //这样好像不大行,不能这样赋值
    //因为这样的话,set是重新创建的一个set,未对原来的set做修改
    // curset.head = malloc(sizeof(Node));
    // curset.tail = malloc(sizeof(Node));
    // curset.head->next = curset.tail;
    // curset.tail->pre = curset.head;
    // curset.size_E = malloc(sizeof(int));
    // *(curset.size_E) = 0; //初试时刻尚未被占用
    sets[i].head = malloc(sizeof(Node));
    sets[i].tail = malloc(sizeof(Node));
    sets[i].head->next = sets[i].tail;
    sets[i].tail->pre = sets[i].head;
    (sets[i].size_E) = (int* )malloc(sizeof(int));
    *(sets[i].size_E) = 0; //初试时刻尚未被占用

}
void remove_node(SET set, Node* node){
    node->next->pre = node->pre;
    node->pre->next = node->next;
    free(node);
    *(set.size_E) = *(set.size_E) - 1;
}

void add_head(SET set, int tag){
    Node* node = malloc(sizeof(Node));
    node->tag = tag;
    node->pre = set.head;
    node->next = set.head->next;
    //还未完成连接
    set.head->next->pre = node;
    set.head->next = node;
    *(set.size_E) = *(set.size_E) +1;
}

参考资料

官方Writup

posted @ 2022-03-17 20:18  Llon_Cheng  阅读(349)  评论(0编辑  收藏  举报