owned this note
owned this note
Published
Linked with GitHub
---
tags: linux2023
---
# 2023q1 Homework1 (quiz1)
contributed by < `as200188` >
## 第一題
參考 [list.h](https://github.com/sysprog21/lab0-c/blob/master/list.h)
### static void list_sort(struct list_head *head)運作原理
若 head 為空或只有一個節點,則不需處理,直接 return。
宣告 list_less 和 list_greater 用來指向小於 pivot 的多個節點和大於 pivot 的多個節點。
取 list 第一個節點作為 pivot。然後將該 pivot 從 list 中移除。
```
list_for_each_entry_safe(itm, is, head, list) {
if (cmpint(&itm->i, &pivot->i) < 0)
list_move(&itm->list, &list_less);
else
list_move(&itm->list, &list_greater);
}
```
逐一走訪 list 並將小於 pivot 值的節點先從原 list 上移除,再將節點移到 list_less 的第一個節點,大於等於 pivot 值的,先從原 list 上移除,再將節點移到 list_greater 的第一個節點。
```
list_sort(&list_less);
list_sort(&list_greater);
```
遞迴處理小於 pivot 的 list 和 大於等於 pivot 的 list。
```
list_add(&pivot->list, head);
list_splice(&list_less, head);
list_splice_tail(&list_greater, head);
```
將 pivot 加到 list 的第一個節點。
此時的 list_less 和 lis_greater 是已排序好的,將 list_less 串接到 list 的頭,list_greater 串接到 list 的尾巴,即可得到已排序的 list。
## 第二題
參考 [list.h](https://github.com/sysprog21/lab0-c/blob/master/list.h)
### list_sort_nr(struct list_head *head)運作原理
:::warning
解釋程式碼原理時,不用重複原本的程式碼 (之後隨堂測驗的程式碼規模會達到數百行),只要列出關鍵程式碼。
:notes: jserv
:::
一開始先判斷傳進來的指標是否為空或只有一個節點,若是則不需處理,直接 return。
宣告一個大小為 512 個節點的 stack,並做初始化,每個節點的 prev 和 next 都指向自己。
```list_splice_init(head, &stack[++top]);```
讓 stack[0] 裡面放的 head 指向 head 所指向的 list,然後初始化 head(即第一個 argument)
:::danger
注意作業書寫規範,中英文間用一個半形空白字元區隔。
:notes: jserv
:::
假設要處理的 list 為 2->1->3。
```
struct list_head partition;
INIT_LIST_HEAD(&partition);
```
宣告 partition 節點並初始化。
此處可優化成 ```LIST_HEAD(partition)```
top 現在為 0 開始進入迴圈,先將 partition 做初始化。
```list_splice_init(&stack[top--], &partition);```
讓 partition 指向 stack[0] 裡面放的 head 所指向的 list,然後初始化 stack[0] 裡面放的 head,最後 top - 1, 現在 top 為 -1。
``` if (!list_empty(&partition) && !list_is_singular(&partition)) ```
若 partition 指向的 list 為非空且非只有一個節點,則條件成立。
```struct list_head list_less, list_greater;```
宣告兩個節點變數,list_less 用來指向比 pivot 小的 list,list_greater 用來指向比 pivot 大的 list。
宣告後都先做初始化。
```struct item *pivot = list_first_entry(&partition, struct item, list);```
list_first_entry(head, type, member)
現在 partition 是指向 list 的第一個節點,所以作為第一個 argument,而 list 由多個 item 節點串接而成,須利用 container_of 來計算 item 節點的位址,所以要傳入母結構和成員,作為第二個和第三個 argument,最後得到 list 上的第一個節點,並回傳該節點的位址。
```list_del(&pivot->list);```
將 pivot 節點從 list 中移除。
現在 partition 為指向舊 list 的第二個節點, 即指向新 list 的第一個節點。
```INIT_LIST_HEAD(&pivot->list);```
初始化 pivot 節點的 list 成員。
```
struct item *itm = NULL, *is = NULL;
list_for_each_entry_safe(itm, is, &partition, list){
list_del(&itm->list);
if (cmpint(&itm->i, &pivot->i) < 0)
list_move(&itm->list, &list_less);
else
list_move(&itm->list, &list_greater);
}
```
```list_for_each_entry_safe(itm, is, &partition, list)```
itm 為 list 的 iterator,用來逐一走訪 list,is 為用來存放下一個節點的資訊。逐一走訪時,只能刪除 iterator 指向的節點。
比較 pivot 節點上的值 和 iterator 指向的節點上的值,若 iterator 指向的節點的值比 pivot 小,則將該節點從原 list 上移除,並加到 list_less 的頭,若值比較大,則加到 list_greater 的頭。
此段程式碼有可優化的地方,從 list_move 的實做可發現,該實做會先做 list_del 將節點從 list 上移除,再將該節點加到指定的 list 的頭。也就是說,在比較值的上一行:```list_del(&itm->list);```是可以移除的。
```list_move_tail(&pivot->list, &list_less);```
將 pivot 加到 list_less 的尾巴。
當前 top 為 -1,stack[0] 已經過初始化(已在 list_splice_init 時初始化了)。
```
if (!list_empty(&list_greater))
list_splice_tail(&list_greater, &stack[++top]);
```
若值較大的 list 非空,則將該 list_greater 串接到 stack[0] 的尾巴。
```
if (!list_empty(&list_less))
list_splice_tail(&list_less, &stack[++top]);
```
若值較小的 list 非空,則將該 list_less 串接到 stack[1] 的尾巴。
回到 while 判斷式,現在 top 為 1,符合條件,進入迴圈。
初始化 partition 後,將 stack[1] 串接到 partition 的頭(即 partition 指向 stack[1] 所指向的地方,該指向的地方為 list 的第一個 item),最後 top - 1,現在 top 為 0,stack[0]為 list_greater,stack[1]為 list_less,partition 為 list_less。
現在
top: 0
stack[0] list_greater: 3
stack[1] list_less: 1->2
若 partition 為非空且非單一節點,則進入 if 讓 partition 指向的 list_less 上的節點與 pivot 做比較,然後分割,並放入 stack。
pivot 為 1,最後將 pivot 加到 list_less 的尾巴。
現在
top: 2
stack[0] list_greater: 3
stack[1] list_greater: 2
stack[2] list_less: 1
回到 while 判斷式,現在 top 為 2,符合條件,進入迴圈。
初始化 partition 後,將 stack[2] 串接到 partition 的頭,然後 top - 1。
現在 top 為 1。
因 partition 指向的 list 只有一個節點,不會進入 if 。
進入else。
```
top++;
```
top + 1 後為 2。
現在
top: 2
stack[0]: list_greater: 3
stack[1]: list_greater: 2
stack[2]: 已初始化
partition: list_less: 1
```
list_splice_tail(&partition, &stack[top]);
```
將 partition 加到 stack[2] 的尾巴。
現在
top: 2
stack[0]: list_greater: 3
stack[1]: list_greater: 2
stack[2]: list_less: 1
partition: list_less: 1
```
while (top >= 0 && list_is_singular(&stack[top])) {
struct item *tmp = list_first_entry(&stack[top], struct item, list);
list_del(&tmp->list);
INIT_LIST_HEAD(&stack[top--]);
list_add_tail(&tmp->list, head);
}
```
當 top >= 0 且 stack[top] 裡面只有一個節點時,將該節點加到 head 的尾巴。並初始化stack[top] 然後將 top - 1。
這段程式可優化成:
```
while (top >= 0 && list_is_singular(&stack[top])) {
struct item *tmp = list_first_entry(&stack[top], struct item, list);
INIT_LIST_HEAD(&stack[top--]);
list_move_tail(&tmp->list, head);
}
```
結論: 當 stack[top] 只有一個節點時,會是當前 list 裡面的最小值,stack[top] 下面都會是 list_greater,所以每次當頂端只有一個節點時,就將節點加到 head 的尾巴,最後就可得到已排序的 list。如果在處理 stack[top] 中途遇到非單一節點時,就回到 if 將其 list 拆開放入 stack,目標是讓 stack[top] 為單一節點和當前最小值。
## 第三題
參考 [XOR linked list](https://en.wikipedia.org/wiki/XOR_linked_list)
### int xorlist_add(xor_list_t *list, xor_node_t *n) 運作原理
```
for (int i = 0; i < 1000; i++) {
struct test_node *new = malloc(sizeof(struct test_node));
xornode_init(&new->xornode);
new->value = i;
xorlist_add(&list, &new->xornode);
if (i == 5)
p1 = &new->xornode;
if (i == 6)
p2 = &new->xornode;
}
```
list 經過初始化後,
list :
head: {cmp: 指向 list 的 tail}
tail: {cmp: 指向 list 的 head}
count: 0
建立一個 xor_list,每次將初始化後的節點加到 list 的第一個節點,做 1000 次。
```
int xorlist_add(xor_list_t *list, xor_node_t *n)
```
首先是第一次插入節點。
```
xor_node_t *real_prev = &list->head;
xor_node_t *node = real_prev->cmp;
```
real_prev: 指向 list 的 head。
node: 指向 head 的 cmp 所指向的地方,即指向 list 的 tail。
```
if (node == &list->tail)
real_next = &list->tail;
else
real_next = node;
```
條件成立,real_next 為指向 list->tail。
```
real_prev->cmp = n;
```
real_prev 現在是指向 list 的 head,也就是 list 的 head 的 cmp 指向 n 所指向的地方,該段程式會將 n 指向的節點加到 list 的最前端。
令 A 是 address of list head
令 B 是 address of list tail
令 C 是 第一個節點的位址
現在
list :
head: {cmp: C}
tail: {cmp: A}
real_prev: 指向 list 的 head
real_next: 指向 list 的 tail
```
n->cmp = XOR_COMP(real_prev, real_next);
```
計算 n 指向的節點的壓縮位址(cmp),將該節點前面節點的 address 和後面節點的 address 做 XOR。
該節點的壓縮位址為 $(A \oplus B)$
```
real_next->cmp = XOR_COMP(n, XOR_COMP(real_prev, real_next->cmp));
```
計算 list tail 的壓縮位址: $C\oplus(A \oplus A)=C$
現在
list:
head: {cmp: C}
tail: {cmp: C}
第一個節點位址: C
cmp: $(A \oplus B)$
驗算:
list 目前只有一個節點
前一個節點位址(head 的位址):A
第一個節點的壓縮位址:$(A \oplus B)$
計算下一個節點的位址:
$A\oplus(A \oplus B)=B$ 得到 B 便可前往尾巴,會走到 tail。
下一步:
尾巴前面的位址: C
尾巴的壓縮位址: C
計算下一個位址: $(C \oplus C)=0$
得到 0 便可知道已走到尾巴。
可以發現該 list 雖然只有一個存放資料的節點,但該節點的壓縮位址需要 head 和 tail 的位址做 XOR 計算,而且需要走到 tail 才能計算出下一個位址是 0
再度進入迴圈,加入新節點
現在
list:
head: {cmp: C}
tail: {cmp: C}
第一個節點位址: C
cmp: $(A \oplus B)$
```
xor_node_t *real_prev = &list->head;
xor_node_t *node = real_prev->cmp;
```
執行完後
real_prev: A
node: C
```
if (node == &list->tail)
real_next = n;
else
real_next = node;
```
條件不成立,real_next 為第一個節點(address: C)
```
real_prev->cmp = n;
```
假設新節點的位址為 D
將新的節點加到 list 的最前端
現在
list:
head: {cmp: D}
tail: {cmp: C}
第一個節點位址: D
cmp: NULL
第二個節點位址: C
cmp: $(A \oplus B)$
real_prev: A
real_next: C
```
n->cmp = XOR_COMP(real_prev, real_next);
```
計算 n 的壓縮位址為$(A \oplus C)$
因為新節點是加到 list 的第一個節點(head 後面),所以前面是 head 後面是原本的第二個節點(address:C),壓縮位址為前後位址做 XOR 計算。
現在
list:
head: {cmp: D}
tail: {cmp: C}
第一個節點位址: D
cmp: $(A \oplus C)$
第二個節點位址: C
cmp: $(A \oplus B)$
```
real_next->cmp = XOR_COMP(n, XOR_COMP(real_prev, real_next->cmp));
```
更新第二個節點的壓縮位址,因為第二個節點的前面原本是 head,現在因為加了新節點,前面變成新節點,所以需要重新計算壓縮位址。
將前面的位址和當前自己的壓縮位址和原本前面的位址做 XOR 計算,這樣可以消掉原本前面的位址,保留後面的位址的資訊,得到 $(D \oplus (A \oplus (A \oplus B)))=(D \oplus B)$
現在
list:
head: {cmp: D}
tail: {cmp: C}
第一個節點位址: D
cmp: $(A \oplus C)$
第二個節點位址: C
cmp: $(D \oplus B)$
驗算:
第二個節點前面是 D
第二個節點壓縮位址: $(D \oplus B)$
計算下一個位址: $D\oplus (D \oplus B)=B$
得到 B 便可走到 tail
tail 前面是 C
tail 壓縮位址為 C
計算下一個位址: $C\oplus C = 0$
算出 0 可得知已走到最後。
結論:
該程式運作需要 head 和 tail 的位址來計算插入的第一個節點的壓縮位址,後面插入新節點到最前端,計算壓縮位址需要 head 的位址和後面一個節點的壓縮位址。
```
#define XOR_COMP(a, b) ((xor_node_t *) ((uint64_t)a^(uint64_t)b))
```
假設 64 bits 機器。
用XOR計算壓縮位址。