用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, ×et, 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;}
- 用mark&sweep回收算法实现个C保守垃圾收集器
- 使用mark-sweep算法的垃圾回收器
- CMS(Concurrent Mark-Sweep)垃圾回收器
- IBM 实现使用称为 mark-sweep-compact(MSC)的垃圾收集算法
- IBM 实现使用称为 mark-sweep-compact(MSC)的垃圾收集算法 2
- 了解CMS(Concurrent Mark-Sweep)垃圾回收器
- 了解CMS(Concurrent Mark-Sweep)垃圾回收器
- 了解CMS(Concurrent Mark-Sweep)垃圾回收器
- 了解CMS(Concurrent Mark-Sweep)垃圾回收器
- 了解CMS(Concurrent Mark-Sweep)垃圾回收器
- 了解CMS(Concurrent Mark-Sweep)垃圾回收器
- 了解CMS(Concurrent Mark-Sweep)垃圾回收器
- 了解CMS(Concurrent Mark-Sweep)垃圾回收器
- 了解CMS(Concurrent Mark-Sweep)垃圾回收器
- 了解CMS(Concurrent Mark-Sweep)垃圾回收器
- 了解CMS(Concurrent Mark-Sweep)垃圾回收器
- 了解CMS(Concurrent Mark-Sweep)垃圾回收器
- 了解CMS(Concurrent Mark-Sweep)垃圾回收器
- android.media.MediaCodec
- Java AES512加密算法
- jQuery的remove()和empty()的区别
- 用WritableSheet编辑多个sheet下载的问题
- Win 32 API中对文件的操
- 用mark&sweep回收算法实现个C保守垃圾收集器
- 千里之行始于足下
- 【0-1 knapsack】 474. Ones and Zeroes
- EM算法求解混合伯努利模型
- Lottie初级教程:打造iOS APP完美动画
- NOIP 2001-2——最大公约数和最小公倍数问题(简单推导/分解质因数)
- 信号集操作函数,信号未决、阻塞、递达
- phpcms 推荐位在首页显示错误问题
- POJ2395 Out of Hay