# 2024q1 Homework6 (integration)
contributed by < [`teresasa0731`](https://github.com/teresasa0731) >
## 實驗環境
```shell
$ gcc --version
gcc (Ubuntu 12.3.0-1ubuntu1~22.04) 12.3.0
Copyright (C) 2022 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
```
:::success
**Multiple GCC Versions on Ubuntu**
原本的 gcc 是 11.4.0 版本,無法編譯ksort,故先安裝 gcc-12。
[安裝參考](https://phoenixnap.com/kb/install-gcc-ubuntu)
:::
## 自我檢查清單
- [ ] 研讀前述 ==Linux 效能分析== 描述,在自己的實體電腦運作 GNU/Linux,做好必要的設定和準備工作 $\to$ 從中也該理解為何不希望在虛擬機器中進行實驗;
- [ ] 閱讀〈[Linux 核心模組運作原理](https://hackmd.io/@sysprog/linux-kernel-module)〉並對照 Linux 核心原始程式碼 (v6.1+),解釋 `insmod` 後,Linux 核心模組的符號 (symbol) 如何被 Linux 核心找到 (使用 List API)、`MODULE_LICENSE` 巨集指定的授權條款又對核心有什麼影響 (GPL 與否對於可用的符號列表有關),以及藉由 [strace](https://man7.org/linux/man-pages/man1/strace.1.html) 追蹤 Linux 核心的掛載,涉及哪些系統呼叫和子系統?
> 〈[Linux 核心模組運作原理](https://hackmd.io/@sysprog/linux-kernel-module)〉列出的程式碼較舊,歡迎編輯頁面,更新到 Linux v6.1 以上。
- [ ] 閱讀《[The Linux Kernel Module Programming Guide](https://sysprog21.github.io/lkmpg/)》(LKMPG) 並解釋 [simrupt](https://github.com/sysprog21/simrupt) 程式碼裡頭的 mutex lock 的使用方式,並探討能否改寫為 [lock-free](https://hackmd.io/@sysprog/concurrency-lockfree);
> 參照 [2021 年的筆記](https://hackmd.io/@linD026/simrupt-vwifi)。歡迎貢獻 LKMPG!
> $\to$ 搭配閱讀〈[並行和多執行緒程式設計](https://hackmd.io/@sysprog/concurrency)〉
- [ ] 探討 Timsort, Pattern Defeating Quicksort (pdqsort) 及 Linux 核心 [lib/sort.c](https://github.com/torvalds/linux/blob/master/lib/sort.c) 在排序過程中的平均比較次數,並提供對應的數學證明;
> 對照 [fluxsort](https://github.com/scandum/fluxsort) 和 [crumsort](https://github.com/scandum/crumsort) 的分析和效能評比方式
- [x] 研讀 [CMWQ](https://www.kernel.org/doc/html/latest/core-api/workqueue.html) (Concurrency Managed Workqueue) 文件,對照 [simrupt](https://github.com/sysprog21/simrupt) 專案的執行表現,留意到 worker-pools 類型可指定 "Bound" 來分配及限制特定 worker 執行於指定的 CPU,Linux 核心如何做到?CMWQ 關聯的 worker thread 又如何與 CPU 排程器互動?
> 搭配閱讀《Demystifying the Linux CPU Scheduler》
- [ ] 解釋 `xoroshiro128+` 的原理 (對照〈[Scrambled Linear Pseudorandom Number Generators](https://arxiv.org/pdf/1805.01407.pdf)〉論文),並利用 [ksort](https://github.com/sysprog21/ksort) 提供的 `xoro` 核心模組,比較 Linux 核心內建的 `/dev/random` 及 `/dev/urandom` 的速度,說明 `xoroshiro128+` 是否有速度的優勢?其弱點又是什麼?
> $\to$ 搭配閱讀: [不亂的「亂數」](https://blog.cruciferslab.net/?p=599)
- [x] 解釋 [ksort](https://github.com/sysprog21/ksort) 如何運用 CMWQ 達到並行的排序;
## 開發紀錄
### 研讀 CMWQ (Concurrency Managed Workqueue) 文件
在linux kernel 中有提供 workqueue 的機制用來存放工作項目,以下探討舊的 workqueue 存在什麼問題以至於需要引入 CMWQ ?以及新的 CMWQ 怎麼實現呼叫功能 & 與舊的系統相容。
#### 為何需要 CMWQ ?
在非同步執行環境中會定義 work,相應處理的線程稱之 worker,並以 workqueue 來儲存工作項目。根據原本 wq 設計,單線程 (single-thread, ST) wq 在整個系統中只有一個工作線程;多線程(multi-thread, MT)在每一個 CPU 都有一個工作線程,前者會有 deadlock 問題,後者則是隨著 CPU 核心增加而創建大量工作線程,進一步導致 PID 空間浪費。
CMWQ 重新設計 wq 實作方式:
* 保持原有wq API的兼容性。
* 每一個 CPU 維持自己的 workers pool,並在 wq 中共享。
* 自動調節 worker pool 和平行度。
CMWQ 提出 worker pool (類似一種 thread pool),在系統中存在 worker pools 不與特定 wq 相關連而是所有 wq 共享,並根據 flag 交付該 work 給 某個 worker pool。
> When a work item is queued to a workqueue, the target worker-pool is determined according to the queue parameters and workqueue attributes and appended on the shared worklist of the worker-pool. For example, unless specifically overridden, a work item of a bound workqueue will be queued on the worklist of either normal or highpri worker-pool that is associated to the CPU the issuer is running on.
#### workqueue.[ch]
根據linux 程式碼 [/include/linux/workqueue.h](https://elixir.bootlin.com/linux/latest/source/include/linux/workqueue.h)可以看到在 `struct workqueue_attrs` 定義了 wq 的屬性,其中有 `cpumask_var_t cpumask` 來限定 work 分配到的 CPU
> Work items in this workqueue are affine to these CPUs and not allowed to execute on other CPUs. A pool serving a workqueue must have the same @cpumask.
此處提及使用此遮罩可以限定在與工作提交相同的 CPU 上執行,也就是說**在同一 wq 的資源持必須有相同 @cpumask**,避免工作在 CPU 間遷移,盡可能使用 CPU 快取的區域局部性。而在 [/kernel/workqueue.c](https://elixir.bootlin.com/linux/latest/source/kernel/workqueue.c) 則透過 `workqueue_init_early()` 函式來進行初始化的設定,包含 記憶體分配、**cpumasks** 和 idr。
### 解釋 [ksort](https://hackmd.io/@tony268596/linux2024-homework6#%E7%A0%94%E8%AE%80-CMWQ-%E6%96%87%E4%BB%B6%EF%BC%8C%E5%B0%8D%E7%85%A7-simrupt-%E5%B0%88%E6%A1%88) 如何運用 CMWQ 達到並行的排序
在 [ksort/sort_impl.c](https://github.com/sysprog21/ksort/blob/master/sort_impl.c) 中引入了 `linux/workqueue.h`。根據 `qsort_algo()` 邏輯,可以看到當元素數量小於 7 時會使用 Insertion Sort:
```cpp
if (n < 7) {
for (pm = (char *) a + es; pm < (char *) a + n * es; pm += es)
for (pl = pm; pl > (char *) a && CMP(thunk, pl - es, pl) > 0;
pl -= es)
q_swap(pl, pl - es);
return;
}
```
元素數目大於 7 時則改用 Quick Sort 排序:
```cpp
pm = (char *) a + (n / 2) * es;
if (n > 7) {
pl = (char *) a;
pn = (char *) a + (n - 1) * es;
if (n > 40) {
d = (n / 8) * es;
pl = med3(pl, pl + d, pl + 2 * d, cmp, thunk);
pm = med3(pm - d, pm, pm + d, cmp, thunk);
pn = med3(pn - 2 * d, pn - d, pn, cmp, thunk);
}
pm = med3(pl, pm, pn, cmp, thunk);
}
q_swap(a, pm);
pa = pb = (char *) a + es;
pc = pd = (char *) a + (n - 1) * es;
for (;;) {
while (pb <= pc && (r = CMP(thunk, pb, a)) <= 0) {
if (r == 0) {
swap_cnt = 1;
q_swap(pa, pb);
pa += es;
}
pb += es;
}
while (pb <= pc && (r = CMP(thunk, pc, a)) >= 0) {
if (r == 0) {
swap_cnt = 1;
q_swap(pc, pd);
pd -= es;
}
pc -= es;
}
if (pb > pc)
break;
q_swap(pb, pc);
swap_cnt = 1;
pb += es;
pc -= es;
}
pn = (char *) a + n * es;
r = min(pa - (char *) a, pb - pa);
vecswap(a, pb - r, r);
r = min(pd - pc, pn - pd - (long) es);
vecswap(pb, pn - r, r);
if (swap_cnt == 0) { /* Switch to insertion sort */
r = 1 + n / 4; /* n >= 7, so r >= 2 */
for (pm = (char *) a + es; pm < (char *) a + n * es; pm += es)
for (pl = pm; pl > (char *) a && CMP(thunk, pl - es, pl) > 0;
pl -= es) {
q_swap(pl, pl - es);
if (++swap_cnt > r)
goto nevermind;
}
return;
}
```
而若是沒有發生交換,即`swap_cnt == 0`,則切換回插入排序;若是交換次數超過閾值 `r = 1 + n / 4` 則會跳入並行處理區間 `nevermind:`
```cpp
nevermind:
nl = (pb - pa) / es;
nr = (pd - pc) / es;
if (nl > 100 && nr > 100) {
struct qsort *q = kmalloc(sizeof(struct qsort), GFP_KERNEL);
init_qsort(q, a, nl, c);
queue_work(workqueue, &q->w);
} else if (nl > 0) {
qs->a = a;
qs->n = nl;
/* The struct qsort is used for recursive call, so don't free it in
* this iteration.
*/
do_free = false;
qsort_algo(w);
}
if (nr > 0) {
a = pn - nr * es;
n = nr;
goto top;
}
if (do_free)
kfree(qs);
}
```
若是分區後左右子陣列都大於 100 ,此時會分配新的 `qsort` 結構給左子陣列並加入 wq 並行處理