# 2023 Homework2 (quiz1) contributed by < `backink` > [github](https://github.com/backink/Linux2023_summer/tree/main/hw2) ## 開發環境 ``` $gcc --version gcc (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0 $lscpu Architecture: x86_64 CPU op-mode(s): 32-bit, 64-bit Address sizes: 44 bits physical, 48 bits virtual Byte Order: Little Endian CPU(s): 16 On-line CPU(s) list: 0-15 Vendor ID: AuthenticAMD Model name: AMD Ryzen 7 4800HS with Radeon Graphics CPU family: 23 Model: 96 Thread(s) per core: 2 Core(s) per socket: 8 Socket(s): 1 Stepping: 1 Frequency boost: enabled CPU max MHz: 2900.0000 CPU min MHz: 1400.0000 BogoMIPS: 5789.25 Virtualization features: Virtualization: AMD-V Caches (sum of all): L1d: 256 KiB (8 instances) L1i: 256 KiB (8 instances) L2: 4 MiB (8 instances) L3: 8 MiB (2 instances) NUMA: NUMA node(s): 1 NUMA node0 CPU(s): 0-15 ``` ## 開發過程 ```c static inline void cond_signal(cond_t *cond, mutex_t *mutex) { fetch_add(&cond->seq, BBBB, relaxed); EEEE(&cond->seq, 1); } ``` **BBBB** 為 1,因為在cond_wait 中會等待 cond->seq 的值改變,在cond_signal時將 condvar 的值加 1 可以讓在 cond_wait 中等待的執行緒可以往下進行。 **EEEE** 為 futex_wake ,在 cond->seq 值改變後,喚醒一個在 futex_wait 中等待此 condvar 改變的 thread。 ```c static inline void cond_broadcast(cond_t *cond, mutex_t *mutex) { fetch_add(&cond->seq, CCCC, relaxed); futex_requeue(&cond->seq, DDDD, &mutex->state); } ``` **CCCC** 為 1 ,原因同 BBBB **DDDD** 為 1 ,因為 broadcast 後所有 thread 都不需要再等待 condvar ,不過還是需要等待 mutex,所以利用 futex_requeue 將原先等待 cond->seq 的 thread 轉到等待 mutex->state 的 wait_queue 中。不過因為最終還是只有一個 thread 可往下執行,所以在此喚醒一個等待中的 thread。 參考 futex man page 中 FUTEX_CMP_REQUEUE 部份說明,和 syscall 參數 * the operation wakes up a maximum of *val* waiters that are waiting on the futex at *uaddr*. If there are more than *val* waiters, then the remaining waiters are removed from the wait queue of the source futex at *uaddr* and added to the wait queue of the target futex at *uaddr2*. * 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.h 檔案中的函式定義,在此 val 為 1,uaddr 為 &cond->seq, uaddr2 為 &mutex->state。 ```c static inline void mutex_unlock(mutex_t *mutex) { int state = exchange(&mutex->state, 0, release); if (state & MUTEX_SLEEPING) FFFF(&mutex->state, 1); } ``` **FFFF** 為 futex_wake,在 mutex_unlock 中如果有 thread 在 mutex->state 的 wait_queue 中則喚醒其中一個 thread。 ```c static inline void cond_wait(cond_t *cond, mutex_t *mutex) { int seq = load(&cond->seq, relaxed); mutex_unlock(mutex); #define COND_SPINS 128 for (int i = 0; i < COND_SPINS; ++i) { if (load(&cond->seq, relaxed) != seq) { mutex_lock(mutex); return; } spin_hint(); } futex_wait(&cond->seq, seq); mutex_lock(mutex); fetch_or(&mutex->state, AAAA, relaxed); } ``` **AAAA** 為 MUTEX_SLEEPING,這裡設定 MUTEX_SLEEPING 的原因是如果 cond->seq 被 broadcast 過,等待 cond->seq 的 thread 會轉至等待 mutex->state,在這個情境下如過沒有設定 MUTEX_SLEEPING 在 mutex_unlock 的時候有可能會因為沒有 MUTEX_SLEEPING 這個 flag 導致雖然有 thread 在等待但卻沒有任何 thread被喚醒。 這個地方我花了不少時間思考,其中遇到兩個問題 1. 如果沒有 MUTEX_SLEEPING 這個 flag 似乎也能正確執行,如果是這樣那設定 MUTEX_SLEEPING 的用意是什麼? 2. 在 cond_wait 和 mutex_lock 中可能有一種情況是拿到 mutex 的 thread 還是需要設定 MUTEX_SLEEPING 的 flag,即使這個 thread 已經脫離 sleeping 的狀態 初步跟同學討論第一個問題的結果認為, futex 是高成本的 syscall ,如果可以在 user space 用某些手段判斷減少呼叫 futex 相關 syscall 的機會,可以降低執行系統成本以及執行時間 第二個問題思考的是,既然 MUTEX_SLEEPING 是用來節制呼叫 futex 的手段,在某些狀況下拿到 mutex 的 thread 仍然會去設定這個 flag 即使自己已經不再需要等待。這樣的方式可以確保在 mutex_unlock的時候會有等待中的 thread 被喚醒,可是如果剛好沒有 thread 正在等待,等於也會呼叫一次無效果的 futex_wake 造成資源浪費。 考慮這樣的情況 MUTEX_SLEEPING 的設計是否有其效益,或是有其他方式可以處理拿到 mutex 還要設定 sleeping 的情況,我需要再思考一下並設計實驗去驗證。 :::success 延伸問題: * 解釋上述程式碼運作原理,應該涵蓋測試方法、futex 使用方式、Linux 核心開發者針對 POSIX Threads 強化哪些相關 futex 實作機制等等 * 修改第 1 次作業的測驗 γ 提供的並行版本快速排序法實作,使其得以搭配上述 futex 程式碼運作 * 研讀〈並行程式設計: 建立相容於 POSIX Thread 的實作〉,在上述程式碼的基礎之上,實作 priority inheritance mutex 並確認與 glibc 實作行為相同,應有對應的 PI 測試程式碼比照 skinny-mutex,設計 POSIX Threads 風格的 API,並利用內附的 perf.c (斟酌修改) 確認執行模式符合預期,過程中也該比較 glibc 的 POSIX Threads 效能表現 :::