--- tags: linux 2023 --- # 2023q1 Homework1 (lab0) contributed by < `randyuncle` > :::danger 變更登記於 https://hackmd.io/@sysprog/linux2023-homework1 的超連結,要用「固定網址」(參見 用固定網址發布筆記),也就是如 https://hackmd.io/@itsme/XXXX 的形式,設定公開發表。 :notes: jserv ::: ## 開發環境 ```shell $ gcc --version gcc (Ubuntu 11.3.0-1ubuntu1~22.04) 11.3.0 Copyright (C) 2021 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. $ lscpu Architecture: x86_64 CPU op-mode(s): 32-bit, 64-bit Address sizes: 39 bits physical, 48 bits virtual Byte Order: Little Endian CPU(s): 12 On-line CPU(s) list: 0-11 Vendor ID: GenuineIntel Model name: Intel(R) Core(TM) i7-8750H CPU @ 2.20GHz CPU family: 6 Model: 158 Thread(s) per core: 2 Core(s) per socket: 6 Socket(s): 1 Stepping: 10 CPU max MHz: 4100.0000 CPU min MHz: 800.0000 BogoMIPS: 4399.99 ``` ## `queue.c` 開發過程 :::info 目前 make test 分數:95/100 ::: ### `q_new` 首先以 `malloc` 來配置 `struct list_head` 大小的記憶體空間給最初的節點,之後再將其以 `list.h` 的內部 function `INIT_LIST_HEAD` 把該節點的內容初始化。 :::warning 改進漢語描述。 :notes: jserv ::: 除此之外,為了防止 `malloc` 無法配置記憶體空間的問題,所以加了一個 if-statement 判斷此節點是否為 NULL,以防在做 `INIT_LIST_HEAD` 的初始化時出錯。 ```c struct list_head *q_new() { struct list_head *head = (struct list_head *) malloc(sizeof(struct list_head)); if (head) INIT_LIST_HEAD(head); return head; } ``` ### `q_free` 以 `list.h` 中的 macro `list_for_each_entry_safe` 對輸入進來的 `struct list_head *head` 做迭代。 其中,將所有經過的節點的 : * `struct list_head list` : 以 `list.h` 的 function `list_del` 刪掉。 * `char *value` : 以 `queue.h` 的 function `q_release_element` 釋放掉。 除此之外,為了防止輸入進來的 `struct list_head *head` 是 NULL,所以多加了一個 if-statement 判斷其是否為 NULL。 ```c void q_free(struct list_head *l) { if (l) { // if head exists, clean the queue. element_t *iterator, *next; list_for_each_entry_safe (iterator, next, l, list) { list_del(&iterator->list); q_release_element(iterator); } free(l); } } ``` ### `q_insert_head` / `q_insert_tail` 這兩個 function 的<s>實做</s> 實作是大同小異的,都是先新增一個 `element_t` 型態的 node 將輸入進 function 的` struct list_head *head`、`char *s` 儲存。 :::warning 注意 [資訊科技詞彙翻譯](https://hackmd.io/@sysprog/it-vocabulary) :notes: jserv ::: 之後,再使用 `list.h` 的 function 將 `element_t` 內的 list 加入到 queue 之中。其中,`q_insert_head` 用的是 `list_add` ; `q_insert_tail` 使用 `list_add_tail`。 ```c bool q_insert(struct list_head *head, char *s){ if (!head || !s) return false; element_t *new = (element_t *) malloc(sizeof(element_t)); if (!new) return false; // no memory space for `new` int s_len = strlen(s) + 1; new->value = (char *) malloc(s_len * sizeof(char)); if (!new->value) { free(new); return false; // no memory space for `new->value` } memcpy(new->value, s, s_len); // insert value /** * depends on `element_t *new` inserts at head or tail of queue list_add(&new->list, head); // insert at head of queue list_add_tail(&new->list, head); // insert at tail of queue */ return true; } ``` ### `q_size` 採用 list.h 的 function `list_for_each` 做 linked-list 的 traverse,達成 queue 長度的計算。 ```c int q_size(struct list_head *head) { if (!head || list_empty(head)) return 0; int size = 0; struct list_head *p; list_for_each (p, head) size++; return size; } ``` ### `q_remove_head` / `q_remove_tail` 這兩個 function 的實作是大同小異的。差別在於 `q_remove_head` 是用 list.h 中的 `list_first_entry` 去找到要被 移走的頭部節點 ; `q_remove_tail` 是用 list.h 中的 `list_last_entry` 去找到要被移走的尾部節點。 ```c element_t *q_remove(struct list_head *head, char *sp, size_t bufsize) { if (!head || list_empty(head)) return NULL; // `head` is NULL, or there's no list in `head` /** * depends on remove head or tail node element_t *remove = list_first_entry(head, element_t, list); element_t *remove = list_last_entry(head, element_t, list); */ list_del(&remove->list); if (sp) { memcpy(sp, remove->value, bufsize); sp[bufsize - 1] = '\0'; } return remove; } ``` ### `q_delete_mid` 在尋找中間節點的實作當中,一開始是參考了 [Floyd’s Cycle detection](https://en.wikipedia.org/wiki/Cycle_detection) 的理論,運用快慢指標的方式找到中間節點。 但是,由於此程式的 queue 是用 circular double linked-list 的形式所構成,所以就稍微改變了一下實作的方式成以下: 1. 令兩個 `struct list_head` 的指標 `foreward` 和 `backward` 。前者的作用是從 queue 的 head 往後跑一個節點 ; 後者的作用是從 queue 的 tail 往前跑一個節點。 2. 當兩個指標指到同一個節點時,或者是 `foreward` 指標的下一個節點是 `backward` 時,則 `foreward` 指標指到的節點為該 queue 的中間節點。 ```c bool q_delete_mid(struct list_head *head) { if (!head || list_empty(head)) return false; // `head` is NULL, or there's no list in `head` /*if the foreward pointer hits the backward pointer, then they're in the * middle of the list*/ struct list_head *foreward = head->next, *backward = head->prev; for (; foreward != backward && foreward->next != backward; foreward = foreward->next, backward = backward->prev) ; list_del(foreward); q_release_element(container_of(foreward, element_t, list)); return true; } ``` ### `q_delete_dup` 運用到了 list.h 中的 macro `list_for_each_entry_safe` 來對 `element_t` structure 的 linked-list `list` 做 traverse 。其中,以 `element_t *iterator` 當作該 macro 中的 entry ,`element_t *next` 當作該 macro 中的 safe 。並且,根據每次的迭代,會有以下判斷的方式判斷是否有重複的字串: * 當 `next != head` 且 `next->value` == `iteration->value` 時: * 進入 do-while 迴圈中,把 `next` 指標指到的節點刪除,並將 `next` 指標移到他的下一個節點上,以相通得判斷式判斷該節點的 `value` 是否也為該重複的字串。 * 迴圈結束後,把目前 iterator 指到的節點刪除,回去 `list_for_each_entry_safe` macro 做迭代。 ```c bool q_delete_dup(struct list_head *head) { if (!head || list_empty(head)) return false; // `head` is NULL, or there's no list in `head` element_t *iterator, *next; /*note that the list is sorted*/ list_for_each_entry_safe (iterator, next, head, list) { if (&next->list != head && !strcmp(iterator->value, next->value)) { do { element_t *next_to_safe = list_entry(next->list.next, element_t, list); list_del(&next->list); q_release_element(next); next = next_to_safe; } while (&next->list != head && !strcmp(iterator->value, next->value)); list_del(&iterator->list); q_release_element(iterator); } } return true; } ``` ### `q_swap` 使用兩個指標 `struct list_head *curr` 和 `struct list_head *next` ,分別代表兩個要被互相交換的節點,以及一個 for 迴圈做 `struct list_head` structure 的 linked-list traverse 其中,`curr` 和 `next` 任兩個指標在未到達 `struct list_head *head` 的情況下,會先以 list.h 中的 function `list_move` 將 `curr` 和 `next` 指向的節點做交換。之後,再將 `curr` 指向自己的下一個指標 ; `next` 指向 `curr` 的下一個指標,避免有交換到已經做過交換的節點。 ```c void q_swap(struct list_head *head) { if (!head || list_empty(head)) return; // `head` is NULL, or there's no list in `head` struct list_head *curr = head->next, *next = curr->next; for (; curr != head && next != head; curr = curr->next, next = curr->next) list_move(curr, next); } ``` ### `q_reverse` 使用 list.h 的 macro `list_for_each_safe` 去 traverse 所有的 `struct list_head` structure 的 linked-list,並將每一個 traverse 到的節點都使用 list.h 的 function `list_move` 搬到整個 queue 的源頭,也就是成為 `struct list_head *head` 後一個節點。 ```c void q_reverse(struct list_head *head) { if (!head || list_empty(head)) return; // `head` is NULL, or there's no list in `head` struct list_head *iterator, *next; /*move each item the iterator points to to the head*/ list_for_each_safe (iterator, next, head) list_move(iterator, head); } ``` ### `q_reverse_k` 目前使用的方法是用 list.h 的 macro `list_for_each_safe` 去 traverse 所有的 `struct list_head` structure 的 linked-list,並在其中加入一個計數器來計是否已經經過 k 個節點。如果已經經過 k 個節點,則會用前一個 function `q_reverse` 去把該 k 個節點反轉。 ```c void q_reverseK(struct list_head *head, int k) { if (!head || list_empty(head)) return; // `head` is NULL, or there's no list in `head` struct list_head *iterator, *next, *start = head, dummy; INIT_LIST_HEAD( &dummy); // pointer dummy serves as same as pointer head does int i = 0; list_for_each_safe (iterator, next, head) { i++; if (i < k) continue; list_cut_position(&dummy, start, iterator); // cut k node of the list out as an // independent list to be reverse q_reverse(&dummy); list_splice_init(&dummy, start); // take dummy back to the original list start = next->prev; i = 0; } } ``` ### `q_sort` 在本程式中的 `q_sort` 我採用的是 merge sort 。其中,製作 merge sort 的方式主要是參考了[你所不知道的 C 語言: linked list 和非連續記憶體](https://hackmd.io/@sysprog/c-linked-list#Merge-Sort-%E7%9A%84%E5%AF%A6%E4%BD%9C) 中的 Merge Sort 的實作。也因此,我把 `q_sort` 拆成了三個 function -- `q_sort` 、`divide`、`merge_two_list` 。 為了能夠不讓指標會因為 circular linked-list 的特性跑到 `struct list_head *head` 的關係,在 function `q_sort` 中就會先將 queue 的尾端節點和 `struct list_head *head` 的連結斷掉,使其失去 circular 的特性,方便進行 linked-list 的 divide。最後,在 queue 已經 sort 完後,再重新把 queue 變成 circular linked-list。 ```c void q_sort(struct list_head *head) { // merge sort machemic if (!head || list_empty(head) || list_is_singular(head)) return; // `head` is NULL, no list in `head`, or one element struct list_head *end = head->prev; // make the list no longer be circular end->next = NULL; head->next->prev = NULL; // start to sort head->next = divide(head->next, end); // make the list to be circular again (move the head back) struct list_head *curr; for (curr = head; curr->next; curr = curr->next) curr->next->prev = curr; curr->next = head; curr->next->prev = curr; } ``` #### `divide` 在 `q_sort` 使 queue 失去 circular linked-list 的特性後,會先進入 `divide` function 。 divide function 主要的目的是用來找一個 linked-list 的中間節點,從而將其拆成兩個 linked-list ,再分別代入 `divide` 做遞迴。等到 linked-list 被拆成只有一個節點的時候,就會將兩個相鄰的 linked-list 以 `merge_two_list` function 統合成排序好的 linked-list。 ```c struct list_head *divide(struct list_head *head, struct list_head *end) { if (head == end) return head; // find middle point struct list_head *foreward = head, *backward = end; for (; foreward != backward && foreward->next != backward; foreward = foreward->next, backward = backward->prev) ; if (foreward == backward) // the list has odd nodes backward = backward->next; // make the list no longer be circular foreward->next = NULL; backward->prev = NULL; // keep divide until hit the break point struct list_head *left = divide(head, foreward); struct list_head *right = divide(backward, end); // conquer the partitions return merge_two_list(left, right); } ``` #### `merge_two_list` 作法源自於[你所不知道的 C 語言: linked list 和非連續記憶體](https://hackmd.io/@sysprog/c-linked-list#Merge-Sort-%E7%9A%84%E5%AF%A6%E4%BD%9C)的一個片段 -- `從 Linux 核心的藝術談起 -- 案例探討: LeetCode 21. Merge Two Sorted Lists` ,也就是利用間接指標的概念將要 merge 的節點一個一個串起來。 ```c struct list_head *merge_two_list(struct list_head *left, struct list_head *right) { // reference: 你所不知道的 C 語言: linked list 和非連續記憶體 struct list_head *head = NULL; struct list_head **p = &head; for (; left && right; p = &((*p)->next)) { element_t *l = list_entry(left, element_t, list); element_t *r = list_entry(right, element_t, list); if (strcmp(l->value, r->value) < 0) { *p = left; left = left->next; } else { *p = right; right = right->next; } } *p = (struct list_head *) ((uintptr_t) left | (uintptr_t) right); // <stdint.h> return head; } ``` ### `q_descend` 根據 [Leetcode 2487. Remove Nodes From Linked List](https://leetcode.com/problems/remove-nodes-from-linked-list/description/) 的例子可以看出,`q_descend` 運行完以後的 sorted linked-list 會呈嚴格降冪排列。 因此,此塊的實作就以兩個 `element_t` 型態的指標:`*p`, `*c_max` 配合一個 for 迴圈對 queue 做逆向 traverse 。其中,當指標 `*p` 指向的節點的 `value` 比起 `*c_max` 指到的來得小或者是一樣的話,就會將其抹除 ; 反之,則將 `*c_max` 更新為 `*p` 指到的節點。 ```c int q_descend(struct list_head *head) { if (!head || list_empty(head)) return 0; // `head` is NULL, or there's no list in `head` // this section cares about value in 'element_t' structure struct list_head *curr = head->prev; element_t *p, *c_max = list_entry(curr, element_t, list); for (; c_max->list.prev != head;) { p = list_entry(c_max->list.prev, element_t, list); if (strcmp(p->value, c_max->value) <= 0) { list_del(&p->list); q_release_element(p); } else c_max = p; } return q_size(head); } ``` ### `q_merge` 將 `queue_contex_t` structure 各個分段的 queue 以 list.h 的 function `list_splice_init` 接在以 `queue_contex_t *main_q` 的 queue 之中。 之後,再使用前面寫的 `q_sort` function 將 `queue_contex_t *main_q` 的 queue 做重新排列。 ```c int q_merge(struct list_head *head) { if (!head || list_empty(head)) return 0; // `head` is NULL, or there's no list in `head` if (list_is_singular(head)) return list_entry(head->next, queue_contex_t, chain)->size; // merging by 'q_context_t' structure queue_contex_t *main_q = list_entry(head->next, queue_contex_t, chain); // 分段合併 for (struct list_head *curr = head->next->next; curr != head; curr = curr->next) { queue_contex_t *c = list_entry(curr, queue_contex_t, chain); list_splice_init(c->q, main_q->q); main_q->size += c->size; c->size = 0; } // sorting q_sort(main_q->q); return main_q->size; } ```