# 2019q1 Homework6 (gc) contributed by < `njjack` > ### ref_count 原先看程式碼時,發現`ref_count` 只有在 `gc_init()` 和 `gc_destroy()` 之中被使用,分別做加 1 和減 1 的操作,且 `gc_init()` 和 `gc_destroy()` 的呼叫時機,只有在測試程式中的 main 函式開始和結束時,猜測一個 garbage collector(一個 `__gc_object`) 可能被多個 process 使用到,而 `ref_count` 記錄的就是這些 process 的個數。 然而考量 garbage collector 用來回收 heap 中的空間,其使用場景較可能是被多個 thread 共用,`ref_count` 則是記錄 thread 個數。同時可以發現目前程式碼中 `gc_init()` 呼叫時機,以及結構 `gc_t` 其實尚不適用 multi-thread 環境 ```clike gc_t __gc_object = (gc_t){.ref_count = 0}; void gc_init(void *ptr, size_t limit) { if (__gc_object.ref_count) { __gc_object.ref_count++; return; } __gc_object = (gc_t){.stack_start = ptr, .ptr_map = {NULL}, .ptr_num = 0, .ref_count = 1, .limit = limit, .min = UINTPTR_MAX, .max = 0, .globals = NULL}; } ``` :::warning 先標註 GC 程式中的 critical section,並且依據 CS:APP 第 12 章的手法去視覺化展現 :notes: jserv ::: ### gc_alloc( ) `gc_t` 的資料成員 `ptr_map[ ]` 用來維護所有 `gc_alloc()` 配置的記憶體空間。 `ptr_map[ ]` 是一個 hash table ,以 singly linked list chainging 處理 collision ,hash funtion 為 `HASH(ptr) % PTR_MAP_SIZE` ,其中 `ptr` 為呼叫 malloc 的回傳值。 每一次呼叫 `gc_alloc()` 配置記憶體空間時,會呼叫 `gc_list_add()` 建立一 `gc_list_t` 型別的物件 `elem` ,並根據配置的記憶體位址(即 `ptr`) 插入 `ptr_map[ ]` 中對應的 linked list 簡述流程: gc_alloc() -> malloc() -> 根據 malloc 回傳地址找到適當位置 -> gc_list_add(),將 elem 插入 linked list ```clike void *gc_alloc(size_t size) { uintptr_t ptr; if (!(ptr = (uintptr_t) malloc(size))) return NULL; gc_ptr_t p = (gc_ptr_t){ .start = ptr, .size = size, .marked = true, }; if (__gc_object.min > ptr) __gc_object.min = ptr; if (__gc_object.max < ptr + size) __gc_object.max = ptr + size; gc_list_add(&__gc_object.ptr_map[HASH(ptr) % PTR_MAP_SIZE], p); __gc_object.ptr_num++; if (__gc_object.ptr_num >= __gc_object.limit) gc_run(); return (void *) ptr; } ``` ### mark and sweap ```clike void gc_mark_stack(void) { uint8_t tmp; gc_mark(__gc_object.stack_start, &tmp); for (gc_list_t *e = __gc_object.globals; e; e = e->next) { gc_mark((uint8_t *) (e->data.start), (uint8_t *) (e->data.start + e->data.size)); } } ``` ### mutithread 在 mutithread 環境中,某一條 thread 呼叫 mark() 只會 mark 這一條 thread 的 reachable 的 object ,若接著直接呼叫 sweap() ,會把其他 thread 的 reachable object 也回收掉。 為了解決上述問題,先採取比較直覺但看起來沒效率的作法,每一次 mark 都檢查所有 thread 的 stack ,mark 每個 thread 的 reachable object 之後才 sweap。 原來的程式碼中 mark 時需要有 stack 的起始位置 (__gc_object.stack_start) 和目前 stack pointer 的位置 (gc_mark_stack 中的 tmp)。multithreading 版本中,呼叫 `pthread_attr_getstack()` 得到 stack 的起始位置 (stackaddr) 和大小 (stacksize);但是 mark 時不知如何拿到其他 thread 目前 stack pointer 的位置,暫時先用很笨的方法,用 (stackaddr + stacksize) 代替 (參考 [get a thread's stack address](https://stackoverflow.com/questions/8729384/can-i-get-a-threads-stack-address-from-pthread-self) ) ```clike void gc_init(size_t limit) { if(__gc_object.ref_count == THREAD_MAXNUM){ return; } if(__gc_object.ref_count == 0){ __gc_object.limit = limit; } pthread_attr_t attr; void * stackaddr; size_t stacksize; pthread_getattr_np(pthread_self(), &attr); pthread_attr_getstack( &attr, &stackaddr, &stacksize ); for(int i = __gc_object.ref_count; ; i = (i + 1) % THREAD_MAXNUM){ if(!__gc_object.stack_start[i]){ __gc_object.stack_start[i] = (uintptr_t)stackaddr; __gc_object.stack_size[i] = stacksize; } } __gc_object.ref_count++; } ``` ```clike void gc_mark_stack(void) { //uint8_t tmp; for(int i=0; i < THREAD_MAXNUM; i++){ if(!__gc_object.stack_start[i]){ gc_mark(__gc_object.stack_start[i], __gc_object.stack_start[i] + __gc_object.stack_size[i]); } } ... } ```