---
# System prepended metadata

title: 'linux2023:shhung Homework 2'
tags: [Linux]

---

# 2023 Homework2 (quiz1)
contributed by < [shhung](https://github.com/shhung) >

## FUTEX (Fast userspace Mutex) 運作原理

### Why and how fast
futex 來源於對一般應用的觀察，大多數的場景下 lock 並不會被競爭，這種時候可以很容易的取得 lock，futex 此時利用 atomic operation 來在 userspace 取得 lock，然而還是可能會有其他的 thread 同時競爭鎖，此時有兩種選擇，
- 迴圈持續使用 atomic operation 競爭鎖 (userspace)
- sleep 讓出資源 (context-switch)

若是選擇 sleep 的話還是需要透過 kernel 來讓出資源，等待 lock 的持有者喚醒。
但是如前所述，大多時候同時競爭的情況相比起來還是比較少，因此滿足 Fast 的形容。

### SYSCALL
```c!
long syscall(SYS_futex, uint32_t *uaddr, int futex_op, uint32_t val,
             const struct timespec *timeout,   /* or: uint32_t val2 */
             uint32_t *uaddr2, uint32_t val3);
```
[futex(2)](https://man7.org/linux/man-pages/man2/futex.2.html)

#### `futex_op`
- `FUTEX_WAIT`
獲取 `uaddr` 的值，是否為預期的值 `val` ，如果相同則進入睡眠，等待 `FUTEX_WAKE` 喚醒，若不同，則失敗並獲得 `EAGAIN` 錯誤。

- `FUTEX_WAKE`
喚醒等待 `uaddr` 的佇列中，最多 `val` 個數量的等待者。

- `FUTEX_REQUEUE`
喚醒等待 `addr` 的佇列中，最多 `val` 個數量的等待者，如果等待者數量超過 `val` ，將未被喚醒的等待者移到等待 `uaddr2` 的佇列， `uaddr2` 的佇列中最多可以有 `val2` 個等待者。

## 測試方法


## Futex Requeue PI
Futex Requeue PI 是為了避免 [Priority Invertion](https://en.wikipedia.org/wiki/Priority_inversion) 的問題，同時也能降低 `pthread_cond_broadcast` 造成的 [Thundering-herd problem](https://en.wikipedia.org/wiki/Thundering_herd_problem) 。
這裡的 PI 是 [Priority inheritance](https://en.wikipedia.org/wiki/Priority_inheritance) 的縮寫，基本的原理是當一個優先權較低的執行緒 A 佔有 lock ，而其他競爭中的執行緒 B, C 優先權都比 A 還高，但是優先權 B < C ，此時會將 A 的優先權暫時提高到和 C 相同，當 A unlock 後再降低優先權，以此避免上面提到的 Priority invertion 。

## 以 futex 修改 qsort-mt 使用的 pthread
最直覺的方法先將 `pthread` 相關函式以 `mutex.h` 和 `cond.h` 裡對應的函式取代，修改後執行並給參數 `v` 做驗證，結果不正確，需要深入去找錯誤的地方。

## 實作 priority inheritance mutex

### 思路
為了以最簡單的方法實現 Priority inheritance ，在 mutex 結構中紀錄 lock 的擁有者以及原本的優先權，並加入以下操作

1. 獲取 lock 後，擁有者除了改變 state 還會記錄自己的 tid 和當下的優先權，這樣在釋放 lock 後如果有調整優先權可以在調整回來
2. 當請求 lock 失敗後，在 `futex_wait` 之前會比較自身的優先權和擁有者的優先權，如果擁有者的優先權較低，將其設定為自己的優先權
3. 釋放 lock ，在 `futex_wake` 之前將優先權設定回原本的優先權

### 改動

#### mutex.h

```diff
typedef struct {
    atomic int state;
+    pthread_t owner;
+    int own_prio;
} mutex_t;
```

```diff
static inline void mutex_lock(mutex_t *mutex)
{
#define MUTEX_SPINS 128
    for (int i = 0; i < MUTEX_SPINS; ++i) {
        if (mutex_trylock(mutex))
            return;
        spin_hint();
    }
+    int own_policy;
+    struct sched_param own_param;

    int state = exchange(&mutex->state, MUTEX_LOCKED | MUTEX_SLEEPING, relaxed);

    while (state & MUTEX_LOCKED) {
+        int policy;
+        struct sched_param param;
+        pthread_getschedparam(mutex->owner, &own_policy, &own_param);
+        pthread_getschedparam(pthread_self(), &policy, &param);
+        if (own_param.sched_priority < param.sched_priority) {
+            pthread_setschedparam(mutex->owner, SCHED_RR, &param);
+        }
        futex_wait(&mutex->state, MUTEX_LOCKED | MUTEX_SLEEPING);
        state = exchange(&mutex->state, MUTEX_LOCKED | MUTEX_SLEEPING, relaxed);
    }

    thread_fence(&mutex->state, acquire);
+    mutex->owner = pthread_self();
+    pthread_getschedparam(mutex->owner, &own_policy, &own_param);
+    mutex->own_prio = own_param.sched_priority;
}
```

```diff
static inline void mutex_unlock(mutex_t *mutex)
{
    int state = exchange(&mutex->state, 0, release);
    if (state & MUTEX_SLEEPING) {
+        pthread_setschedprio(mutex->owner, mutex->own_prio);
        futex_wake(&mutex->state, 1);
    }
}
```

在 TSAN 的檢查下 `pthread_setschedprio()` 發生 SEGV