# 2024q1 Homework2 (quiz1+2)
contributed by < [ChenFuhuangKye](https://github.com/kyehuang) >
## 2024q1 第 1 週測驗題
### 測驗 `1`
> commit [60f92f8](https://github.com/kyehuang/linux_api/commit/60f92f8b0ae4577b87417bd2bfc2ec3438b23de8)
>
首先考慮 node_t 的結構,他利用 `next` 連接下一個 node, `left` 連接左邊的 node 以及 `right` 連接右邊的 node。
```graphviz
digraph structs {
node[shape=record];
struct0 [label="{<left>left|<right>right}|<next>next|<data>value",color=purple]
rankdir=LR;
node[shape=box];
}
```
在 `list_tail` 函數功能是要走到 list 的尾端,如下兩圖
```c
node_t *list_tail(node_t **left)
```
```graphviz
digraph structs {
node[shape=record];
struct0 [label="{<left>left|<right>right}|<next>next|<data>value",color=purple]
struct1 [label="{<left>left|<right>right}|<next>next|<data>value",color=purple]
struct2 [label="{<left>left|<right>right}|<next>next|<data>value",color=purple]
struct3 [label= "pointer"];
rankdir=LR;
node[shape=box];
struct3->struct0:n
struct0:next -> struct1:n [color=red];
struct1:next -> struct2:n [color=red];
}
```
```graphviz
digraph structs {
node[shape=record];
struct3 [label= "pointer"];
struct0 [label="{<left>left|<right>right}|<next>next|<data>value",color=purple]
struct1 [label="{<left>left|<right>right}|<next>next|<data>value",color=purple]
struct2 [label="{<left>left|<right>right}|<next>next|<data>value",color=purple]
rankdir=LR;
node[shape=box];
struct3->struct1:n
struct0:next -> struct1:n [color=red];
struct1:next -> struct2:n [color=red];
}
```
因此需要利用 next 找下一個 node 位置直到尾端。
```diff
while ((*left) && (*left)->next)
- left = &(AAAA);
+ left = &((*left)->next);
```
`list_length` 是要計算出 list 的長度,如同 list_tail 一樣,計算走到尾端的次數即可。
```diff
while (*left) {
++n;
- left = &(BBBB);
+ left = &((*left)->next);
}
```
在 quick_sort 中,會先找到 pivot ,並且將 p 指向 pivot 的 next 再將其的 next 改成 null 。
```graphviz
digraph structs {
node[shape=record];
node[shape=box];
struct0 [label= "pivot"];
struct1 [label= "p"];
struct2 [label= "node1"];
struct3 [label= "node2"];
struct4 [label= "node3"];
struct5 [label= "node4"];
{ rank=same; struct2 -> struct3 }
{ rank=same; struct3 -> struct4 }
{ rank=same; struct4 -> struct5 }
struct0 -> struct2 [color=red];
}
```
```graphviz
digraph structs {
node[shape=record];
node[shape=box];
struct0 [label= "pivot"];
struct1 [label= "p"];
struct2 [label= "node1"];
struct3 [label= "node2"];
struct4 [label= "node3"];
struct5 [label= "node4"];
{ rank=same; struct3 -> struct4 }
{ rank=same; struct4 -> struct5 }
struct0 -> struct2 [color=red];
struct1 -> struct3 [color=red];
}
```
而 p 要跟前面的 list_tail 走過全部的 node 直到尾端,因此要改成這樣,讓 p 走到每一個 node直到尾部。
```diff
while (p) {
node_t *n = p;
- p = CCCC;
+ p = p->next;
list_add(n->value > value ? &right : &left, n);
}
```
找到 node 時會利用 list_add 將其分到 left 或是 right,假設 node2 的 value 比 node1 小,因此 node2 會分到 left。
```graphviz
digraph structs {
node[shape=record];
node[shape=box];
struct0 [label= "pivot"];
struct1 [label= "p"];
struct2 [label= "node1"];
struct3 [label= "node2"];
struct4 [label= "node3"];
struct5 [label= "node4"];
left [label= "left"];
right [label= "right"];
{ rank=same; struct3 -> struct4 }
{ rank=same; struct4 -> struct5 }
struct0 -> struct2 [color=red];
struct1 -> struct3 [color=red];
}
```
```graphviz
digraph structs {
node[shape=record];
node[shape=box];
struct0 [label= "pivot"];
struct1 [label= "p"];
struct2 [label= "node1"];
struct3 [label= "node2"];
struct4 [label= "node3"];
struct5 [label= "node4"];
left [label= "left"];
right [label= "right"];
{ rank=same; struct4 -> struct5 }
struct0 -> struct2 [color=red];
struct1 -> struct4 [color=red];
left -> struct3 [color=red]
}
```
當 p 走完全部的 node 時,會把除了 pivot 指向的 node,分配到 left 及 right,如下圖。
```graphviz
digraph structs {
node[shape=record];
node[shape=box];
struct0 [label= "pivot"];
struct1 [label= "p"];
struct2 [label= "node1"];
struct3 [label= "node2"];
struct4 [label= "node3"];
struct5 [label= "node4"];
left [label= "left"];
right [label= "right"];
struct0 -> struct2 [color=red];
right -> struct4 [color=red];
left -> struct3 [color=red];
{ rank=same; struct3 -> struct5 }
}
```
接著再存入 begin 及 end 陣列中,在陣列中如果其 index 相同代表其為同一個鏈結, begin 為鏈結頭部而 end 為鏈結尾端。如下兩圖。
> 圖一 切分前。
```graphviz
digraph structs {
node[shape=record];
end [label="{end |<0>0}", color=purple];
begin [label="{begin |<0>0}", color=purple];
node[shape=box];
struct0 [label= "pivot"];
// struct1 [label= "p"];
struct2 [label= "node1"];
struct3 [label= "node2"];
struct4 [label= "node3"];
struct5 [label= "node4"];
{ rank=same; struct2 -> struct3 }
{ rank=same; struct3 -> struct4 }
{ rank=same; struct4 -> struct5 }
struct0 -> struct2 [color=red];
// struct1 -> struct3 [color=red];
end:0 -> struct2
begin:0 -> struct5
}
```
> 圖二 切分後
```graphviz
digraph structs {
node[shape=record];
end [label="{end |<0>i|<1>i+1|<2>i+2}", color=purple];
begin [label="{begin |<0>i|<1>i+1|<2>i+2}", color=purple];
node[shape=box];
struct3 [label= "node2"];
struct5 [label= "node4"];
struct4 [label= "node3"];
struct2 [label= "node1"];
{ rank=same; struct3 -> struct5 }
end:0 -> struct3
begin:0 -> struct5
end:1 -> struct2
begin:1 -> struct2
end:2 -> struct4
begin:2 -> struct4
}
```
左邊鏈結的開頭會存在 begin 中的第 i 個位置, 中間鏈結的開頭會在 begin 中的第 i+1 個位置, 右邊鏈結的開頭會在 begin 中的第 i+2 個位置
所以 end 要存的是每條鏈結的尾端,可以利用 list_tail 找到尾端。
```diff
begin[i] = left;
- end[i] = DDDD;
+ end[i] = list_tail(&left);
begin[i + 1] = pivot;
end[i + 1] = pivot;
begin[i + 2] = right;
- end[i + 2] = EEEE;
+ end[i + 2] = list_tail(&right);
```
### 測驗 `2`
> commit [6ea4c9e](https://github.com/ChenFuhuangKye/linux_api/commit/6ea4c9e6fd64ae6ba09be7936d6371215c3a2b2d)
在 `merge` 中, tail 會隨著 node 插入指向下一個位置。
因此 tail 的初始質會是 head 的位置, tail 是間接指標,所以要用 & 來取址。
```diff
struct list_head *head;
- struct list_head **tail = AAAA;
+ struct list_head **tail = &head;
```
在比較過後,需要將插入的節點存入 tail 中,接著 tail 需要移動到下一個位置,以存入下一個節點。
```diff
for (;;) {
/* if equal, take 'a' -- important for sort stability */
if (cmp(priv, a, b) <= 0) {
*tail = a;
- tail = BBBB;
+ tail = &a->next;
a = a->next;
if (!a) {
*tail = b;
break;
}
} else {
*tail = b;
- tail = CCCC;
+ tail = &b->next;
b = b->next;
if (!b) {
*tail = a;
break;
}
}
}
```
`build_prev_link` 是要建立一個環狀的鏈結。
```c
do {
list->prev = tail;
tail = list;
list = list->next;
} while (list);
```
這段先將 list 皆在 tail 後面,直到結束。
最後需要將頭尾接在一起。
```diff
- DDDD = head;
+ tail->next = head;
- EEEE = tail;
+ head->prev = tail;
```
在 `timsort` 中,在進行最後合併之前,如果推疊的大小為 0 或是 1 需要呼叫 `build_prev_link` 建立一個環狀的鏈結。
```diff
-if (stk_size <= FFFF) {
+if (stk_size <= 1) {
build_prev_link(head, head, stk0);
return;
}
```
![image](https://hackmd.io/_uploads/H17kNF3pa.png)
## 2024q1 第 2 週測驗題
### 測驗 `1`
`hlist_add_head` 是將新的節點插在在 head 後面。
因此新的節點後面要接 head 原本的節點。
```diff
-n->next = AAAA;
+n->next = h->first;
```
在 `find` 中,找到想要查詢的哈希值,在利用 `hlist_for_each` 遍歷,`hlist_for_each` 接的 head 要是指標,所以要加上取址 `&`。
下一行要找到 on 指向的位置,可以利用 `list_entry` 找到。
```diff
-hlist_for_each (p, BBBB) {
- struct order_node *on = CCCC(p, struct order_node, node);
+hlist_for_each (p, &heads[hash]) {
+ struct order_node *on = list_entry(p, struct order_node, node);
if (num == on->val)
return on->idx;
}
```
在 `node_add` 中呼叫 `hlist_add_head` 將節點插在指定哈希值 head 的後面。
```diff
int hash = (val < 0 ? -val : val) % size;
- hlist_add_head(&on->node, DDDD);
+ hlist_add_head(&on->node, &heads[hash]);
```
### 測驗 `2`
`hlist_del` 是刪除指定節點,這邊要注意的是 pprev ,是指向上一個節點的 next 位置。因此 `*pprev = next;`這行已經指定節點前一個節點指向下一個節點,但沒有考慮到下一個節點指回前一個節點,因此當有下一個節點時,需要接回去。
```diff
struct hlist_node *next = n->next, **pprev = n->pprev;
*pprev = next;
if (next)
- EEEE = pprev;
+ next->pprev = pprev;
```
在 `lRUCacheFree` 中,想要找到 LRUNode 節點。
首先考慮 LRUNode 的資料結構。
```c
typedef struct {
int key;
int value;
struct hlist_node node;
struct list_head link;
} LRUNode;
```
由於 pos 的資料結構是 list_head ,所以其 member 為 link。
找到節點之後,想要將該節點刪除,先利用 `list_del` 從鏈結剔除,再把空間釋放。由於 LRUNode 中的 link 不是指標,因此要加上取址符號。
```diff
list_for_each_safe (pos, n, &obj->dhead) {
- LRUNode *cache = list_entry(pos, LRUNode, FFFF);
- list_del(GGGG);
+ LRUNode *cache = list_entry(pos, LRUNode, link);
+ list_del(&cache->link);
free(cache);
}
```
在 `lRUCacheGet`中,也想要找 LRUNode 節點。
但是這邊的 pos 的資料結構是 hlist_node ,所以其 member 為 node。找到適合的節點後,要移動到 head 後面,由於 LRUNode 中的 link 不是指標,因此要加上取址符號。
```diff
hlist_for_each (pos, &obj->hhead[hash]) {
- LRUNode *cache = list_entry(pos, LRUNode, HHHH);
+ LRUNode *cache = list_entry(pos, LRUNode, node);
if (cache->key == key) {
- list_move(IIII, &obj->dhead);
+ list_move(&cache->link, &obj->dhead);
return cache->value;
}
}
```
最後在 `lRUCachePut` 也想要找到節點並移動適合的節點到 head 後方,如同 `lRUCacheGet` 作法一樣。
```diff
hlist_for_each (pos, &obj->hhead[hash]) {
- LRUNode *c = list_entry(pos, LRUNode, JJJJ);
+ LRUNode *c = list_entry(pos, LRUNode, node);
if (c->key == key) {
- list_move(KKKK, &obj->dhead);
+ list_move(&c->link, &obj->dhead);
cache = c;
}
}
```
### 測驗 `3`
`__ffs` 是利用二元搜尋法,尋找 word 中第一個為 1 的位。
如果 word 前 32 位有 1,但是後 32 接為 0 ,才需要將 word 往右移動 32 位。 因此 word 需要跟 32 個 1 做 and 運算。
```diff
-if ((word & AAAA) == 0) {
+if ((word & 0xffffffff) == 0) {
num += 32;
word >>= 32;
}
```
`__clear_bit` 是將 word 中指定的位元設成 0 。
先產生一個 mask 並且其指定的位元設成 1 ,由於要將目標值的址定位元設為 0 ,因此取 ~mask 與目標值做 and 運算。
```diff
- *p &= BBBB;
+ *p &= ~mask;
```
在 `FIND_NTH_BIT` 中,為了避免使用的有效位元之外的數字
```diff
-if (sz CCCC BITS_PER_LONG)
+if (sz % BITS_PER_LONG)
tmp = (FETCH) & BITMAP_LAST_WORD_MASK(sz);
```