owned this note
                
                
                     
                     owned this note
                
                
                     
                    
                
                
                     
                    
                
                
                     
                    
                        
                            
                            Published
                        
                        
                            
                                
                                Linked with GitHub
                            
                            
                                
                                
                            
                        
                     
                
            
            
                
                    
                    
                
                
                    
                
                
                
                    
                        
                    
                    
                    
                
                
                
                    
                
            
            
         
        
        # 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 並行處理