2023 Homework2 (quiz1)
======================
#### contributed by <[shlin41](https://github.com/shlin41)>
---
## Q1:解釋程式碼運作原理
> 註:解釋上述程式碼運作原理,應該涵蓋測試方法、futex 使用方式、Linux 核心開發者針對 POSIX Threads 強化哪些相關 futex 實作機制等等
### [atomic.h]
* 目的:用 MACRO 簡化 <stdatomic> 中function 的名字
* read_fense: thread_fence(state, acquire) -> 用來避免 LL, LS 互換。保護前面的那個 L。
```graphviz
digraph orderAcquire {
node [shape=box]
{
a [label="L"]
c [label="L"]
}
{
a1 [label="L"]
c1 [label="S"]
}
{
a->a1 [dir="both" fontsize="20" color = "red" headlabel="×" labeldistance="3.5" labelangle="0" minlen="2"]
c->c1 [dir="both" fontsize="20" color = "red" headlabel="×" labeldistance="3.5" labelangle="0" minlen="2"]
}
}
```
* write_fense: thread_fence(state, release) -> 用來避免 SS, LS 互換。保護後面的那個 S。
```graphviz
digraph orderRelease {
node [shape=box]
{
a [label="S"]
c [label="L"]
}
{
a1 [label="S"]
c1 [label="S"]
}
{
a->a1 [dir="both" fontsize="20" color = "red" headlabel="×" labeldistance="3.5" labelangle="0" minlen="2"]
c->c1 [dir="both" fontsize="20" color = "red" headlabel="×" labeldistance="3.5" labelangle="0" minlen="2"]
}
}
```
* 心得:[Atomic_thread_fence](https://en.cppreference.com/w/cpp/atomic/atomic_thread_fence)
* ==memory_order_acquire: 先 atomic_action 再 read_fence==
```
atomic_exchange_explicit(state, true, acquire);
等於
atomic_exchange_explicit(state, true, relaxed); // 先 atomic action
atomic_thread_fence(acquire); // 再 read memory barrier
```
* ==memory_order_release: 先 write_fence 再 atomic_action==
```
atomic_exchange_explicit(state, true, release);
等於
atomic_thread_fence(release); // 先 write memory barrier
atomic_exchange_explicit(state, true, relaxed); // 再 atomic action
```
* ==memory_order_acq_rel: 先 write_fence 再 atomic_action 再 read_fence==
```
atomic_exchange_explicit(state, true, acq_rel);
等於
thread_fence(release); // 先 write memory barrier
atomic_exchange_explicit(state, true, relaxed); // 再 atomic action
thread_fence(acquire); // 再 read memory barrier
### [futex.h]
* SystemCall 宣告: (six arguments)
```
long futex(uint32_t *uaddr, int futex_op, uint32_t val,
const struct timespec *timeout, /* or: uint32_t val2 */
uint32_t *uaddr2, uint32_t val3);
```
* kernel 對應的 function: (7 arguments)
```
long do_futex(u32 __user *uaddr, int op, u32 val, ktime_t *timeout,
u32 __user *uaddr2, u32 val2, u32 val3)
```
* 對照看 [man futex(2)](https://man7.org/linux/man-pages/man2/futex.2.html):
* FUTEX_WAIT
* FUTEX_WAKE
* FUTEX_REQUEUE
* FUTEX_CMP_REQUEUE
* FUTEX_CMP_REQUEUE_PI
### [mutex.h]
* 實做 mutex_trylock, mutex_lock, mutex_unlock
* int state: 搭配 {MUTEX_LOCKED, MUTEX_SLEEPING} 紀錄狀態
* int state: 因為至少需要兩個 bits,所以直接使用 int
* MUTEX_SLEEPING: 設計得很怪
* 應該是為了紀錄有沒有人在 queue 裡面等待
* 如果有 (i.e. MUTEX_SLEEPING is set) 則 futex_wake
* 但是 state == b01 的機會很少
* 用到 cond_wait 時,每次都設定 state == b11
* 只有在使用 mutex_trylock / mutex_lock 的第一個人,才有機會 state = b01
* ==不設計/處理 MUTEX_SLEEPING,每次都 futex_wake,其實程式也可正常執行==
```
static inline void mutex_unlock(mutex_t *mutex) {
int state = exchange(&mutex->state, 0, release);
//if (state & MUTEX_SLEEPING)
futex_wake(&mutex->state, 1);
}
```
* ==mutex 中的 memory fence 擺放的位置搞不懂== [Release-Acquire_ordering](https://en.cppreference.com/w/cpp/atomic/memory_order#Release-Acquire_ordering)
> Q1: 為什麼 mutex_lock 要在最後面擺一個 read_fence? 為什麼 mutex_unlock 要在最前面擺一個 write_fence?
Q2: read_fence 和 write_fence 夾出了一個區間,和滿足 Visible 有何關係?
### [spinlock.h]
* 實做 spin_trylock, spin_lock, spin_unlock
* bool state: 因為只需要紀錄 locked / unlocked
* 因為 spinlock 屬於 busy wait,因此不需要借助 futex 進入 queue 中睡覺
* 利用 spin_hint():生成對應平台的 pause 機器指令
* 和 mutex 有一樣的 memory_fence 設計
* spin_trylock / spin_lock 最後面擺一個 read_fence
* spin_unlock 最前面擺一個 write_fence
### [cond.h]
* 實做 cond_wait, cond_signal, cond_broadcast
* int seq:很有趣的設計,流水號遞增。
* 為了讓 cond_wait 當時的 seq 和之後 cond_signal 當時的 seq 不同
* 如果用 bool seq:會有如下問題
> 假設:
step 1: waiter_1, waiter_2 都讀到 seq == 0 進入 queue
step 2: server 呼叫 cond_signal(seq = 1) 可以叫醒一個 waiter
step 3: server 呼叫 cond_signal(seq = 0) 無法叫醒另一個 waiter
* 那 int seq 就沒問題嗎?
> Ans:
當然不是
同時 max_uint 個人 wait,server 一個一個叫,一樣會有最後一個人叫不醒的問題
### [main.c]
* node 連接方式:
```graphviz
digraph mygraph{
node1 -> node0;
node2 -> node1;
nodeN -> node2;
}
```
* 每個 node 做的事情:(clock 是共享的)
* clock_wait(clock, tick):等到 tick 時間到
(即 clock >= tick)
* 如果有 parent,則等 parent ready
* 交替地 clock_tick(clock) 和 node_signal(self)
* 程式停止條件:clock == 2^16^,由 main 去停止 (set clock = -1)
* 其他討論:
* 為什麼不會卡在 node_wait?
> Ans: 因為 node0 不會 wait,導致 node1 一定可以起床。以此類推。
* 為什麼不會卡在 clock_wait?
> Ans: 比較難解釋。原則上 clock 上漲飛快。在地一次由 main 呼叫 clock_tick 後,所有 node 被喚醒且都會去跑一次 clock_tick。
---
## Q2:修改第 1 次作業的測驗 γ
>註:修改第 1 次作業的測驗 γ提供的並行版本快速排序法實作,使其得以搭配上述 futex 程式碼運作
修改完的版本:[Source Code](https://github.com/shlin41/GammaModify)
* 把 USE_PTHREAD 的部份刪除,只保留 USE_LINUX 的部份 (避免不小心又用到原本的 pthread)
* 把 pthread_mutex_XXX / pthread_cond_XXX 的 functions 都改成 mutex_XXX / cond_XXX
* function 的 argus 和 return 以 pthread 的 templete 為主:mutex.h, cond.h 搭配修改。主要是補 return 0。
==注意:mutex_trylock 原本的邏輯剛好相反,改過來。==
## Q3:實作 priority inheritance mutex
>註:研讀〈並行程式設計: 建立相容於 POSIX Thread 的實作〉,在上述程式碼的基礎之上,實作 priority inheritance mutex 並確認與 glibc 實作行為相同,應有對應的 PI 測試程式碼
## Q4:設計 POSIX Threads 風格的 API
> 註:比照 skinny-mutex,設計 POSIX Threads 風格的 API,並利用內附的 perf.c (斟酌修改) 確認執行模式符合預期,過程中也該比較 glibc 的 POSIX Threads 效能表現
## Reference
* [man futex(2)](https://man7.org/linux/man-pages/man2/futex.2.html)
* [Atomic_thread_fence](https://en.cppreference.com/w/cpp/atomic/atomic_thread_fence)、[Release-Acquire_ordering](https://en.cppreference.com/w/cpp/atomic/memory_order#Release-Acquire_ordering)