用mark&sweep回收算法实现个C保守垃圾收集器

来源:互联网 发布:房子短租软件 编辑:程序博客网 时间:2024/06/02 02:40

注意:本文重点在实现(x86 gcc unix like环境下),不在算法(否则也不用最简单的回收算法),并且纯属教学 (娱乐) 作用,不讲究效率,但求能用即可,也没有继续做到支持多线程。

具体代码放在github上:[https://github.com/Yuandong-Chen/singlethreadGC]

目录:

  • 实现中的重难点介绍
    • 对外接口部分示意代码
    • 如何找到marksweep的根节点
    • 实现用到的一些设计与具体方法
    • mark和sweep函数的实现
    • 用mark和sweep函数实现自己的__gc_calloc函数
    • 测试

实现中的重难点介绍:

对外接口部分示意代码:

#include "interface3.h"#include <...> // 必要头文件int main(){    __gc_init(); /* to init gc */    /*add some code you like*/    ...    /* need some memory in heap */    void *forgetToFree = (void *)gc_calloc(sizeof(struct XXX));    /*add some code you like*/    ...    __gc_exit(); /* to destroy gc */    return 0;}

如何找到mark&sweep的根节点:

i) 对于自动变量的标记,从当前栈顶一直向上找到environ指正位置即可,当然也可以通过在程序中读入/proc/pid/maps 查看栈底地址。 然后,只要把这些区域标记即可。

ii) 对于全局变量的标记,利用链接器变量 _etext, _end标记即可。( 注意 gcc工具是支持的,但是其他编译链接工具完全不能保证 )

iii) 对于临时变量,标记当前寄存器。

具体代码片段:

marksweep.c 文件中:

static unsigned int stack_bottom = 0;extern char _end[];extern char _etext[];extern char** environ;void gc_collect(){    unsigned int stack_top;    unsigned int _eax_,_ebx_,_ecx_,_edx_,_esi_,_edi_;    asm volatile ("movl    %%ebp, %0" : "=r" (stack_top));    asm volatile ("movl    %%eax, %0" : "=r" (_eax_));    asm volatile ("movl    %%ebx, %0" : "=r" (_ebx_));    asm volatile ("movl    %%ecx, %0" : "=r" (_ecx_));    asm volatile ("movl    %%edx, %0" : "=r" (_edx_));    asm volatile ("movl    %%esi, %0" : "=r" (_esi_));    asm volatile ("movl    %%edi, %0" : "=r" (_edi_));/* 我们标记一些寄存器里头的临时变量 */    mark(_eax_);    mark(_ebx_);    mark(_ecx_);    mark(_edx_);    mark(_esi_);    mark(_edi_);    mark_from_region((ptr *)((char *)stack_top + 4),(ptr *)(stack_bottom)); //我们标记自动变量    mark_from_region((ptr *)((char *)_etext + 6),(ptr *)(_end)); //我们标记全局变量    sweep();}int __gc_init(){    unsigned int stack_top;    unsigned int curr;    asm volatile ("movl %%ebp, %0" : "=r" (stack_top));    curr = stack_top;    /* 用简单的循环检查出栈底地址(有逻辑漏洞) */    while(*(unsigned int *)curr != (unsigned int)environ)    {        curr++;    }    stack_bottom = curr;    return __rb_init();}

实现用到的一些设计与具体方法:

在代码中会有一些令人费解的宏定义,如:

unordered_list.h 文件中:

...#define CHUNKSIZE  (1<<12) //分配器可用空间...#define NEXT_BLKP(bp)   ((char *)(bp) + GET_SIZE(((char *)(bp) - WSIZE -DSIZE))) //下一个块的地址...

这些其实是记录了分配区域的信息,并且把信息记录在了分配的内存块上,设计方法完全参考《深入理解计算机系统》9.10 垃圾收集一节。我们的代码结构也是完全参考书中来实现。

书中要求设计内存分配器,对于已经分配的内存块,用了平衡树(红黑树)结构存储来改进,加快一点效率(书中提到),对于空闲块,用了分离适配,空闲链表的简单首次适配搜索。当然我们加以改进,用了多个空闲链表,以空闲块大小区分这些空闲链表,加快一点效率。

具体代码片段:

unordered_list.c 文件中:

/* 首次适配算法 */static char* openlist[14];static void *find_fit(size_t asize){    void *bp = NULL;    int index = sizetoindex(asize);    int end = 14;    for (; index < end; index++) {        bp = openlist[index];        while((bp != NULL) && (GET_SIZE(HDRP(bp)) < asize))        {            bp = (void *)GET_LIST_SUCC(bp);        }        if(bp != NULL)        {            return bp;        }    }    return NULL; }/* 根据块大小找到对应的空闲链表头指针 */int sizetoindex(size_t size){    int i = 0;    size_t csize = 1;    while(csize < size)    {        i++;        if(i >= 13){            return 13;        }        csize <<=1;    }    return i;}

interface2.h 文件中:
提供了一下接口来操作红黑树里头的分配块。

void *__rb_calloc(size_t size);void __rb_free(void *bp);int __rb_init();void __rb_exit();RBTree __rb_find_node(Type key);

(具体的红黑树实现完全用 skywang 的代码,经过了少量修改以适应分配器)

mark和sweep函数的实现:

有了以上的接口,我们很容易就能实现具体的函数(参考《深入理解计算机系统》图 9-51):

static void mark(ptr p){    if(!p)       return;    ptr b;    int i,len;    if((b =(unsigned int)__rb_find_node(p)) == 0)        return;    if(b == (unsigned int)close_rbtree_root) {        fprintf(stderr, "Warning: sth points to stack root\n");        return;    }    if(GET_MARK(b))        return;    SET_MARK(b, MARK);    len = GET_SIZE(b) - 2*DSIZE - WSIZE;    for (i = 0; i < len; i += 4)    {        mark(*((unsigned int *)(unsigned int)(b+4+i)));    }    return;}static void sweep(){    Ln *curr = NULL;    Rn *root = (Rn *)__malloc(sizeof(Rn));    root->node = NULL;    postorder_sweep(close_rbtree_root->node, root);    curr = root->node;    while(curr)    {           __rb_free((RBTree)(curr->data));        curr = curr->next;    }    link_destroy(root->node);    int i = 0;    __free(root);    root = NULL;}

用mark和sweep函数实现自己的__gc_calloc函数:

void *__gc_calloc(size_t size){    double ratio = (double)(rvolume) / MAX_VOLUME;    /* 当已用堆内存占用分配器总内存的75%,进行回收。为了测试正确性,在运行贪食蛇程序时,去掉条件语句,改为每次分配必运行gc_collect函数 */    if(ratio > 0.75){       gc_collect();      }    return __rb_calloc(size);}

测试:

测试代码:

#include <stdio.h>#include <stdlib.h>#include <time.h>#include "interface3.h"#define NUM 10000#define TEST 100#define RANGE 100int *a[NUM];size_t size[NUM];void leak(int i){    int* leak = (int *)__gc_calloc(size[i]);    return;}int main(int argc, char *argv[]){    clock_t start, finish, end;    double duration1, duration2;    int j;    int i;    for (i = 0; i < NUM; ++i)    {        size[i] = 1 + (size_t)(1.0*RANGE*rand()/(RAND_MAX+1.0));    }    __gc_init();    start = clock();    for (j = 0; j < TEST; ++j)    {        for(i = 0; i <NUM; i++){            a[i] = (int *)__gc_calloc(size[i]);              *a[i] = i;                      }    }    finish = clock();    for (j = 0; j < TEST; ++j)    {        for(i = 0; i <NUM; i++){            a[i] = (int *)malloc(size[i]);  /* malloc might return NULL since we already reserve 40M for our GC */            *a[i] = i;   /* warning: if malloc return NULL, this will cause segmentation fault! */         }        for(i = 0; i <NUM; i++){            free(a[i]);        }    }    end = clock();    duration1 = (double)(finish - start) / CLOCKS_PER_SEC;    duration2 = (double)(end - finish) / CLOCKS_PER_SEC;    __gc_exit();    printf("\t\ttime(s)\t\tfrequency(HZ)\n");    printf("\t-------------------------------------------\n");    printf("__gc_malloc\t%.4f\t\t%.2f\nmalloc\t\t%.4f\t\t%.2f\n",         duration1, 2*NUM*TEST/duration1, duration2, 2*NUM*TEST/duration2);    return 0;}

输出结果:
这里写图片描述

贪食蛇正确运行:
这里写图片描述

附贪食蛇代码(改用其他人的贪食蛇程序):

/************************************** wsad控制上下左右,q退出,空格暂停* 编译命令 gcc snake.c -lcurses* 可能需要下载ncurses库,具体方法请百度* 发现bug请在贴吧@微笑一日**************************************/#include <unistd.h>#include <signal.h>#include <stdio.h>#include <curses.h>#include <stdlib.h>#include <sys/time.h>#include "interface3.h"typedef struct snake {int x, y;struct snake *next, *prev;}SNAKE, *Snake;typedef struct food {int x, y;}FOOD, *Food;int a;int dir_x, dir_y;Snake head, tail;FOOD food;int set_ticker(int n_msec){struct itimerval timeset;long n_sec, n_usec;n_sec = n_msec / 1000;n_usec = (n_msec % 1000) * 1000L;timeset.it_interval.tv_sec = n_sec;timeset.it_interval.tv_usec = n_usec;timeset.it_value.tv_sec = n_sec;timeset.it_value.tv_usec = n_usec;return setitimer(ITIMER_REAL, &timeset, NULL);}int Game_Over(){__gc_exit();sleep(1);endwin();exit(0);}void Snake_Move(){Snake p, tmp;for(p = tail; p != head; p = p->prev) {p->x = p->prev->x;p->y = p->prev->y;}p->x += dir_x;p->y += dir_y;if(head->x > 79)head->x = 0;if(head->x < 0)head->x = 79;if(head->y > 23)head->y = 0;if(head->y < 0)head->y = 23;move(head->y, head->x);if((char)inch() == '*') { //eat selfGame_Over();}if((char)inch() == 'o') { //eat foodmove(head->y, head->x);addch('*');refresh();tmp = (Snake)__gc_calloc(sizeof(SNAKE));tmp->x = head->x + dir_x;tmp->y = head->y + dir_y;tmp->next = head;head->prev = tmp;head = tmp;do {food.x = rand() % 80;food.y = rand() % 24;move(food.y, food.x);}while(((char)inch()) == '*');move(food.y, food.x);addch('o');refresh();}move(head->y, head->x);addch('*');refresh();move(tail->y, tail->x);addch(' ');refresh();}void key_ctl(){int ch = getch();switch(ch) {case 'W':case 'w':if(dir_x == 0)break;dir_x = 0;dir_y = -1;break;case 'S':case 's':if(dir_x == 0)break;dir_x = 0;dir_y = 1;break;case 'A':case 'a':if(dir_y == 0)break;dir_y = 0;dir_x = -1;break;case 'D':case 'd':if(dir_y == 0)break;dir_y = 0;dir_x = 1;break;case ' ':set_ticker(0);do {ch = getch();}while(ch != ' ');set_ticker(500);break;case 'Q':case 'q':Game_Over();break;default:break;}}void Init(){initscr();cbreak();noecho();curs_set(0);srand(time(0));dir_x = 1;dir_y = 0;head = (Snake)__gc_calloc(sizeof(SNAKE));head->x = rand() % 80;head->y = rand() % 24;head->next = (Snake)__gc_calloc(sizeof(SNAKE));tail = head->next;tail->prev = head;tail->x = head->x - dir_x;tail->y = head->y - dir_y;do {food.x = rand() % 80;food.y = rand() % 24;move(food.y, food.x);}while((char)inch() == '*');move(head->y, head->x);addch('*');move(food.y, food.x);addch('o');refresh();}void sig_alrm(int singo){set_ticker(500);Snake_Move();}int main(){__gc_init();char ch;Init();signal(SIGALRM, sig_alrm);set_ticker(500);while(1) {key_ctl();}endwin();return 0;}
0 0
原创粉丝点击