---
tags: linux2023
---
# 2023q1 Homework1 (quiz1)
contributed by < `paul90317` >
[題目連結](https://hackmd.io/@sysprog/linux2023-quiz1)
### 測驗 `1`
快速排序程式碼填空
```c
static void list_sort(struct list_head *head)
{
if (list_empty(head) || list_is_singular(head))
return;
/* 宣告左右兩 list */
struct list_head list_less, list_greater;
INIT_LIST_HEAD(&list_less);
INIT_LIST_HEAD(&list_greater);
/* 以第一個節點當 pivot */
struct item *pivot = list_first_entry(head, AAA, BBB);
list_del(&pivot->list);
/* 大小分割 */
struct item *itm = NULL, *is = NULL;
CCC (itm, is, head, list) {
if (cmpint(&itm->i, &pivot->i) < 0)
DDD (&itm->list, &list_less);
else
EEE (&itm->list, &list_greater);
}
list_sort(&list_less);
list_sort(&list_greater);
list_add(&pivot->list, head);
list_splice(&list_less, head);
FFF(&list_greater, head);
}
```
`list_first_entry` 的輸入是節點 `head`,輸出是 `entry` 所以會用到 `container_of`,故 `AAA` 是 `struct item`,`BBB` 是 `list`。
`CCC` 是 `list_for_each_entry_safe`。
`DDD` 這個程式碼發生在 `itm` 比 `pivot` 小時發生。題目要求是該排序演算法要是 stable,所以應填入 `list_add_tail`。
`EEE` 同 `DDD`,也是 `list_add_tail`。
`FFF` 可以看出程式碼想要以 `list_less`, `pivot`, `list_greater` 建立新的 list 於 `head`,所以要填 `list_splice_tail`。
:::danger
在 `DDD` `FFF` 的程式碼中,忘記把 `itm` 從原本的 list 中移除,正確答案應該要是 `list_move_tail`。
如果要填 `list_add_tail`,就要在分割結束串接之前加上 `INIT_LIST_HEAD(head)`。
參考:[`as200188`](https://hackmd.io/@as200188/linux2023-quiz1)
:::
### 測驗 `2`
```c=
#define MAX_DEPTH 512
static void list_sort_nr(struct list_head *head)
{
if (list_empty(head) || list_is_singular(head))
return;
struct list_head stack[MAX_DEPTH];
for (int i = 0; i < MAX_DEPTH; i++)
INIT_LIST_HEAD(&stack[i]);
int top = -1; // top 代表最後一個 list,而非下一個
list_splice_init(head, &stack[++top]);
struct list_head partition;
INIT_LIST_HEAD(&partition);
/* 每次執行 statement 相當於遞迴呼叫 */
while (top >= 0) {
INIT_LIST_HEAD(&partition);
/* 在 stack 頂部拿取一個 partition 進行排序 */
list_splice_init(&stack[top--], &partition);
/* 如果 partition 不為空 */
if (!list_empty(&partition) && !list_is_singular(&partition)) {
struct list_head list_less, list_greater;
INIT_LIST_HEAD(&list_less);
INIT_LIST_HEAD(&list_greater);
/* 選取第一個節點當作 pivot */
struct item *pivot =
list_first_entry(&partition, struct item, list);
list_del(&pivot->list);
INIT_LIST_HEAD(&pivot->list);
/* 分成大小兩邊 */
struct item *itm = NULL, *is = NULL;
GGGG (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_stable 才能
* 達到 stable sort 的目的 */
}
/* 將 pivot 接在 list_less 後面 */
HHHH (&pivot->list, &list_less);
/* 將左右兩條 list (未經 partition) 推入 stack */
if (!list_empty(&list_greater))
list_splice_tail(&list_greater, IIII);
if (!list_empty(&list_less))
list_splice_tail(&list_less, JJJJ);
/* 應先放入 greater 再放 less,因為下方程式碼
* 會將 list 從頂部取出再推入 head 的尾巴 */
/* 若 partition 為空或只有單一元素 */
} else {
/* 直接推入 stack */
top++;
list_splice_tail(&partition, &stack[top]);
while (top >= 0 && list_is_singular(&stack[top])) {
/* 當 list 只有單一節點,將 list 拿出並插入 head 尾端 */
struct item *tmp =
list_first_entry(&stack[top], struct item, list);
list_del(&tmp->list);
INIT_LIST_HEAD(KKKK);
list_add_tail(&tmp->list, head);
}
}
}
}
```
`GGGG` 是 `list_for_each_entry_safe`
`HHHH` 是 `list_add_tail`
`IIII` 是 `&stack[++top]`
`JJJJ` 是 `&stack[++top]`
`KKKK` 是 `&tmp->list`
:::success
**1. 解釋上方程式碼運作原理,指出設計和實作的缺失**
* 在 40、42 行應該改成 `list_add_tail` 以達到 stable sort 的目的。
* 在 115 行應該改成 `if (!list_empty(!list_empty(&partition)))` 以防止將空的 list 推入並造成無窮迴圈。
:::
### 測驗 `3`
```c
typedef struct _xorlist_node {
struct _xorlist_node *cmp;
} xor_node_t;
typedef struct _xor_list_struct {
xor_node_t head, tail;
uint32_t count;
} xor_list_t;
struct test_node {
int value;
xor_node_t xornode;
};
#define XOR_COMP(a, b) ((xor_node_t *) (LLLL))
```
根據題目說明,要把前一節點和後一節點的指標做相連,`LLLL` 是 `a & b`
```c
#define xorlist_for_each(node, rp, rn, list) \
for (rp = &(list)->head, node = rp->cmp; node != &(list)->tail; \
rn = address_of(rp, node->cmp), rp = node, node = rn)
```
上方巨集迴圈初始化部分會將 node 直接指派成 `list->head.cmp`,這意味著第一個節點 (first entry) 不是 `list->head`,而是 `list->head` 的下一節點。
`rp` 是上一節點的指標,所以在迴圈迭代下一節點是 `rn = address_of(rp, node->cmp)`。
當 `node` 是 `&list->tail` 的時候,會跳出迴圈,因為它的容器是 `xor_list_t` 而非自訂義容器 `struct test_node`。
```c
#define xorlist_for_each_prev(node, rp, rn, list) \
for (rp = &(list)->tail, node = rp->cmp; node != &(list)->head; \
rn = address_of(rp, node->cmp), rp = node, node = rn)
```
上方巨集的走訪方向與 `xorlist_for_each` 相反。
```c
#define xorlist_for_each_from(node, pos1, pos2, rp, rn, list) \
for (rp = pos2, node = pos1; node != &(list)->tail; \
rn = address_of(rp, node->cmp), rp = node, node = rn)
```
上方巨集定義了初始節點 `pos2`,需要再給它 `pos1` 以獲得下一節點。
```c
#define xorlist_for_each_from_prev(node, pos1, pos2, rp, rn, list) \
for (rp = pos1, node = pos2; node != &(list)->head; \
rn = address_of(rp, node->cmp), rp = node, node = rn)
```
上方巨集的走訪方向與 `xorlist_for_each_from` 相反。
```c=
int xorlist_add(xor_list_t *list, xor_node_t *n)
{
/* n 的下一節點 */
xor_node_t *real_next;
if (!n)
return ENOMEM;
/* n 的前一節點,也就是 head */
xor_node_t *real_prev = &list->head;
/* node 是原本的第一個節點 */
xor_node_t *node = real_prev->cmp;
/* 對 n 的下一節點指派 */
if (node == &list->tail)
real_next = MMMM;
else
real_next = node;
/* 將 head->cmp 直接指派為 n 因為 head 沒有前一節點 */
real_prev->cmp = n;
/* 根據定義指派 n->cmp */
n->cmp = XOR_COMP(real_prev, real_next);
/* real_next 是 node
* node 的 cmp 是 n (node 的前一節點) 與 node 的下一節點的 xor
* 故 XOR_COMP(real_prev, PPPP) 是 node 的下一節點
* PPPP 是 node->cmp */
real_next->cmp = XOR_COMP(n, XOR_COMP(real_prev, PPPP));
list->count++;
return 0;
}
```
`real_next` 是 `n` 的下一個節點,在一般的情況下它是原本的第一個節點 `node`,但在沒有節點時它是 tail,故 `MMMM` 是 `&list->tail`。
`PPPP` 是 `node->cmp`