# 2021q1 Homework2 (quiz2) ###### tags:<`linux2021`> ## 題目`1` 考慮以下仿效 Linux 核心 [include/linux/list.h](https://github.com/torvalds/linux/blob/master/include/linux/list.h) 的精簡實作: ```cpp #include <stddef.h> /** * container_of() - Calculate address of object that contains address ptr * @ptr: pointer to member variable * @type: type of the structure containing ptr * @member: name of the member variable in struct @type * * Return: @type pointer of object containing ptr */ #ifndef container_of #define container_of(ptr, type, member) \ __extension__({ \ const __typeof__(((type *) 0)->member) *__pmember = (ptr); \ (type *) ((char *) __pmember - offsetof(type, member)); \ }) #endif /** * struct list_head - Head and node of a doubly-linked list * @prev: pointer to the previous node in the list * @next: pointer to the next node in the list */ struct list_head { struct list_head *prev, *next; }; /** * LIST_HEAD - Declare list head and initialize it * @head: name of the new object */ #define LIST_HEAD(head) struct list_head head = {&(head), &(head)} /** * INIT_LIST_HEAD() - Initialize empty list head * @head: pointer to list head */ static inline void INIT_LIST_HEAD(struct list_head *head) { head->next = head; head->prev = head; } /** * list_add_tail() - Add a list node to the end of the list * @node: pointer to the new node * @head: pointer to the head of the list */ static inline void list_add_tail(struct list_head *node, struct list_head *head) { struct list_head *prev = head->prev; prev->next = node; node->next = head; node->prev = prev; head->prev = node; } /** * list_del() - Remove a list node from the list * @node: pointer to the node */ static inline void list_del(struct list_head *node) { struct list_head *next = node->next, *prev = node->prev; next->prev = prev; prev->next = next; } /** * list_empty() - Check if list head has no nodes attached * @head: pointer to the head of the list */ static inline int list_empty(const struct list_head *head) { return (head->next == head); } /** * list_is_singular() - Check if list head has exactly one node attached * @head: pointer to the head of the list */ static inline int list_is_singular(const struct list_head *head) { return (!list_empty(head) && head->prev == head->next); } /** * list_splice_tail() - Add list nodes from a list to end of another list * @list: pointer to the head of the list with the node entries * @head: pointer to the head of the list */ static inline void list_splice_tail(struct list_head *list, struct list_head *head) { struct list_head *head_last = head->prev; struct list_head *list_first = list->next, *list_last = list->prev; if (list_empty(list)) return; head->prev = list_last; list_last->next = head; list_first->prev = head_last; head_last->next = list_first; } /** * list_cut_position() - Move beginning of a list to another list * @head_to: pointer to the head of the list which receives nodes * @head_from: pointer to the head of the list * @node: pointer to the node in which defines the cutting point */ static inline void list_cut_position(struct list_head *head_to, struct list_head *head_from, struct list_head *node) { struct list_head *head_from_first = head_from->next; if (list_empty(head_from)) return; if (head_from == node) { INIT_LIST_HEAD(head_to); return; } head_from->next = node->next; head_from->next->prev = head_from; head_to->prev = node; node->next = head_to; head_to->next = head_from_first; head_to->next->prev = head_to; } /** * list_entry() - Calculate address of entry that contains list node * @node: pointer to list node * @type: type of the entry containing the list node * @member: name of the list_head member variable in struct @type */ #define list_entry(node, type, member) container_of(node, type, member) /** * list_first_entry() - get first entry of the list * @head: pointer to the head of the list * @type: type of the entry containing the list node * @member: name of the list_head member variable in struct @type */ #define list_first_entry(head, type, member) \ list_entry((head)->next, type, member) /** * list_for_each - iterate over list nodes * @node: list_head pointer used as iterator * @head: pointer to the head of the list */ #define list_for_each(node, head) \ for (node = (head)->next; node != (head); node = node->next) ``` 接著延伸上述程式碼時做 doubly-linked list 的 merge sort: ```cpp #include <string.h> typedef struct __element { char *value; struct __element *next; struct list_head list; } list_ele_t; typedef struct { list_ele_t *head; /* Linked list of elements */ list_ele_t *tail; size_t size; struct list_head list; } queue_t; static list_ele_t *get_middle(struct list_head *list) { struct list_head *fast = list->next, *slow; list_for_each (slow, list) { if (COND1 || COND2) break; fast = fast->next->next; } return list_entry(TTT, list_ele_t, list); } static void list_merge(struct list_head *lhs, struct list_head *rhs, struct list_head *head) { INIT_LIST_HEAD(head); if (list_empty(lhs)) { list_splice_tail(lhs, head); return; } if (list_empty(rhs)) { list_splice_tail(rhs, head); return; } while (!list_empty(lhs) && !list_empty(rhs)) { char *lv = list_entry(lhs->next, list_ele_t, list)->value; char *rv = list_entry(rhs->next, list_ele_t, list)->value; struct list_head *tmp = strcmp(lv, rv) <= 0 ? lhs->next : rhs->next; list_del(tmp); list_add_tail(tmp, head); } list_splice_tail(list_empty(lhs) ? rhs : lhs, head); } void list_merge_sort(queue_t *q) { if (list_is_singular(&q->list)) return; queue_t left; struct list_head sorted; INIT_LIST_HEAD(&left.list); list_cut_position(&left.list, &q->list, MMM); list_merge_sort(&left); list_merge_sort(q); list_merge(&left.list, &q->list, &sorted); INIT_LIST_HEAD(&q->list); list_splice_tail(&sorted, &q->list); } ``` * COND1 : `fast -> next == list` * COND2 : `fasr->next->next == list` * MMM : `&get_middle(&q->list)->list` * TTT : `slow` 測試程式碼 main.c 如下: ```cpp #include <assert.h> #include <stdbool.h> #include <stdio.h> #include <stdlib.h> static bool validate(queue_t *q) { struct list_head *node; list_for_each (node, &q->list) { if (node->next == &q->list) break; if (strcmp(list_entry(node, list_ele_t, list)->value, list_entry(node->next, list_ele_t, list)->value) > 0) return false; } return true; } static queue_t *q_new() { queue_t *q = malloc(sizeof(queue_t)); if (!q) return NULL; q->head = q->tail = NULL; q->size = 0; INIT_LIST_HEAD(&q->list); return q; } static void q_free(queue_t *q) { if (!q) return; list_ele_t *current = q->head; while (current) { list_ele_t *tmp = current; current = current->next; free(tmp->value); free(tmp); } free(q); } bool q_insert_head(queue_t *q, char *s) { if (!q) return false; list_ele_t *newh = malloc(sizeof(list_ele_t)); if (!newh) return false; char *new_value = strdup(s); if (!new_value) { free(newh); return false; } newh->value = new_value; newh->next = q->head; q->head = newh; if (q->size == 0) q->tail = newh; q->size++; list_add_tail(&newh->list, &q->list); return true; } int main(void) { FILE *fp = fopen("cities.txt", "r"); if (!fp) { perror("failed to open cities.txt"); exit(EXIT_FAILURE); } queue_t *q = q_new(); char buf[256]; while (fgets(buf, 256, fp)) q_insert_head(q, buf); fclose(fp); list_merge_sort(q); assert(validate(q)); q_free(q); return 0; } ``` ### (一)解釋程式碼運作原理: 根據 merge-sort 的實作方法,第一步先將整個序列對半分割: ```graphviz digraph mergesort{ O [label = "Original Array" , shape = Box] P1[label = "P1" , shape = Box] P2[label = "P2" , shape = Box] O -> P1 [label = "partition into two"] O -> P2 } ``` 分別將左右兩個序列依照一樣的步驟分割直到不能再分割(size is 1)後倆倆合併 : ```graphviz digraph merge{ P1 [label = "E1(size 1)" , shape = "Box"] P2 [label = "E2(size 1)" , shape = "Box"] P3 [label = "E3(size 1)" , shape = "Box"] P4 [label = "E4(size 1)" , shape = "Box"] P5 [label = "M1(size 2)" , shape = "Box"] P6 [label = "M2(size 2)" , shape = "Box"] P1->P5 [label = "Merge" , fontcolor = red] P2->P5 P3->P6 [label = " Merge" , fontcolor = red] P4->P6 P7 [label = "Final(size 4)" , shape = "Box"] P5 -> P7 [label = "Merge" , fontcolor = red] P6 -> P7 } ``` 對於 Linked List 裡面該如何將其分成兩段?在程式碼中的 `get_middle` 使用的方法是利用 `fast` 、 `slow` 指標,`fast` 指標走訪 linked list 的速度是 `slow` 的兩倍,因為是 doubly linked list 所以當 `fast` 走到開頭的時候代表整個串列已經走訪完,所以 `slow` 指標會指在串列的中間位置,對應的程式碼為: ```cpp static list_ele_t *get_middle(struct list_head *list) { struct list_head *fast = list->next, *slow; list_for_each (slow, list) { if (fast->next == list || fast->next->next == list) break; fast = fast->next->next; } return list_entry(slow, list_ele_t, list); } ``` 上述程式碼在最後的回傳值是 `list_ele_t*` (pointer to list_ele_t) ,但是 `slow` 是 `struct list_head*` (pointer to struct list_head) ,所以在回傳時使用 `list_entry` 巨集算出包含 `slow` 指標的 `list_ele_t` 記憶體位置。 接著看程式碼中在哪裡呼叫 `get_middle`: ```cpp ... list_cut_position(&left.list, &q->list, &get_middle(&q->list)->list); list_merge_sort(&left); list_merge_sort(q); list_merge(&left.list, &q->list, &sorted); ... ``` 由此可知是在 `list_cut_position` 中呼叫的,接下來看看 `list_cut_position` 的實作: ```cpp static inline void list_cut_position(struct list_head *head_to, struct list_head *head_from, struct list_head *node) { struct list_head *head_from_first = head_from->next; if (list_empty(head_from)) return; if (head_from == node) { INIT_LIST_HEAD(head_to); return; } head_from->next = node->next; head_from->next->prev = head_from; head_to->prev = node; node->next = head_to; head_to->next = head_from_first; head_to->next->prev = head_to; } ``` 對應的操作如圖: * 一開始的狀況,`e1` 為 list 的開頭 `e3` 為中間的節點 ```graphviz digraph list{ rankdir = LR head_from_first [label = "head_from_first" , shape = "box"] head_from [label = "head_from" , shape = "box"] head_to[shape = "box"] node_ [label = "node" , shape = "box"] e1 [shape = "box"] e2 [shape = "box"] e3 [shape = "box"] e4 [shape = "box"] e5 [shape = "box"] e1 -> e2[tailclip = true , arrowsize = 1] e2->e1 e2 -> e3 e3->e2 e3 -> e4 e4->e3 e4 -> e5 e5->e4 e1 -> e5 e5->e1 head_to -> head_to head_from -> e1 [arrowhead = vee , style = dashed , color = red] head_from_first -> e2 [arrowhead = vee , style = dashed , color = red] node_ -> e3 [arrowhead = vee , style = dashed , color = red] } ``` * 讓 `e1` (head_from 所指的記憶體位址) 的 next 指向 `e4` (node->next 所指的記憶體位址),`e4` (head_from->next 所指的記憶體位址) 的 pre 指向 `e1` (head_from 所指的記憶體位址) ```graphviz digraph list{ rankdir = LR head_from_first [label = "head_from_first" , shape = "box"] head_from [label = "head_from" , shape = "box"] head_to[shape = "box"] node_ [label = "node" , shape = "box"] e1 [shape = "box"] e2 [shape = "box"] e3 [shape = "box"] e4 [shape = "box"] e5 [shape = "box"] e1 -> e4[ color = blue , style = dashed] e2->e1 e2 -> e3 e3->e2 e3 -> e4 e4->e1 [color = blue , style = dashed] e4 -> e5 e5->e4 e1 -> e5 e5->e1 head_to -> head_to head_from -> e1 [arrowhead = vee , style = dashed , color = red] head_from_first -> e2 [arrowhead = vee , style = dashed , color = red] node_ -> e3 [arrowhead = vee , style = dashed , color = red] } ``` * 接下來執行完剩下的四行,就可以拆成兩段了 ```graphviz digraph list{ rankdir = LR head_from_first [label = "head_from_first" , shape = "box"] head_from [label = "head_from" , shape = "box"] head_to[shape = "box"] node_ [label = "node" , shape = "box"] e1 [shape = "box"] e2 [shape = "box"] e3 [shape = "box"] e4 [shape = "box"] e5 [shape = "box"] e1 -> e4 e2 -> head_to[color = blue] e3->e2 e2 -> e3 e3 -> head_to[color = blue] e4->e1 e4 -> e5 e5->e4 e1 -> e5 e5->e1 head_to -> e3 [label = "pre" color = blue] head_to -> e2 [label = "next" color = blue] head_from -> e1 [arrowhead = vee , style = dashed , color = red] head_from_first -> e2 [arrowhead = vee , style = dashed , color = red] node_ -> e3 [arrowhead = vee , style = dashed , color = red] } ``` 接下來針對分出的兩段 linked list 個別去做 merge sort 依序遞迴下去, (Keep going) ## 題目 `2` 考慮函式 func 接受一個 16 位元無號整數 N,並回傳小於或等於 N 的 power-of-2 (漢字可寫作為 2 的冪) 假定 N = 101012 = 2110,那麼 func(21) 要得到 16,請補完程式碼,使其運作符合預期。 實作程式碼如下: ```cpp uint16_t func(uint16_t N) { /* change all right side bits to 1 */ N |= N >> 1; N |= N >> X; N |= N >> Y; N |= N >> Z; return (N + 1) >> 1; } ``` * X : 2 * Y : 4 * Z : 8 ### (一)解釋程式碼運作原理 * 考慮以下數字 0b 1000 0000 ```graphviz digraph bits{ node [shape=plaintext, fontcolor=red, fontsize=18] "Value:" -> "Bits:" [color=white] node [shape=record, fontcolor=black, fontsize=14, width=4.75, fixedsize=true] values [label="<f0> 1 | <f1> 0 | <f2> 0 | <f3> 0 | <f4> 0 | <f5> 0 | <f6> 0 | <f7> 0", color=blue, fillcolor=white, style=filled] indices [label="0th | 1th | 2th | 3th | 4th | 5th |6th | 7th", color=white] { rank=same; "Value:"; values } { rank=same; "Bits:" ; indices} } ``` * 先將其向右 shift 一格會變成 ```graphviz digraph bits{ node [shape=plaintext, fontcolor=red, fontsize=18] "Value:" -> "Bits:" [color=white] node [shape=record, fontcolor=black, fontsize=14, width=4.75, fixedsize=true] values [label="<f0> 0 | <f1> 1 | <f2> 0 | <f3> 0 | <f4> 0 | <f5> 0 | <f6> 0 | <f7> 0", color=blue, fillcolor=white, style=filled] indices [label="0th | 1th | 2th | 3th | 4th | 5th |6th | 7th", color=white] { rank=same; "Value:"; values } { rank=same; "Bits:" ; indices} } ``` * 將其對原數做 `or(|)` 運算後得到的結果可見最高位元的右邊一格變成 1 ```graphviz digraph bits{ node [shape=plaintext, fontcolor=red, fontsize=18] "Value:" -> "Bits:" [color=white] node [shape=record, fontcolor=black, fontsize=14, width=4.75, fixedsize=true] values [label="<f0> 1 | <f1> 1 | <f2> 0 | <f3> 0 | <f4> 0 | <f5> 0 | <f6> 0 | <f7> 0", color=blue, fillcolor=white, style=filled] indices [label="0th | 1th | 2th | 3th | 4th | 5th |6th | 7th", color=white] { rank=same; "Value:"; values } { rank=same; "Bits:" ; indices} } ``` * 目的是希望最高位元的右邊全部都變為 1 ,故必須用剛得到的結果繼續做,由於現在最高的二位元都為 1 ,必須對向右移 2 的結果做 `or` 運算才能把前面 4 位元都變為 1 ,結果如圖 ```graphviz digraph bits{ node [shape=plaintext, fontcolor=red, fontsize=18] "Value:" -> "Bits:" [color=white] node [shape=record, fontcolor=black, fontsize=14, width=4.75, fixedsize=true] values [label="<f0> 1 | <f1> 1 | <f2> 1 | <f3> 1 | <f4> 0 | <f5> 0 | <f6> 0 | <f7> 0", color=blue, fillcolor=white, style=filled] indices [label="0th | 1th | 2th | 3th | 4th | 5th |6th | 7th", color=white] { rank=same; "Value:"; values } { rank=same; "Bits:" ; indices} } ``` * 接下來為了把最右邊的 4 位元都變成 1 ,就將結果向右移動 4 位元再做 `or` 便可以把它填滿並變成 1 ```graphviz digraph bits{ node [shape=plaintext, fontcolor=red, fontsize=18] "Value:" -> "Bits:" [color=white] node [shape=record, fontcolor=black, fontsize=14, width=4.75, fixedsize=true] values [label="<f0> 1 | <f1> 1 | <f2> 1 | <f3> 1 | <f4> 1 | <f5> 1 | <f6> 1 | <f7> 1", color=blue, fillcolor=white, style=filled] indices [label="0th | 1th | 2th | 3th | 4th | 5th |6th | 7th", color=white] { rank=same; "Value:"; values } { rank=same; "Bits:" ; indices} } ``` * 這邊舉例是 8 bit 的無號數,但題目敘述是對 16 bit 無號數做運算,故將剛剛得到的結果再向右移 8 個位元便可填滿最右邊 8 位元 ### (二)查閱 linux 核心原始碼 #### `is_power_of_2` 在 [arch/microblaze/mm/pgtable.c](https://elixir.bootlin.com/linux/latest/source/arch/microblaze/mm/pgtable.c#L186) 中將其定為 macro: ```clike #define is_power_of_2(x) ((x) != 0 && (((x) & ((x) - 1)) == 0)) ``` 若該數為 2 的冪次方則該數的最高非 0 位元右側必定全都為 0 ,將該數對該數扣 1 做 `and` 運算若為 0 則該數為 2 的冪次方