# 2023q1 Homework3 (quiz3)
###### tags: `linux2023`
contributed by <Paintako>
## 測驗 1
考慮以下結構體, "利用 ABI 特性來「標示不用的節點」手段"
```c
typedef struct __node {
uintptr_t color;
struct __node *left, *right;
struct __node *next;
long value;
} node_t __attribute__((aligned(sizeof(long))));
```
首先看到 `__attribute__((aligned(sizeof(long))))`, 透過 `__attribute__((aligned(sizeof(long))))` 讓編譯器知道 struct rb_node 會對齊 sizeof(long)
指的是, `struct __node` 的起始位址會是 8 Byte 倍數的位置
反過來說, 如果某個位址對齊了 8 byte (1000), 會有3個 bit 是永遠用不到的.
以下實驗證明了使用 `__attribute__((aligned(sizeof(long))))` 的結構體的地址永遠對齊 8 Bytes:
```c
#include <stdio.h>
typedef struct __node1{
int value;
char c;
} node1_t __attribute__((aligned(sizeof(long))));
int main() {
node1_t *ptr = malloc(sizeof(node1_t));
printf("%p\n",ptr);
node1_t *ptr1 = malloc(sizeof(node1_t));
printf("%p\n",ptr1);
node1_t *ptr2 = malloc(sizeof(node1_t));
printf("%p\n",ptr2);
return 0;
}
```
```shell
Node1 address: 0x7ffcbb1b7040
Node2 address: 0x7ffcbb1b7018
Node3 address: 0x7ffcbb1b7020
```
那 `uintptr_t color` 這段 color 到底代表的是?
只知道是一個指標,具體這個指標指的東西是指?
看到後續程式碼:
```c
#define rb_parent(r) ((node_t *) (AAAA))
#define rb_color(r) ((color_t) (r)->color & 1)
```
可得知, 父點和顏色都存儲在 `node_t` 中,猜測父點和顏色都存在 `color`中
結合前面已知條件, `node_t`的記憶體位址皆對齊於8, 所以不論 color 指向的記憶體位址為何, 把後 3 bit 給清除掉, 那此地址就符合 `node_t` 的規範.
再結合記憶體對齊的規範, 如果此記憶體對齊 8 Byte, 那後 3 bit 皆不用不到, 那此 3 bit 可以用來表示此點是黑還是紅
結合以上兩者操作, 基本可以判定 color 指的就是:
1. 父點指標
2. 自己的顏色
故程式碼如下:
```c
#define rb_parent(r) ((node_t *) (r)->color & ~7) // AAAA
#define rb_color(r) ((color_t) (r)->color & 1)
```
要設定一個 `node_t` 的顏色為黑色的話, 根據 `typedef enum { CMAP_RED = 0, CMAP_BLACK } color_t;` 得知, 黑色=1, 紅色=0
```c
#define rb_set_red(r)
do {
r->color &= 0; // BBBB
} while (0)
#define rb_set_black(r)
do {
r->color |= 1; // CCCC
} while (0)
```
:::warning
ABI:
* 二進制介面(Application Binary Interface),也就是程序二進制接口或應用程式介面。ABI 定義了在這個架構下不同程式和函數之間的二進制接口規範,例如函數的呼叫約定、參數傳遞方式、資料對齊方式等等。
while(0):
* while (0) 是一個空循環,其主要作用是在 Marco 定義中使用,以便在調用 Marco 時可以避免出現語法錯誤。
Ref: ChatGpt
:::
關於插入紅黑樹, 非常直覺的, 比現在這個節點的值小就往左子樹找, 反之往右子樹找
如果往下走的過程中當前節點是 NULL, 等於找到要插入的位置
所以以下程式碼:
```c
if (res < 0) {
if (!cur->left) {
cur->left = node;
rb_set_parent(node, cur);
cmap_fix_colors(obj, node);
break;
}
cur = curr->left; // DDDD
} else {
if (!cur->right) {
cur->right = node;
rb_set_parent(node, cur);
cmap_fix_colors(obj, node);
break;
}
cur = curr->right; // EEEE
}
```
以下程式碼, 會把一個 list 的 node_t 給傳入, 然後建立一個新的 map 給這個 list, 一個個的插入 node_t
插入後相當於建好一棵紅黑樹, 再從這棵樹中依照 `cmap_next` 中取得下一個點, 放入 list 中, 這樣子把整個 cmap iterate 完後, 等同於排序好整個 list.
```c
void tree_sort(node_t **list)
{
node_t **record = list;
cmap_t map = cmap_new(sizeof(long), sizeof(NULL), cmap_cmp_int);
while (*list) {
cmap_insert(map, *list, NULL);
list = &(*list)->next; // FFFF
}
node_t *node = cmap_first(map), *first = node;
for (; node; node = cmap_next(node)) {
*list = node;
list = &(*list)->next // GGGG
}
*list = NULL; // HHHH
*record = first;
free(map);
}
```
## 測驗 2
考慮以下程式碼:
```c
#define AVL_NODE_ALIGNED __attribute__((aligned(sizeof(unsigned long))))
struct avl_node {
unsigned long parent_balance;
struct avl_node *left, *right;
} AVL_NODE_ALIGNED;
```
可得知, 這裡的對齊手法跟紅黑樹的對齊手法一致, 讓地址皆對齊於 8 byte
```c
static inline struct avl_node *avl_parent(struct avl_node *node)
{
return (struct avl_node *) ((node->parent_balance)& ~7); // IIII
}
```
AVL tree 對於樹高有嚴格要求, 這裡使用一個 enum 來紀錄左右子樹高度差
`enum avl_node_balance { AVL_NEUTRAL = 0, AVL_LEFT, AVL_RIGHT, };`
當:
1. 左右子樹一樣高: node_balance = 0
2. 左樹比又右樹高: node_balance = 1
3. 左樹比又右樹高: node_balance = 2
可用 2 個 bit 表示這棵樹的平衡狀態
故當要讀取此樹的高度差時, 只需要讀取最後兩個 bit 就可以了
```c
static inline enum avl_node_balance avl_balance(const struct avl_node *node)
{
return (enum avl_node_balance)((node->parent_balance) & 3); // JJJJ
}
```
`static void avl_set_parent`:
此函式的用意在於給定父點的指標, 並且設定 current node 的 `parent_balance`, 已知 `parent_balance` 含:
1. 父點指標
2. 自身平衡狀態 (0,1,2)
參考以下兩位同學的作法
* avl_balance(node) [Ref](https://hackmd.io/@paul90317/linux2023q1-quiz3#%E6%B8%AC%E9%A9%97-2)
* node->parent_balance & 3 [Ref](https://hackmd.io/@Toneary/2023q1-Homework3-quiz3)
看到原本程式碼中的註解:
```c
* struct avl_node - node of an avl tree
* @parent: pointer to the parent node in the tree
* @balance: balance of the node
* @parent_balance: combination of @parent and @balance (lowest two bits)
* @left: pointer to the left child in the tree
* @right: pointer to the right child in the tree
```
一開始有點疑惑, 但後來才想到這個函式的用意是設定新的父點, 那原本 node 內的後 3 bit 代表的是原本的平衡狀態, 故結合新的 parent pointer 以及原本的平衡狀態就可以更新 `parent_balance` 了
前面的問題釐清後, 要解答 `static void avl_set_balance` 就簡單很多, 只要把 `parent_balance` 後 3 bit 設定成0 剩餘的就是地址了, 再結合 balance 就可以把 `parent_balance` 給設定好.
```c
static void avl_set_balance(struct avl_node *node,
enum avl_node_balance balance)
{
node->parent_balance = (node->parent_balance) & ~7 | balance;
}
```
接著, 看到 `void avl_insert_balance(struct avl_node *node, struct avl_root *root)`
他的描述是: 沿著樹往"上"走, 然後沿路 rebalance, 如果路上會遇到:
RL/RR/LR/LL 這四種情況就會進行調整.
在 `static inline void avl_insert` 中會呼叫到此函式
插入前, 先呼叫 `avl_link_node` 函式, 作用是將給定的 parent 連結到此點上面, 連結後相當於這棵樹多了一個 leaf, 再從這個葉子往上走到 root為止, 期間如果有遇到不平衡狀態就調整.
當插入後, 先判斷插入的點是左是右(因為要區分父點平衡狀態), 然後檢查父點的平衡狀態
* 當前節點是右邊的話, 則父點有三種情況
* 平衡
* ![](https://i.imgur.com/IP9czMV.png)
* 這樣就無須調整, 只須把父點的平衡設成右子樹比較高, 繼續往上檢查
* 左子樹比右子樹還高
* ![](https://i.imgur.com/58O24gS.png)
* 原本父點是左子樹比較高, 但是右邊插入一個點後, 父點變平衡
* 右子樹比左子樹還高
* 這種情況下, 是不可能發生在 leaf 的, 考慮已經往上爬了一點距離的情況
* 如果 node 平衡狀況是:
* 平衡
* ![](https://i.imgur.com/oIF4chI.png)
* 右子樹比較高
* ![](https://i.imgur.com/nurQasJ.png)
* 程式碼中並未定義這種情形, 原因是因為上面平衡的情況下就會先進行旋轉, 來避免LL/RR 的情況
* 左子樹比較高
* ![](https://i.imgur.com/wimPtic.png)
* 進行 RL 調整 `avl_rotate_rightleft(node, parent, root)`
釐清了以上觀念, 考慮插入節點是左子的情況:
* 當前節點是左邊的話, 則父點有三種情況
* 平衡
* ![](https://i.imgur.com/BVrbSko.png)
* 原本父點平衡, 插入左子後, 父點平衡變左子比右子高
* 左子樹比右子樹還高
* 同上面的結論, 這種情況不可能發生在 leaf 的, 考慮已經往上爬了一點距離的情況
* 如果 node 平衡狀況是:
* 平衡
* ![](https://i.imgur.com/fAhXsvU.png)
* 為了避免出現 RR 的情況, 會提前進行旋轉
* 左子樹比較高
* ![](https://i.imgur.com/Mw6O61x.png)
* 程式碼中並未定義這種情形, 原因是因為上面平衡的情況下就會先進行旋轉, 來避免LL/RR 的情況
* 右子樹比較高
* ![](https://i.imgur.com/kTDDX0c.png)
* LR 調整
* 右子樹比左子樹還高
* ![](https://i.imgur.com/rWSRQ7V.png)
* 這種情況下, 因為原本右子比左子還高, 插入左子後, 讓父點剛好變平衡
程式碼:
```c
void avl_insert_balance(struct avl_node *node, struct avl_root *root)
{
struct avl_node *parent;
/* go tree upwards and fix the nodes on the way */
while ((parent = avl_parent(node))) {
if (avl_is_right_child(node)) {
switch (avl_balance(parent)) {
default:
case AVL_RIGHT:
/* compensate double right balance by rotation
* and stop afterwards
*/
switch (avl_balance(node)) {
default:
case AVL_RIGHT:
case AVL_NEUTRAL:
avl_rotate_left(node, parent, root);
break;
case AVL_LEFT:
avl_rotate_rightleft(node, parent, root);
break;
}
parent = NULL;
break;
case AVL_NEUTRAL:
/* mark balance as right and continue upwards */
avl_set_balance(parent, AVL_RIGHT);
break;
case AVL_LEFT:
/* new right child + left leaning == balanced
* nothing to propagate upwards after that
*/
avl_set_balance(parent, AVL_NEUTRAL);
parent = NULL;
break;
}
} else {
switch (avl_balance(parent)) {
default:
case AVL_RIGHT:
/* new left child + right leaning == balanced
* nothing to propagate upwards after that
*/
avl_set_balance(parent, AVL_NEUTRAL);
parent = NULL;
break;
case AVL_NEUTRAL:
/* mark balance as left and continue upwards */
avl_set_balance(parent, AVL_LEFT);
break;
case AVL_LEFT:
/* compensate double left balance by rotation
* and stop afterwards
*/
switch (avl_balance(node)) {
default:
case AVL_LEFT:
case AVL_NEUTRAL:
avl_rotate_right(node, parent, root); // MMMM
break;
case AVL_RIGHT:
avl_rotate_leftright(node, parent, root); // NNNN
break;
}
parent = NULL;
break;
}
}
if (!parent)
break;
node = parent;
}
}
```