# 2021q3 Homework2 (hp)
contributed by < `new-type` >
## 簡介
* **問題描述**:
* 請參閱 [2021 年暑期 Linux 核心 第 2 週測驗題](https://hackmd.io/@sysprog/linux2021-summer-quiz2)
* [原程式](https://github.com/sysprog21/concurrent-programs/tree/master/hp_list)
## 延伸問題 1 - 解釋上述程式碼運作原理
原程式主要是利用 Harzard pointers 來避免正在被其它 threads 使用的物件被刪除。
從原程式 430 行可以看出建立 thread 時會有 3 個 Harzard Pointer
```c
list_hp_t *hp = list_hp_new(3, __list_node_delete);
```
而在 134 行可以看到 list_hp 中有
```c
struct list_hp {
int max_hps;
alignas(128) atomic_uintptr_t *hp[HP_MAX_THREADS];
alignas(128) retirelist_t *rl[HP_MAX_THREADS * CLPAD];
list_hp_deletefunc_t *deletefunc;
};
```
* max_hps:最大的 Harzard pointer 數量 (程式中設定為 3 )
* hp[HP_MAX_THREADS]:正在使用需要防止被刪除的 list
* rl[HP_MAX_THREADS]:已經可以被刪除的 list
* deletefunc:釋放記憶體的函數 (__list_node_delete)
刪除的機制是將準備刪除的物件先拷到 rl 中,處理刪除時再檢查該物件是不是仍然在 hp 中,若是則代表該物件還在使用中,先不刪除;反之則呼叫 deletefunc 刪除。
其中這裏面都是 alignas(128),應該是為了避免 false sharing 的問題。但是一個 cache line 在 x86_64 上是 64 bytes,這邊使用了 128 bytes 可能是考量到目前最大的 cache line 大小為 128 bytes。
程式在一開始會先建立兩個 node,分別用 head 和 tail 指向它們。而維護 Hazard pointer 機制的主要圍繞在三個函數:
* __find_list
首先在 __find_list 中利用了 CAS 來確保資料狀態是一致的,也就是說要確認 prev, curr 和 curr->next 是不是還是存在之前的 list 中,也就是在確認 curr 的前後 node 是不是還是沒變動,若有變化就代表要重新找。
此外在 node 之中存的都是位置,而且是以遞增的方式,所以可以利用位置的大小判斷新舊的關係。再確認 next 是否有變化。若有變化則代表可能有別的 thread 正在使用,先移到 retire list 之後再刪除。反之若是一致。評估 curr 的 key 若是比輸入的 \*key 小,則可以直接釋放記憶體。反之就可能是大於或是等於的關係,就可以利用是否等於來判斷有沒有找到。
```c
static bool __list_find(list_t *list,
list_key_t *key,
atomic_uintptr_t **par_prev,
list_node_t **par_curr,
list_node_t **par_next)
{
atomic_uintptr_t *prev = NULL;
list_node_t *curr = NULL, *next = NULL;
try_again:
prev = &list->head;
curr = (list_node_t *) ATOMIC_LOAD(prev);
(void) list_hp_protect_ptr(list->hp, HP_CURR, (uintptr_t) curr);
if (ATOMIC_LOAD(prev) != get_unmarked(curr)) {
TRACE(retry);
goto try_again;
}
while (true) {
if (is_marked(curr))
TRACE(contains);
next = (list_node_t *) ATOMIC_LOAD(&get_unmarked_node(curr)->next);
(void) list_hp_protect_ptr(list->hp, HP_NEXT, get_unmarked(next));
/* On a CAS failure, the search function, "__list_find," will simply
* have to go backwards in the list until an unmarked element is found
* from which the search in increasing key order can be started.
*/
if (ATOMIC_LOAD(&get_unmarked_node(curr)->next) != (uintptr_t) next) {
TRACE(retry);
goto try_again;
}
if (ATOMIC_LOAD(prev) != get_unmarked(curr)) {
TRACE(retry);
goto try_again;
}
if (get_unmarked_node(next) == next) {
if (!(get_unmarked_node(curr)->key < *key)) {
*par_curr = curr;
*par_prev = prev;
*par_next = next;
return (get_unmarked_node(curr)->key == *key);
}
prev = &get_unmarked_node(curr)->next;
(void) list_hp_protect_release(list->hp, HP_PREV,
get_unmarked(curr));
} else {
uintptr_t tmp = get_unmarked(curr);
if (!CAS(prev, &tmp, get_unmarked(next))) {
TRACE(retry);
goto try_again;
}
list_hp_retire(list->hp, get_unmarked(curr));
}
curr = next;
(void) list_hp_protect_release(list->hp, HP_CURR, get_unmarked(next));
TRACE(traversal);
}
}
```
* list_insert
在 list_insert 中會先確認 prev 是不是指到 curr ,如果是才會將新的 node 插入到 list;反之會重做一次。
```c
bool list_insert(list_t *list, list_key_t key)
{
list_node_t *curr = NULL, *next = NULL;
atomic_uintptr_t *prev = NULL;
list_node_t *node = list_node_new(key);
while (true) {
if (__list_find(list, &key, &prev, &curr, &next)) {
list_node_destroy(node);
list_hp_clear(list->hp);
return false;
}
ATOMIC_STORE_EXPLICIT(&node->next, (uintptr_t) curr,
memory_order_relaxed);
uintptr_t tmp = get_unmarked(curr);
if (CAS(prev, &tmp, (uintptr_t) node)) {
list_hp_clear(list->hp);
return true;
}
TRACE(ins);
}
}
```
* list_delete
在 list_delete 中會確認 curr->next 與 next 是不是同一個,若不是就重做;反之就先把要被刪除 node 的 next 標記並存到 curr->next。然後比較 prev 和 curr 的狀況看是可以需要放到 retire list 中或是可以立刻刪除。
```c
bool list_delete(list_t *list, list_key_t key)
{
list_node_t *curr, *next;
atomic_uintptr_t *prev;
while (true) {
if (!__list_find(list, &key, &prev, &curr, &next)) {
list_hp_clear(list->hp);
return false;
}
uintptr_t tmp = get_unmarked(next);
if (!CAS(&curr->next, &tmp, get_marked(next))) {
TRACE(del);
continue;
}
tmp = get_unmarked(curr);
if (CAS(prev, &tmp, get_unmarked(next))) {
list_hp_clear(list->hp);
list_hp_retire(list->hp, get_unmarked(curr));
} else {
list_hp_clear(list->hp);
}
return true;
}
}
```
## 延伸問題 2 - 指出改進空間並著手實作
## 延伸問題 3 - 對比 rcu_list,解釋同為 lock-free 演算法,跟上述 Hazard pointer 手法有何異同?能否指出 rcu_list 實作缺陷並改進?
## 參考
[Lock-Free Data Structures with Hazard Pointers](https://erdani.org/publications/cuj-2004-12.pdf)