# linux-2023-summer: costoxff
> [題目連結](https://hackmd.io/@sysprog/linux2023-summer-quiz0)
## α - 1
> 簡短複習 [AVL Tree](https://oi-wiki.org/ds/avl/), [RB Tree](https://oi-wiki.org/ds/rbtree/)
先來觀察節點結構與指標操作
```cpp
struct st_node {
short hint;
struct st_node *parent;
struct st_node *left, *right;
};
```
```graphviz
digraph st_node {
// config
node[shape=record];
graph[rankdir=LR]
// node
n [
label="<f0> hint | <f1> *parent | {*left | *right}"
];
}
```
> [Record-based Nodes](https://graphviz.org/doc/info/shapes.html#record)
可嵌入到自定義的結構,並搭配 [container_of](https://hackmd.io/@sysprog/linux-macro-containerof)
```cpp
struct st_data {
void *data;
struct st_node;
}
```
```graphviz
digraph st_data {
graph[rankdir=LR]
node[shape=record];
subgraph cluster {
data[label="*data"]
n [
label="hint |<f1> *parent | {<f2> *left | <f3> *right}"
];
label = "st_data";
}
nil[label="null"]
nil1[label="null"]
nil2[label="null"]
n:f1 -> nil
n:f2 -> nil2
n:f3 -> nil1
}
```
```cpp
#define st_root(r) (r->root)
#define st_left(n) (n->left)
#define st_right(n) (n->right)
#define st_rparent(n) (st_right(n)->parent)
#define st_lparent(n) (st_left(n)->parent)
#define st_parent(n) (n->parent)
```
使用巨集來代替繁瑣的指標操作。
展開巨集`st_rparent(n)`,變成`n->left->parent`。
取得透過 n 節點指向 left 節點的指標,再指向 left 節點的 parenet 指標。
> 注意,巨集取得的是指標,不是節點本身。
### Function of S-Tree (1)
簡單的說明
```cpp
struct st_node *st_first(struct st_node *n);
struct st_node *st_last(struct st_node *n);
void st_rotate_left(struct st_node *n);
void st_rotate_right(struct st_node *n);
int st_balance(struct st_node *n);
int st_max_hint(struct st_node *n);
```
#### `st_first` / `st_last`
以傳入的節點當作初始點,向左指標查找節點是否存在,如果存在,就呼叫自己,將當前節點的左指標傳入,遞迴查找,否則回傳當前節點指標。`st_last`則是向右的版本。
#### `st_rotate_left/right`
如同 BST 中的旋轉,參照 [Tree rotation](https://en.wikipedia.org/wiki/Tree_rotation)

> 取自 [https://en.wikipedia.org/wiki/Tree_rotation](https://en.wikipedia.org/wiki/Tree_rotation#/media/File:Tree_rotation_animation_250x250.gif)
#### `st_balance`
用來判斷節點中,左、右子樹的 hint 差值。回傳值像是 AVL Tree 的 balance factor,大於 0 代表左子樹 hint 大於右子樹,小於 0 則是相反。
#### `st_max_hint`
比較左、右子樹的 hint 並回傳最大值。用於將值附於當前節點的 hint ,可用於判斷當前節點下最長的節點連接數。
### Function of S-Tree (2)
```cpp
void st_update(struct st_node **root, struct st_node *n);
void st_insert(struct st_node **root, struct st_node *p,
struct st_node *n, enum st_dir d)
void st_replace_right(struct st_node *n, struct st_node *r);
void st_replace_left(struct st_node *n, struct st_node *l);
void st_remove(struct st_node **root, struct st_node *del);
```
為何要以 **root 傳入,詳見[指標篇](https://hackmd.io/@sysprog/c-prog/%2Fs%2FHyBPr9WGl#%E6%B2%92%E6%9C%89%E3%80%8C%E9%9B%99%E6%8C%87%E6%A8%99%E3%80%8D%E5%8F%AA%E6%9C%89%E3%80%8C%E6%8C%87%E6%A8%99%E7%9A%84%E6%8C%87%E6%A8%99%E3%80%8D)
#### `st_upgdate`
以傳入的 n 節點為起點,先以`st_balance(n)`計算 hint 差值,小於 1 代表向右傾,所以進行右旋。大於 1 則是左傾,進行左旋。接著,會以`st_max_hint(n)`更新當前節點的 hint。
當更新後的 hint 等於 0 ,或更新前的 hint (prev_hint) 不等於更新後的,就沿著親代節點做遞迴式更新。
#### `st_insert`
單純的選擇要將 n 節點新增到 p 節點的左邊或右邊。
#### `st_remove` `st_replace_left\right`
[Deletion in BST](https://alrightchiu.github.io/SecondRound/binary-search-tree-sortpai-xu-deleteshan-chu-zi-liao.html#delete)
節點的刪除
### S-Tree
最後,了解對節點的操作,剩下的就可以自己針對樹的操作進行設計。
以下是[檔案](https://gist.github.com/jserv/4dfaf78cf516cc20f4bc55ce388c195d)裡提供的方法,當然也可以自己設計、改寫結構。
```cpp
struct treeint {
int value;
struct st_node st_n;
};
int treeint_init();
int treeint_destroy();
struct treeint *treeint_insert(int);
struct treeint *treeint_find(int);
int treeint_remove(int);
```
## α - 2
## α - 3
## β - 1
```cpp=
#include <stdint.h>
static inline uintptr_t align_up(uintptr_t sz, size_t alignment)
{
uintptr_t mask = alignment - 1;
if ((alignment & mask) == 0) { /* power of two? */
return MMMM;
}
return (((sz + mask) / alignment) * alignment);
}
```
> MMMM = sz + mask & ~mask
### 原理分析
#### 取整函數
得出結論的想法來自於[上取整函數(ceiling function)](https://en.wikipedia.org/wiki/Floor_and_ceiling_functions)。電腦科學中一般記作 $ceil(x)$,表示不小於x的整數中最小的一個。
$[x] = min \{ n \in \mathbb Z \ |\ x \leq n \}$
為了方便說明,設計兩個取整的 ceiling function,一個是取整到個位數,另一個取整到十位數。
```cpp
/* function one */
int ceil(float num)
{
return (int) (((num + 0.9) / 1) * 1);
}
/* function two */
int ceil2tens(int num)
{
return (((num + 9) / 10) * 10);
}
```
相信大家在初學C語言時對於這樣的取整函數做法非常熟悉,第一種無疑就是加上0.9,在利用轉型(type-casting)捨去個位數後的部份。或是像第二種,利用整數除法作為捨去十位數後數字的手段。
我認為關鍵點在於兩點,**==進位==** 的條件,以及 **==捨去==** 不需要的部份。
初看上取整函數,不難看出當小數部份不為 0 時個位數加 1 (進位),否則就保持原來的數。
同樣的做法也可以套用到相鄰的兩個數位(digit)。
而函式`ceil2tens`與題目第 9 行`(((sz + mask) / alignment) * alignment);`這段程式碼,概念上其實是相近的操作。
所以如果 alignment = 4 就用 4 進位制的方式去看待數字,其他進位制也一樣。
#### power of two?
[How to check if a number is a power of 2](https://stackoverflow.com/a/600306)
alignment 與 mask 的關係無非是相差 1,但如果以 2 進位的角度思考,當 alignment 為 2 的冪時,列出以下表格
| alignment | | mask | |
| -------- | -------- | -------- | -------- |
| $10_{(2)}$ | $2_{(10)}$ | $01_{(2)}$ | $1_{(10)}$ |
| $100_{(2)}$ | $4_{(10)}$ | $011_{(2)}$ | $3_{(10)}$ |
| $1000_{(2)}$ | $8_{(10)}$ | $0111_{(2)}$ | $7_{(10)}$ |
| $10000_{(2)}$ | $16_{(10)}$ | $01111_{(2)}$ | $15_{(10)}$ |
2的冪情況下,alignment 只有唯一一個位元會是 1 其他都是 0。
將 alignment 那個唯一的位元 1 的位元位置記為 $n$,可發現在 mask 最低有效位(LSB)到 $n - 1$ 位的位元全都是 1,正如表格所示。
若今天需要判斷某個數 a 是否為 2 的冪,可透過
```cpp
(a & (a - 1)) == 0)
```
來進行判斷,或是
```cpp
(a & -a) == a
```
### `sz + mask & ~mask` 的操作
```cpp=
align_up(120, 4) = 120
align_up(121, 4) = 124
align_up(122, 4) = 124
align_up(123, 4) = 124
align_up(124, 4) = 124
```
以題目的範例說明,現在給出一個記憶體位置,需要以 4 進行對齊。
#### `sz + mask`
以 mask 進行 __"上取整"__
```cpp=
120 + 3 = 123
121 + 3 = 124
122 + 3 = 125
123 + 3 = 126
124 + 3 = 127
```
#### `~mask`
```cpp=
~mask = (-mask - 1) = -3 - 1 = -4
```
$-4$ 在這裡的二進制為 $1111...1111000_{(2)}$ 共有 61 個 1 加上後 3 個 0
利用這點對 **==捨去==** 進行改寫。
```cpp
/ alignment * alignment
```
變成
```cpp
& ~mask
```
> 參考 [Mask (computing)](https://en.wikipedia.org/wiki/Mask_(computing))
#### `sz + mask & ~mask`
由於現在 ~mask 其他位元為1,後3位為0,利用 bitwise AND 進行**捨去**。
```cpp=
123 & -4 = 120
124 & -4 = 124
125 & -4 = 124
126 & -4 = 124
127 & -4 = 124
```
## β - 2
## γ - 1
### code tracing
先列出容易觀察到的 function 間的調用關係,由上至下。
CMP 巨集會展開成 med 裡的 function point: __*cmp__
```cpp
#define CMP(t, x, y) (cmp((x), (y)))
char *med3(char *a, char *b, char *c, cmp_t *cmp, void *thunk)
```
```graphviz
digraph {
node[shape=box]
qsort_mt->pthread_create
pthread_create[color=green]
pthread_create->qsort_thread
qsort_thread->qsort_algo
qsort_algo->med
qsort_algo->allocate_thread
CMP[label="CMP(macro)"]
med->CMP
qsort_algo->swap
qsort_algo->vecswap
swapfunc->swapcode
swap->swapfunc
vecswap->swapfunc
}
```
追蹤 `qsort-mt`,對 thread pool 配置記憶體, mutex cond 初始化,建立執行序。
而 label f3 f2 f1 做資源的釋放。
執行序建立後,多個執行序會先在 `qsort_thread` 裡的 `pthread_cond_wait` 等待第一次的 signal。
## γ - 2
以 [Thread Sanitizer](https://github.com/google/sanitizers/wiki/ThreadSanitizerCppManual) 進行編譯並執行。
```shell
gcc -g -Wall -fsanitize=thread -o qsort qsort-mt.c -lpthread
./qsort
```
也可以將執行結果導向到文件
```shell
./qsort 2> report.txt
```
得到錯誤訊息
```
==================
WARNING: ThreadSanitizer: data race (pid=121156)
Write of size 4 at 0x7b4000000080 by thread T1 (mutexes: write M0):
#0 allocate_thread /home/nocmos/codespace/linux2023smhw1/qsortmt/qsort-mt.c:211 (qsort+0x3bc1)
#1 qsort_algo /home/nocmos/codespace/linux2023smhw1/qsortmt/qsort-mt.c:314 (qsort+0x3bc1)
#2 qsort_thread /home/nocmos/codespace/linux2023smhw1/qsortmt/qsort-mt.c:352 (qsort+0x4008)
Previous read of size 4 at 0x7b4000000080 by thread T2 (mutexes: write M2):
#0 qsort_thread /home/nocmos/codespace/linux2023smhw1/qsortmt/qsort-mt.c:343 (qsort+0x3fca)
Location is heap block of size 256 at 0x7b4000000000 allocated by main thread:
#0 calloc ../../../../src/libsanitizer/tsan/tsan_interceptors_posix.cpp:672 (libtsan.so.0+0x31edc)
#1 qsort_mt /home/nocmos/codespace/linux2023smhw1/qsortmt/qsort-mt.c:132 (qsort+0x4408)
#2 main /home/nocmos/codespace/linux2023smhw1/qsortmt/qsort-mt.c:505 (qsort+0x2ab7)
Mutex M0 (0x7ffed69559a8) created at:
#0 pthread_mutex_init ../../../../src/libsanitizer/tsan/tsan_interceptors_posix.cpp:1227 (libtsan.so.0+0x4bee1)
#1 qsort_mt /home/nocmos/codespace/linux2023smhw1/qsortmt/qsort-mt.c:130 (qsort+0x43e9)
#2 main /home/nocmos/codespace/linux2023smhw1/qsortmt/qsort-mt.c:505 (qsort+0x2ab7)
Mutex M2 (0x7b40000000a8) created at:
#0 pthread_mutex_init ../../../../src/libsanitizer/tsan/tsan_interceptors_posix.cpp:1227 (libtsan.so.0+0x4bee1)
#1 qsort_mt /home/nocmos/codespace/linux2023smhw1/qsortmt/qsort-mt.c:136 (qsort+0x44d2)
#2 main /home/nocmos/codespace/linux2023smhw1/qsortmt/qsort-mt.c:505 (qsort+0x2ab7)
Thread T1 (tid=121158, running) created by main thread at:
#0 pthread_create ../../../../src/libsanitizer/tsan/tsan_interceptors_posix.cpp:969 (libtsan.so.0+0x605b8)
#1 qsort_mt /home/nocmos/codespace/linux2023smhw1/qsortmt/qsort-mt.c:144 (qsort+0x4490)
#2 main /home/nocmos/codespace/linux2023smhw1/qsortmt/qsort-mt.c:505 (qsort+0x2ab7)
Thread T2 (tid=121159, running) created by main thread at:
#0 pthread_create ../../../../src/libsanitizer/tsan/tsan_interceptors_posix.cpp:969 (libtsan.so.0+0x605b8)
#1 qsort_mt /home/nocmos/codespace/linux2023smhw1/qsortmt/qsort-mt.c:144 (qsort+0x4490)
#2 main /home/nocmos/codespace/linux2023smhw1/qsortmt/qsort-mt.c:505 (qsort+0x2ab7)
SUMMARY: ThreadSanitizer: data race /home/nocmos/codespace/linux2023smhw1/qsortmt/qsort-mt.c:211 in allocate_thread
==================
ThreadSanitizer: reported 1 warnings
```
可發現資源競爭出現在 function `allocate_thread()` 中的 `c->pool[i].st = ts_work;`
由 [NOVBobLee](https://hackmd.io/@sysprog/SkmKiSfh2#NOVBobLee) 提及,應該從 thread pool 獲取 thread 並改變狀態前,先行獲得 mutex。
### 圖解
## γ - 3