# 2019q1W7下: 劉正仁
## 測驗 1
### OGC: Reference counting
OGC 中 reference counting 的機制相當簡單。利用 `grep` 查看程式碼中有那些地方更改到了 garbage collector 中 `ref_count` 的值,來進一步做程式碼的閱讀。
```shell
$ grep ref_count *.c -n
core.c:3:gc_t __gc_object = (gc_t){.ref_count = 0};
core.c:7: if (__gc_object.ref_count) {
core.c:8: __gc_object.ref_count++;
core.c:14: .ref_count = 1,
core.c:70: __gc_object.ref_count--;
core.c:71: if (__gc_object.ref_count <= 0) {
core.c:72: __gc_object.ref_count = 0;
```
在其他程式碼中,開發者可能會多次呼叫 `core.c` 中的 `gc_init`,來代表使用(reference) 到 garbage collector。
```clike=
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};
}
```
在上述程式碼 3-6 行,為當已經有人使用了 garbage collector,其他人再次使用時,只會將 garbage collector 的 `ref_count++`,代表在其他程式碼中,有 `ref_count` 個地方使用到 garbage collector。
反之,當我們使用完 garbage collector 時,會呼叫 `core.c` 中 `gc_destroy`。這個函式很簡單,當 garbage collector 的 `ref_count` 減為 0 時,代表沒有人使用 garbage collector。程式碼就將由 garbage collector 控管的記憶體全部 `free` 掉。
```clike
void gc_destroy(void)
{
__gc_object.ref_count--;
if (__gc_object.ref_count <= 0) {
__gc_object.ref_count = 0;
for (int i = -1; ++i < PTR_MAP_SIZE;) {
gc_list_t *m = __gc_object.ptr_map[i];
while (m) {
gc_list_t *tmp = m;
free((void *) (m->data.start));
m = m->next;
free(tmp);
}
__gc_object.ptr_map[i] = 0;
}
}
}
```
### OGC: Mark and sweep
程式碼中執行 mark & sweep 的動作在 `core.c` 中的 `void gc_run(void)`。
```clike
void gc_run(void)
{
gc_mark_stack();
gc_sweep();
}
```
`gc_mark_stack` 做完後,所有 reachable 的 memory block 都會被 mark。因此在接下來的 `gc_sweep`,這些 memory block 就不會被 free 掉,反之,沒有被 mark 的都會被 free 掉。
這邊觀看一下 `gc_mark_stack`.
```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));
}
}
```
程式碼第 4 行,程式碼呼叫 `gc_mark`,並傳入 `__gc_object.stack_start`, `&tmp`。用 `test/dummy.c` 這個測試碼,`stack_start` 為 `&argc`。
:::danger
上述的 for 迴圈初始值會永遠保持 NULL,似乎是 BUG。我認為可以直接忽略。
```shell
$ grep stack_start *.c -n
core.c:11: __gc_object = (gc_t){.stack_start = ptr,
utils.c:36: gc_mark(__gc_object.stack_start, &tmp);
```
:::
再來看看 `gc_mark` 的行為
```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++;
}
}
```
程式碼會在傳進去的某個地址範圍,找尋 gc_list_t 的指標,若有找到則查看此 memory block 有否被 mark,若無則 mark。然後繼續遞迴的搜索此 memory block 有沒有 gc_list_t 的指標。
以下用是上述程式碼的圖解
![](https://i.imgur.com/Dt4daZg.png)
最後是 `gc_sweep` 做 garbage collection 的操作。
```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++;
}
}
}
```
程式碼還算直覺,查看所有由 garbage collector 控管的 memory block,若其沒有被 mark (`!e->data.marked`),就會被 free 掉,反之則將 marked 的 memory block 變為 unmark,留待下一輪的 garbage collection 處理。
###### tags: `sysprog2019`