# 2019q1 Homework6 (gc) contributed by < `tony2037` > ## Complete [第 7 週測驗題 (下)](https://hackmd.io/s/Hy8aBIgYV) ### **reference counting** & **mark and sweep** - references: - [17.2| Mark and Sweep](https://www.youtube.com/watch?v=2XKifr16o7Y) - work flow - mark phase - 從 **root** 出發 (v) - if(mark(v) == 0) mark(v) $\leftarrow1$ - 設 可從 v reach $v_1 ... v_n$ - 把 $v_1 ... v_n$ 放進 **todo list** - 剩下的 unreachable 的 會在 **sweep phase** 被 free - sweep phase - 找出 $mark=0$ 的 objects, 放進 free list, 這些 objects 就是 garbage - 把 $mark=1$ 的 $mark$ 設為 1 ### 改寫 tests/string-separate.c - my_strdup ```clike= char *my_strdup(char *s){ size_t size = strlen(s); char *dup = gc_alloc(sizeof(char) * (size + 1)); dup[size] = '\0'; strcpy(dup, s); return dup; } ``` - main ```clike= + char *s = my_strdup("Hello World"); + assert(s); + gc_dump_internals(); ``` 基本上就是使用 **gc library** 可以利用 **gc_dump_internals()** 記憶體配置的變化 ## [ogc](https://github.com/tony2037/ogc) ### Analysis - **void \*gc_alloc(size_t size)** 配置記憶體, 來看看具體上做了什麼事吧 ```clike= uintptr_t ptr; if (!(ptr = (uintptr_t) malloc(size))) return NULL; ``` 就是直接呼叫 **malloc(size)** 配置記憶體 ```clike= gc_ptr_t p = (gc_ptr_t){ .start = ptr, .size = size, .marked = true, }; ``` 把相關 attributes 配置好, 包括 **ptr**: address, **size**: 就是 size, 還有後續使用 **mark and sweep** 要用到的 **marked** 而 **gc_ptr_t** structure is defined in `gc_internal.h` ```clike= struct __gc_ptr { uintptr_t start; size_t size; bool marked; }; typedef struct __gc_ptr gc_ptr_t; ``` 其中又在 `gc_internal.h` 定義了一個 **globel gc object** ```clike= struct __gc { void *stack_start; gc_list_t *ptr_map[PTR_MAP_SIZE]; size_t ptr_num; size_t limit; size_t ref_count; uintptr_t min, max; gc_list_t *globals; }; typedef struct __gc gc_t; /* global GC object */ extern gc_t __gc_object; ``` **ptr_map** 就是 **roots** 對應 [17.2| Mark and Sweep](https://www.youtube.com/watch?v=2XKifr16o7Y) 的 **todo list** **gc_list_t** 的定義如下, 看起來就是一個 **singly-linked list** ```clike= struct __gc_list { struct __gc_list *next; gc_ptr_t data; }; typedef struct __gc_list gc_list_t; ``` 那麼我就好奇了, 如何決定一個 root 到底要擺在 **ptr_map**哪裡勒? 回去看到 **gc_alloc(size)** ```clike= gc_list_add(&__gc_object.ptr_map[HASH(ptr) % PTR_MAP_SIZE], p); ``` 看來就是 **HASH** 之後 take remainder(餘數), 而這邊的 **HASH** 也就是向右位移 3 bits 最後, 在 **gc_alloc** 結尾 ```clike= if (__gc_object.ptr_num >= __gc_object.limit) gc_run(); ``` 如果超過 pointers number 的上限就會啟動 **gc_run()** 也就是 gargage collection 機制 - **void gc_free(void \*ptr)** ```clike= void gc_free(void *ptr) { gc_list_t *lst = __gc_object.ptr_map[HASH(ptr) % PTR_MAP_SIZE]; if (lst && gc_list_exist(lst, (uintptr_t) lst)) { gc_list_del(&lst, (uintptr_t) lst); gc_mfree(lst); } } ``` 看懂 **gc_alloc(size)**, **gc_free(ptr)** 就簡單多了 基本就是靠著 **HASH, 取餘數** 找到所屬的 linked-list, 然後 free 了 整個 list (?) :::warning 這邊我不是很確定 ::: **Mark and Sweep** - **void gc_mark(uint8_t \*start, uint8_t \*end)** ```clike= void gc_mark(uint8_t *start, uint8_t *end) { if (start > end) swap_ptr(&start, &end); while (start < end) { gc_list_t *idx = gc_ptr_index((uintptr_t)(*((void **) start))); if (idx && idx->data.marked != true) { idx->data.marked = true; gc_mark((uint8_t *) (idx->data.start), (uint8_t *) (idx->data.start + idx->data.size)); } start++; } } ``` (swap(&start, &end) 基本就是確保 end address > start address, 不然就交換) 要講這個 function 就得先看到 **gc_ptr_index** ```clike= gc_list_t *gc_ptr_index(uintptr_t ptr) { if (ptr > __gc_object.max || ptr < __gc_object.min) return NULL; int h = HASH(ptr) % PTR_MAP_SIZE; gc_list_t *e; if ((e = list_search(ptr, __gc_object.ptr_map[h]))) return e; for (int i = 0; ++i + h < PTR_MAP_SIZE || (h > i);) { if ((h > i) && (e = list_search(ptr, __gc_object.ptr_map[h - i]))) return e; if ((h + i < PTR_MAP_SIZE) && (e = list_search(ptr, __gc_object.ptr_map[h + i]))) return e; } return NULL; } ``` **gc_ptr_index** 就是 **given a pointer, return the header within singly-linked list that the pointer is between header's start and header's end(start + size)** 所以總結來說 **gc_mark** 就是 給定 起始位址 (start), 終止位址(end), 我要把這段範圍的 reachable elements 全部 mark 起來, 怎麼找呢, 每個位置 (start++) 透過 **gc_ptr_index** 找到對應的 **singly-linked list** elements, mark 為 reachable, 並且遞迴呼叫 **gc_mark()**, start address 為 start, end address 為 start + size. - **void gc_sweep(void)** ```clike= void gc_sweep(void) { for (int i = 0; ++i < PTR_MAP_SIZE;) { gc_list_t *e = __gc_object.ptr_map[i]; int k = 0; while (e) { if (!e->data.marked) { gc_mfree(e); e = e->next; gc_list_del(&__gc_object.ptr_map[i], k); } else { e->data.marked = false; e = e->next; } k++; } } } ``` 所以就是 traverse **ptr_map** 的每個 **singly-linked list** 然後沒有被 **marked** 的就會被 **free** :::warning 說實在的, 我真心不懂 **gc_mfree(e)** 這個 function 真的不會造成 memory leak 嗎? ::: :::success 應該不會, 因為 start address = header address ::: ### Multithreading 總之先寫了一個 multithread allocate memory 版本 - `tests/multi.c` ```clike= #include <stdio.h> #include <stdlib.h> #include <assert.h> #include <pthread.h> #include "gc.h" void *sample(void *h) { void **ptr = gc_alloc(10); *ptr = gc_alloc(11); *ptr += 3; gc_dump_internals(); return NULL; } void *hello(void *ptr) { printf("Hello world"); return ptr; } int main(int argc, char *argv[]) { pthread_t t1, t2, t3; gc_init(&argc, 1); int ret = 0; if((ret = pthread_create(&t1, NULL, *hello, NULL))) { fprintf(stderr,"Error - pthread_create() return code: %d\n",ret); exit(EXIT_FAILURE); } if((ret = pthread_create(&t2, NULL, *sample, NULL))) { fprintf(stderr,"Error - pthread_create() return code: %d\n",ret); exit(EXIT_FAILURE); } if ((ret = pthread_join(t2, NULL))) { fprintf(stderr,"Error - pthread_join() return code: %d\n",ret); exit(EXIT_FAILURE); } if((ret = pthread_create(&t3, NULL, *hello, NULL))) { fprintf(stderr,"Error - pthread_create() return code: %d\n",ret); exit(EXIT_FAILURE); } if ((ret = pthread_join(t1, NULL))) { fprintf(stderr,"Error - pthread_join() return code: %d\n",ret); exit(EXIT_FAILURE); } if ((ret = pthread_join(t2, NULL))) { fprintf(stderr,"Error - pthread_join() return code: %d\n",ret); exit(EXIT_FAILURE); } if ((ret = pthread_join(t3, NULL))) { fprintf(stderr,"Error - pthread_join() return code: %d\n",ret); exit(EXIT_FAILURE); } gc_destroy(); return 0; } ``` 透過 **gdb** 發現都是在 **gc_mark** 的時候被中斷(SIGSEGV) ``` 28 void gc_mark(uint8_t *start, uint8_t *end) │ │29 { │ │30 if (start > end) │ │31 swap_ptr(&start, &end); │ │32 while (start < end) { │ >│33 gc_list_t *idx = gc_ptr_index((uintptr_t)(*((void **) start))); │ │34 if (idx && idx->data.marked != true) { │ │35 idx->data.marked = true; │ │36 gc_mark((uint8_t *) (idx->data.start), │ │37 (uint8_t *) (idx->data.start + idx->data.size)); │ │38 } │ │39 start++; │ │40 } │ │41 } ``` 看來需要對這邊做點處理, 直覺想到 [pthread_mutex_lock](https://linux.die.net/man/3/pthread_mutex_lock) :::info 失敗 ::: ### 使用 **valgrind** 檢測 ``` ==6141== Thread 3: ==6141== Conditional jump or move depends on uninitialised value(s) ==6141== at 0x401268: gc_ptr_index (utils.c:15) ==6141== by 0x400F0D: gc_mark (core.c:46) ==6141== by 0x401393: gc_mark_stack (utils.c:36) ==6141== by 0x401027: gc_run (core.c:77) ==6141== by 0x400D2D: gc_alloc (alloc.c:22) ==6141== by 0x4009AB: sample (multi.c:9) ==6141== by 0x4E416B9: start_thread (pthread_create.c:333) ==6141== ==6141== Conditional jump or move depends on uninitialised value(s) ==6141== at 0x401275: gc_ptr_index (utils.c:15) ==6141== by 0x400F0D: gc_mark (core.c:46) ==6141== by 0x401393: gc_mark_stack (utils.c:36) ==6141== by 0x401027: gc_run (core.c:77) ==6141== by 0x400D2D: gc_alloc (alloc.c:22) ==6141== by 0x4009AB: sample (multi.c:9) ==6141== by 0x4E416B9: start_thread (pthread_create.c:333) ==6141== ==6141== ==6141== Process terminating with default action of signal 11 (SIGSEGV) ==6141== Bad permissions for mapped region at address 0x6823000 ==6141== at 0x400F03: gc_mark (core.c:46) ==6141== by 0x401393: gc_mark_stack (utils.c:36) ==6141== by 0x401027: gc_run (core.c:77) ==6141== by 0x400D2D: gc_alloc (alloc.c:22) ==6141== by 0x4009AB: sample (multi.c:9) ==6141== by 0x4E416B9: start_thread (pthread_create.c:333) ``` > > 看起來是存取到 invalid memory... 但不知道從何下手 > ```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)); } } ``` ```clike= void gc_mark(uint8_t *start, uint8_t *end) { if (start > end) swap_ptr(&start, &end); while (start < end) { gc_list_t *idx = gc_ptr_index((uintptr_t)(*((void **) start))); if (idx && idx->data.marked != true) { idx->data.marked = true; gc_mark((uint8_t *) (idx->data.start), (uint8_t *) (idx->data.start + idx->data.size)); } start++; } } ``` 應該是 start -> end 的過程中存取到其他 threads's memory :::warning 我不確定, **gc_mark_stack** 中: ```clike= uint8_t tmp; &tmp ``` 的技巧應該是拿到 **stack** end 的 address. ::: # 需要一點靈感 :::warning 我需要一點提示 不知道有沒有什麼資源可以參考 ::: # 嘗試在 thread 建立時先把 stack pointer 存起來, 然後在 `gc_mark_stack` 時不再從 global stack pointer 開始 mark 改從每個 thread 的 sp 開始 :::danger 結果就是不知道為什麼有時候是可以運作的, 有時候造樣觸發 segmentation fault ``` parallels@parallels-vm:~/Desktop/ogc$ ./tests/multi Segmentation fault (core dumped) parallels@parallels-vm:~/Desktop/ogc$ ./tests/multi Segmentation fault (core dumped) parallels@parallels-vm:~/Desktop/ogc$ ./tests/multi Segmentation fault (core dumped) parallels@parallels-vm:~/Desktop/ogc$ ./tests/multi Hello world## GC internals ## - GC.ptr_map[34]: { ptr: 0x7fbd1c000910, size: 11, marked: 0 } ## GC internals ## - GC.ptr_map[34]: { ptr: 0x7fbd1c000910, size: 11, marked: 0 } ``` ::: - [ ] `gc_internal.h` ```clike= struct __gc { ... void *multi_stack_start[100]; bool multithreading; }; ``` * `multi_stack_start`: 用來存 threads 的 sp * `multithreading`: 表示 gc_object 是不是在 multithreading 的假設下運行, 對應 `gc_mark_stack` 的 `start` 如何選擇 - [ ] `src/core.c` ```clike= void gc_run(void) { (__gc_object.multithreading) ? gc_mark_stack(__gc_object.multi_stack_start[pthread_self() % 100]) : gc_mark_stack(__gc_object.stack_start); gc_sweep(); } ``` 如果是 `mulithreading` 的話, `start` 會根據 `thread_id` 從 array 裡面找 - [ ] `src/util.c` ```clike= void gc_run(void) { (__gc_object.multithreading) ? gc_mark_stack(__gc_object.multi_stack_start[pthread_self() % 100]) : gc_mark_stack(__gc_object.stack_start); gc_sweep(); } ``` :::info 這邊找到幾個可能有用的 function `pthread_attr_getstack` `pthread_self` 現在的目標是在 只在該 thread 上的 stack 做 gc, 所以能確保不要存取到其他 thread 的 address 應該就沒問題了 ::: :::warning 先標註 GC 程式中的 critical section,並且依據 CS:APP 第 12 章的手法去視覺化展現 :notes: jserv ::: :::info TODO --- * TLS (Thread Local Storage) :::