# 2024q1 Homework2 (quiz1+2)
contributed by < nelson0720j >
## 第 1 周測驗題
### 測驗一
#### 解析
node_t 結構
```clike
typedef struct __node {
struct __node *left, *right;
struct __node *next;
long value;
} node_t;
```
```graphviz
digraph node_t {
node [shape = record]
struct1 [label = "left|value|right|<next>next"];
struct2 [label = "left|value|right|<next>next"];
struct1:next -> struct2
}
```
#### 解題
1. list_tail
```clike
node_t *list_tail(node_t **left)
{
while ((*left) && (*left)->next)
left = &(AAAA); // *left->next
return *left;
}
```
目標: 找出鏈結串列的最後一個節點,並輸出指向他的指標
`*left` 不斷指向他的 `next` 所指向的節點,直到這個節點不存在為止。
2. list_length
```clike
int list_length(node_t **left)
{
int n = 0;
while (*left) {
++n;
left = &(BBBB); // (*left)->next
}
return n;
}
```
同上述,不斷更新 `*left` 所指向的節點,每往下移長度就加一。
3. iterative Quicksort
```clike
void quick_sort(node_t **list)
{
int n = list_length(list);
int value;
int i = 0;
int max_level = 2 * n;
node_t *begin[max_level], *end[max_level];
node_t *result = NULL, *left = NULL, *right = NULL;
begin[0] = *list;
end[0] = list_tail(list);
while (i >= 0) {
node_t *L = begin[i], *R = end[i];
if (L != R) {
node_t *pivot = L;
value = pivot->value;
node_t *p = pivot->next;
pivot->next = NULL;
while (p) {
node_t *n = p;
p = CCCC; // p->next
list_add(n->value > value ? &right : &left, n);
}
begin[i] = left;
end[i] = DDDD; // list_tail(&left)
begin[i + 1] = pivot;
end[i + 1] = pivot;
begin[i + 2] = right;
end[i + 2] = EEEE; // list_tail(&right)
left = right = NULL;
i += 2;
} else {
if (L)
list_add(&result, L);
i--;
}
}
*list = result;
}
```
首先, `pivot` 設為最左邊的點, `p` 為 `pivot` 的下一個節點。依序看 `p` 的 `vlaue` 是否比 `pivot` 的 `value` 還大, 若大於則加到 `right` 的鏈結串列中,反之,則加到 `left` 的鏈結串列中。
接下來,會分成三個鏈結串列。begin[0] 及 end[0] 紀錄 left 頭及尾端節點, begin[1] 及 end[1] 紀錄當前 pivot 節點,begin[2] 及 end[2] 紀錄 right 頭及尾端節點。
最後,迭代操作,直到分開的三個鏈結串列都只有一個節點,依序將節點加入到 `result` 。
### 測驗二
Timsort
#### 解析
#### 解題
1. merge
merge之實作
```clike
static struct list_head *merge(void *priv,
list_cmp_func_t cmp,
struct list_head *a,
struct list_head *b)
{
struct list_head *head;
struct list_head **tail = AAAA; // &head
for (;;) {
/* if equal, take 'a' -- important for sort stability */
if (cmp(priv, a, b) <= 0) {
*tail = a;
tail = BBBB; // &a->next
a = a->next;
if (!a) {
*tail = b;
break;
}
} else {
*tail = b;
tail = CCCC; // &b->next
b = b->next;
if (!b) {
*tail = a;
break;
}
}
}
return head;
}
```
將 `tail` 指向 `head` 來達成首尾相接
```graphviz
digraph G {
node [shape=box];
rankdir = LR
head [label="struct list_head *head"];
tail [label="struct list_head **tail"];
tail->head;
head->"";
}
```
接著,比較 a 和 b 所指向節點的值,若 a 小於或等於 b ,則將 a 加入 `head` 所指向的新佇列,反之,則將 b 加入。
2. build_prev_link
建立雙向循環鏈表的前向和後向鏈接。
```clike
static void build_prev_link(struct list_head *head,
struct list_head *tail,
struct list_head *list)
{
tail->next = list;
do {
list->prev = tail;
tail = list;
list = list->next;
} while (list);
/* The final links to make a circular doubly-linked list */
DDDD = head; // tail->next
EEEE = tail; // head->prev
}
```
```graphviz
digraph G {
node [shape=box];
rankdir = LR
head [label="struct list_head *head"];
tail [label="struct list_head *tail"];
list [label="struct list_head *list"];
tail -> list; // tail->next = list
list -> tail; // list->prev = tail
list:se -> head:sw;
head:nw -> list:e;
tail -> head; // Final circular links
head -> tail;
}
```
3. timsort
```clike
void timsort(void *priv, struct list_head *head, list_cmp_func_t cmp)
{
stk_size = 0;
struct list_head *list = head->next, *tp = NULL;
if (head == head->prev)
return;
/* Convert to a null-terminated singly-linked list. */
head->prev->next = NULL;
do {
/* Find next run */
struct pair result = find_run(priv, list, cmp);
result.head->prev = tp;
tp = result.head;
list = result.next;
stk_size++;
tp = merge_collapse(priv, cmp, tp);
} while (list);
/* End of input; merge together all the runs. */
tp = merge_force_collapse(priv, cmp, tp);
/* The final merge; rebuild prev links */
struct list_head *stk0 = tp, *stk1 = stk0->prev;
while (stk1 && stk1->prev)
stk0 = stk0->prev, stk1 = stk1->prev;
if (stk_size <= FFFF) { // 1
build_prev_link(head, head, stk0);
return;
}
merge_final(priv, cmp, head, stk1, stk0);
}
```
`find_run` 找出適合的遞增 run ,再將 `tp` 指向當前 run 的頭節點。
`merge_collapse` 執行合併操作,合併最小的兩個 run 。
`merge_force_collapse` 會持續合併,直到 stk_size < 3 (也就是小於 3 個 run ),如果有 1 個 run ,則 `build_prev_link` , 2 個則 `merge_final`。
## 第 2 周測驗題
### 測驗一
#### 結構
```clke
struct hlist_node {
struct hlist_node *next, **pprev;
};
```
[Linux 核心的 hash table 實作](/rpIi8FeqQ9eBJ-UTFgb--A)
`hlist_node` 結構為一個雙向鏈結串列, `next` 指標指向下一個節點本身, `pprev` 為指向指標的指標,存 `&next` 指向前一個節點的 `next` 指標
```graphviz
digraph node_t {
node[shape="record"];
rankdir=LR;
splines=false;
list_head[label="<h>list_head|<f>first"];
hlist_node1[label="<n1>node1|{<p>pprev|<n>next}"]
hlist_node2[label="<n2>node2|{<p>pprev|<n>next}"]
NULL [shape= plaintext]
{rank = "min" list_head}
list_head -> hlist_node1-> hlist_node2[
weight = 10, style=invis
]
list_head:f->hlist_node1:n1;
hlist_node1:p->list_head:f;
hlist_node1:n->hlist_node2:n2;
hlist_node2:f->hlist_node1:n;
hlist_node2:n->NULL;
}
```
#### 測驗
1.
```clike
static inline void hlist_add_head(struct hlist_node *n, struct hlist_head *h)
{
if (h->first)
h->first->pprev = &n->next;
n->next = AAAA; //h->first
n->pprev = &h->first;
h->first = n;
}
```
目標為: 將新節點 `n` 加到串列前面。
先檢查 `h` 的成員 `first` 是否非空,若為空,將原先 `h` 的 `pprev` 所指向的節點( `node2` )的 `pprev` 指向要加入的 `n` 的 `next` 。
```graphviz
digraph node_t {
node[shape="record"];
rankdir=LR;
splines=false;
list_head[label="<h>h|<f>first"];
hlist_node1[label="<n1>n|{<p>prv|<n>next}"]
hlist_node2[label="<n2>node2|{<p>prv|<n>next}"]
NULL [shape= plaintext]
{rank = "min" list_head}
list_head -> hlist_node1-> hlist_node2[
weight = 10, style=invis
]
hlist_node2:p->hlist_node1:n[color="red"];
hlist_node2:n->NULL;
}
```
再將 `n` 的 `next` 指向 `h` 的 `first` 原先指向的節點,即可完成 `n` 和原先第一個節點 `h->first` 的雙向鏈結。
```graphviz
digraph node_t {
node[shape="record"];
rankdir=LR;
splines=false;
list_head[label="<h>h|<f>first"];
hlist_node1[label="<n1>n|{<p>pprev|<n>next}"]
hlist_node2[label="<n2>node2|{<p>pprev|<n>next}"]
NULL [shape= plaintext]
{rank = "min" list_head}
list_head -> hlist_node1-> hlist_node2[
weight = 10, style=invis
]
hlist_node2:p->hlist_node1:n;
hlist_node1:n->hlist_node2:n2[color="red"];
hlist_node2:n->NULL;
}
```
因此 `AAAA` 為 `h->first` 。
2.
```clile
static int find(int num, int size, const struct hlist_head *heads)
{
struct hlist_node *p;
int hash = (num < 0 ? -num : num) % size;
hlist_for_each (p, BBBB) {
struct order_node *on = CCCC(p, struct order_node, node);
if (num == on->val)
return on->idx;
}
return -1;
}
```
此函式功用為找尋串列中是否有結構的值和參數 `num` 一樣,若有則回傳此節點的 `index` 。
`hlist_for_each` 傳入根據 mod 後的結構位址,因此 `BBBB` 為 `&head[hash]`。
接著要取得這個結構的值來比對,這裡用一個結構 `order_node` 來取得值和索引,透過傳入結構,指標和成員來取得這個結構 `order_node` 的位址。
因此 `CCCC` 是 `list_entry` 。
3.
```clike
static inline void node_add(int val,
int idx,
int size,
struct hlist_head *heads)
{
struct order_node *on = malloc(sizeof(*on));
on->val = val;
on->idx = idx;
int hash = (val < 0 ? -val : val) % size;
hlist_add_head(&on->node, DDDD);
}
```
`hlist_add_head` 他需要的參數為 `hlist_head *` ,因此根據 `hash` ,傳入目標位址。
`DDDD` 為 `&heads[hash]` 。
#### 延伸問題
* Linux 核心的 cgroups 相關原始程式碼
根據 [kernel.docs](https://docs.kernel.org/admin-guide/cgroup-v1/cgroups.html)
>Control Groups provide a mechanism for aggregating/partitioning sets of tasks, and all their future children, into hierarchical groups with specialized behaviour.
[cgroups](https://github.com/torvalds/linux/blob/005f6f34bd47eaa61d939a2727fc648e687b84c1/include/linux/cgroup.h#L240) 作用為限制,控制與隔離 Process 所使用到的系統資源。
cgroup 內可以有多個 subsystems ,各個 subsystem 分別對 cpu, memory, network 等去進行控制。</br>
cgroup 可以以階層結構 ( Hierarchical Organization ) 的方式組織和管理,其子節點預設繼承父節點的屬性,形成一個 forest 的結構。
更多請見 [cgroup v1 和 v2 介紹](https://medium.com/starbugs/%E7%AC%AC%E4%B8%80%E5%8D%83%E9%9B%B6%E4%B8%80%E7%AF%87%E7%9A%84-cgroups-%E4%BB%8B%E7%B4%B9-a1c5005be88c) , [v2 kernel.docs](https://docs.kernel.org/admin-guide/cgroup-v2.html) 。
在此先觀察 cgroup v1 的 pre-order walk 程式碼,
```clike
#define css_for_each_descendant_pre(pos, css) \
for ((pos) = css_next_descendant_pre(NULL, (css)); (pos); \
(pos) = css_next_descendant_pre((pos), (css)))
```
上述程式碼會用 pre-order 的方式走過 css ( cgroup subsystem ) 的後代,並且提到會在依序走過樹的時候使用 `cu_read_lock()` 的函式作同步控制,確保狀態更新的一致。
另外,在 [`cgroup-defs.h`](https://github.com/torvalds/linux/blob/005f6f34bd47eaa61d939a2727fc648e687b84c1/include/linux/cgroup-defs.h) 中的 `struct cgroup_subsys_state` 有提到保存 css 狀態的結構,方便管理整個樹狀的結構。
### 測驗二
LRU可能的合法 C 程式實作
1.
```clike
void hlist_del(struct hlist_node *n)
{
struct hlist_node *next = n->next, **pprev = n->pprev;
*pprev = next;
if (next)
EEEE = pprev;
}
```
解釋:
`**pprev = n->pprev` 為將 `pprev` 指標指向 `n` 的 `pprev` 指標所指向的 `next` 指標,即 `pprev` 存 `&(n->pprev)` 。
解題:
先將 `n` 節點的前一個節點的 `next` 指向 `n` 的下一個節點(即 `n->next` ) 。
接著將 `n` 的下一個節點的 `pprev` 指向 `n` 的前一個節點的 `next` ,即可跳過節點 `n` ,達成類似刪除節點 `n` 的效果(實際上 `n` 節點仍存在)。
`EEEE` 為 `next->pprev` 。
2.
```clike
void lRUCacheFree(LRUCache *obj)
{
struct list_head *pos, *n;
list_for_each_safe (pos, n, &obj->dhead) {
LRUNode *cache = list_entry(pos, LRUNode, FFFF);
list_del(GGGG);
free(cache);
}
free(obj);
}
```
`list_entry` 的第三個參數是第二個參數型態的成員,因此根據以下 `LRUNode` 結構,可能成員為 `node` 或 `link`
```clike
typedef struct {
int key;
int value;
struct hlist_node node;
struct list_head link;
} LRUNode;
```
且根據 `struct list_head *pos` pos指標需要存一個 `list_head` 型別,
因此 `FFFF` 為 `link`。
找到結構位址後,用 `list_del()` 刪除結構,參數為 `struct list_head *entry` ,因此 `GGGG` 為 `&cache->link` 。
最後再釋放 `cache` 所指向的整個結構。
3.
```clike
int lRUCacheGet(LRUCache *obj, int key)
{
int hash = key % obj->capacity;
struct hlist_node *pos;
hlist_for_each (pos, &obj->hhead[hash]) {
LRUNode *cache = list_entry(pos, LRUNode, HHHH);
if (cache->key == key) {
list_move(IIII, &obj->dhead);
return cache->value;
}
}
return -1;
}
```
解釋:
函式功能為獲取指定 key 的 value,並將最近使用過的節點,也就是符合 `key` 值的點,透過 `list_move` 移動到串列頭,以此代表最近使用過,來實現 LRU 。
解題:
`pos` 為 `hlist_node *` 型別,因此 `HHHH` 為 `node` 。
`void list_move(struct list_head *entry, struct list_head *head)` 需傳入 `list_head` 型別,因此 `IIII` 為 `&cache->link`。
4.
```clike
void lRUCachePut(LRUCache *obj, int key, int value)
{
LRUNode *cache = NULL;
int hash = key % obj->capacity;
struct hlist_node *pos;
hlist_for_each (pos, &obj->hhead[hash]) {
LRUNode *c = list_entry(pos, LRUNode, JJJJ);
if (c->key == key) {
list_move(KKKK, &obj->dhead);
cache = c;
}
}
#以下省略
}
```
這個函式用於將一個鍵值對放入LRU快取中。根據LRU快取的特性,如果快取已滿且新的鍵值對要放入,則應該淘汰最近最少使用的鍵值對。
`JJJJ` 為 `node` ,`KKKK` 為 `&c->link` 。
#### 延伸問題
1. 解釋程式碼
結構:
```graphviz
digraph G {
rankdir=LR;
node[shape=record];
subgraph cluster_0 {
node1 [label="capacity|count|{dhead|<h>hhead[]}"];
label = "LRUCache";
}
map [label="<f>hlist_head.first |<ht0> |<ht1> |<ht2> |<ht3> |<ht4> |<ht5> |<ht7> |<ht8> "];
node[shape=none]
null1 [label=NULL]
null2 [label=NULL]
subgraph cluster_1 {
style=filled;
color=lightgrey;
node [shape=record];
hn1 [label="hlist_node | {<prev>pprev | <next>next}"];
label="hash_key 1"
}
subgraph cluster_2 {
style=filled;
color=lightgrey;
node [shape=record];
hn2 [label="hlist_node | {<prev>pprev | <next>next}"];
label="hash_key 2"
}
subgraph cluster_3 {
style=filled;
color=lightgrey;
node [shape=record];
hn3 [label="hlist_node | {<prev>pprev | <next>next}"];
label="hash_key 3"
}
map:ht1 -> hn1
hn1:next -> hn2
hn2:next -> null1
hn2:prev:s -> hn1:next:s
map:ht5 -> hn3
hn3:next -> null2
hn1:prev:s -> map:ht1
hn3:prev:s -> map:ht5
node1:h->map:f
}
```
2. 在 Linux 核心找出 LRU 相關程式碼並探討
[lru_cache](https://github.com/torvalds/linux/blob/master/lib/lru_cache.c)
`struct lc_element`: 代表快取中的一個元素。
`struct lru_cache`: 代表整個 LRU 快取。
`lc_create`: 用於初始化 LRU 快取。
`lc_get`: 通過標籤獲取元素,並可能改變活動集。
`lc_put`: 減少元素的引用計數,並在引用計數為零時將其移到 LRU 列表中。
### 測驗三
## 參考資料
[作業要求](https://hackmd.io/@sysprog/BkmmEQCn6)