# 2023q1 Homework1 (lab0)
contributed by < [willow11235811](https://github.com/willow11235811) >
## 開發環境
```shell
$ paleofetch
OS: Arch Linux x86_64
Host: X570 AORUS ELITE -CF
Kernel: 6.1.12-arch1-1
Uptime: 3 hours, 41 mins
Battery: Not found
Packages: 1791 (pacman)
Shell: zsh
Resolution: 1920x1080
Terminal: st-256color
CPU: AMD Ryzen 7 5700X 8- Processor (16) @ 4.661GHz
Memory: 2850MiB / 32023MiB (8%)
$ gcc --version
gcc (GCC) 12.2.1 20230201
```
## :notebook: 開發過程
- [x] 實作 `queue.c`
- [ ] 開啟 [Address Sanitizer](https://github.com/google/sanitizers/wiki/AddressSanitizer),修正 `qtest` 執行過程中的錯誤
- [ ] 運用 Valgrind 排除 `qtest` 實作的記憶體錯誤,並透過 Massif 視覺化 "simulation" 過程中的記憶體使用量,需要設計對應的實驗
- [ ] 精簡 ```q_delete_dup```
- [ ] 思考其他 ```q_merge``` 實作方式
- [ ] 研讀 Linux 核心原始程式碼的 [lib/list_sort.c](https://github.com/torvalds/linux/blob/master/lib/list_sort.c) 並嘗試引入到 `lab0-c` 專案
:::spoiler
比較你自己實作的 merge sort 和 Linux 核心程式碼之間效能落差
:::
- [ ] 確保 `qtest` 提供的 `web` 命令得以搭配上述佇列實作使用
:::spoiler
目前一旦網頁伺服器啟動後,原本處理命令列操作的 `linenoise` 套件就無法使用,請提出改善機制
* 提示: 使用 [select](http://man7.org/linux/man-pages/man2/select.2.html) 系統呼叫
:::
- [ ] 在 `qtest` 提供新的命令 `shuffle`
:::spoiler
允許藉由 [Fisher–Yates shuffle](https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle) 演算法,對佇列中所有節點進行洗牌 (shuffle) 操作,需要以統計的原理來分析你的實作,探究洗牌的「亂度」
:::
- [ ] 在 `qtest` 中執行 `option entropy 1` 並搭配 `ih RAND 10` 一類的命令
:::spoiler
觀察亂數字串的分布,並運用「資訊與熵互補,資訊就是負熵」的觀念,設計一組新的命令或選項,得以在 `qtest` 切換不同的 PRNG 的實作 (可選定 [Xorshift](https://en.wikipedia.org/wiki/Xorshift)),搭配統計分析工具,比較亂數產生器 (如 Xorshift vs. 內建) 的品質
* 參見: [由大數法則理解訊息熵 (entropy) 的數學意義](https://hackmd.io/@8dSak6oVTweMeAe9fXWCPA/ryoegPgw_)
* 參見: [The Linux Pseudorandom Number Generator Revisited](https://eprint.iacr.org/2012/251.pdf)
:::
- [ ] 研讀論文〈[Dude, is my code constant time?](https://eprint.iacr.org/2016/1123.pdf)〉
:::spoiler
解釋本程式的 "simulation" 模式是如何透過以實驗而非理論分析,達到驗證時間複雜度,需要解釋 [Student's t-distribution](https://en.wikipedia.org/wiki/Student%27s_t-distribution) 及程式實作的原理。
* 注意:現有實作存在若干致命缺陷,請討論並提出解決方案
* 在 [oreparaz/dudect](https://github.com/oreparaz/dudect) 的程式碼具備 percentile 的處理,但在 [lab0-c](https://github.com/sysprog21/lab0-c) 則無。
:::
- [ ] 指出現有程式的缺陷或者可改進之處 (例如更全面地使用 Linux 核心風格的鏈結串列 API),嘗試實作並提交 pull request
- [ ] 詳閱第 1 週所有教材及作業描述 [(一)](/@sysprog/linux2023-lab0-a), [(二)](/@sysprog/linux2023-lab0-b), [(三)](/@sysprog/linux2023-lab0-c), [(四)](/@sysprog/linux2023-lab0-d), [(五)](/@sysprog/linux2023-lab0-e),記錄你的啟發和疑惑
- [ ] 解釋 [select](http://man7.org/linux/man-pages/man2/select.2.html) 系統呼叫在本程式的使用方式,並分析 [console.c](https://github.com/sysprog21/lab0-c/blob/master/console.c) 的實作
:::spoiler
說明其中運用 CS:APP [RIO 套件](http://csapp.cs.cmu.edu/2e/ch10-preview.pdf) 的原理和考量點。可對照參考 [CS:APP 第 10 章重點提示](https://hackmd.io/@sysprog/H1TtmVTTz)
:::
## 實作 queue.c
> Score: 95 / 100
### q_new
利用 `malloc` 配置記憶體。
若配置失敗先釋放記憶體空間,並回傳 ```NULL``` 值。
若成功配置,呼叫 ```list.h``` 的 ```INIT_LIST_HEAD``` 進行初始化。
> commit: [85b8cd1](https://github.com/willow11235811/lab0-c/commit/85b8cd13dfcf449faa1f8fe457bbdfded310c423)
:::danger
不用張貼完整程式碼。
:notes: jserv
> 已修改
:::
### q_free
先檢查是否為 ```NULL``` 佇列或是空的佇列,
利用 ```list.h``` 提供的 ```list_for_each_safe``` 走訪所有節點,
再使用 ```q_release_element``` 釋放節點內的所有 element。
commit: [325c1fe](https://github.com/willow11235811/lab0-c/commit/325c1fe16620b797587000de97805c7af608a008)
:::spoiler
```c
void q_free(struct list_head *l)
{
if (!l)
return;
if (list_empty(l)) {
free(l);
return;
}
struct list_head *node, *save;
list_for_each_safe(node, save, l)
q_release_element(container_of(node, element_t, list));
free(l);
}
```
:::
### q_insert_head
利用 ```list.h``` 提供的鏈結串列巨集和函式,可管理鏈結串列,
重點在於實作出新的節點和管理指標與記憶體。
藉由 ```malloc``` 和 ```strncpy``` 來獲取新的記憶體位置。
commit: [aae2f1c](https://github.com/willow11235811/lab0-c/commit/aae2f1c1f3bea14bde7b4419063f4ff1e6f735b0)
:::spoiler
```c
bool q_insert_head(struct list_head *head, char *s)
{
if (!head || !s)
return false;
element_t *new_ele = malloc(sizeof(element_t));
if (!new_ele) {
free(new_ele);
return false;
}
size_t len = strlen(s) + 1;
char *new_str = malloc(len * sizeof(char));
if (!new_str) {
free(new_str);
free(new_ele);
return false;
}
strncpy(new_str, s, head);
new_ele->value = new_str;
list_add(&new_ele->list, head);
return true;
}
```
:::
### q_insert_tail
與 ```q_insert_head``` 作法相同,改使用 ```list_add_tail``` 實作。
commit: [f04edd1](https://github.com/willow11235811/lab0-c/commit/f04edd1f3cc38f2996ce8701273df36da51b5f50)
:::spoiler
```c
bool q_insert_tail(struct list_head *head, char *s)
{
if (!head || !s)
return false;
element_t *new_ele = malloc(sizeof(element_t));
if (!new_ele) {
free(new_ele);
return false;
}
size_t len = strlen(s) + 1;
char *new_str = malloc(len * sizeof(char));
if (!new_str) {
free(new_str);
free(new_ele);
return false;
}
strncpy(new_str, s, len);
new_ele->value = new_str;
list_add_tail(&new_ele->list, head);
return true;
}
```
:::
### q_remove_head
要移除佇列的頭部且回傳被移除的元素,同時要將元素的字串資料儲存。
利用 ```list.h``` 提供的 ```container_of``` 可以找到頭部元素,並使用 ```list_del``` 移除元素,使用 ```strncpy``` 複製字串資料,並在最後加上 null terminator 。
commit: [7d4331c](https://github.com/willow11235811/lab0-c/commit/7d4331c7878ec2ac89dbde4dcbbaaa51d9767f8a), [ca27194](https://github.com/willow11235811/lab0-c/commit/ca27194a831fcd0f553c587dba21ad41ff295a0e)
:::spoiler
```c
element_t *q_remove_head(struct list_head *head, char *sp, size_t bufsize)
{
if (!head || list_empty(head))
return NULL;
element_t *tmp = container_of(head->next, element_t, list);
if (sp && bufsize) {
strncpy(sp, tmp->value, bufsize - 1);
*(sp + bufsize - 1) = '\0';
}
list_del(head->next);
return tmp;
}
```
:::
### q_remove_tail
與 q_remove_head 作法相同,改尋找尾部。
commit: [7aad974](https://github.com/willow11235811/lab0-c/commit/7aad974e1b4ca3cc0010ea2ff66d182bc1a9a3db), [ca27194](https://github.com/willow11235811/lab0-c/commit/ca27194a831fcd0f553c587dba21ad41ff295a0e)
:::spoiler
```c
element_t *q_remove_tail(struct list_head *head, char *sp, size_t bufsize)
{
if (!head || list_empty(head))
return NULL;
element_t *tmp = container_of(head->prev, element_t, list);
if (sp && bufsize) {
strncpy(sp, tmp->value, bufsize - 1);
*(sp + bufsize - 1) = '\0';
}
list_del(head->prev);
return tmp;
}
```
:::
### q_size
利用 ```list.h``` 提供的 ```list_for_each``` 走訪所有節點,這個實作的時間複雜度是 $O(N)$ 。
commit: [693b886](https://github.com/willow11235811/lab0-c/commit/693b886ca63fdffcd209fe2242fcdbfdab3f172b)
:::spoiler
```c
int q_size(struct list_head *head)
{
if (!head || list_empty(head))
return 0;
int size = 0;
struct list_head *count = NULL;
list_for_each (count, head)
size++;
return size;
}
```
:::
### q_delete_mid
利用快慢指標演算法 (Tortoise and Hare Algorithm) ,藉由快指標:慢指標移動速度2:1,當快指標走完整個 linked list 時,慢指標會到達中點。
commit: [17573d1](https://github.com/willow11235811/lab0-c/commit/17573d14933fb79049c6c29cbea39916b7ca7e0a)
:::spoiler
```c
bool q_delete_mid(struct list_head *head)
{
// https://leetcode.com/problems/delete-the-middle-node-of-a-linked-list/
if (!head || list_empty(head))
return false;
struct list_head *fast = head->next, *slow = head->next;
while (fast != head && fast->next != head)
slow = slow->next, fast = fast->next->next;
list_del(slow);
q_release_element(list_entry(slow, element_t, list));
return true;
}
```
:::
### q_delete_dup
一開始想利用 ```list_for_each_safe``` 走訪所有節點,但根據定義只允許刪除 ```node``` 節點,其他操作都可能造成 undefined behavior 。
所以利用 ```tmp``` 走訪 ```save``` 到尾段的節點,只要發現 ```node``` 與 ```tmp``` 有相同的 ```value``` ,就刪除 ```node``` 這個節點。
再利用 ```break``` 跳脫至最外層的 ```list_for_each_safe``` 繼續走訪剩下節點。
commit: [930e5db](https://github.com/willow11235811/lab0-c/commit/930e5db42ec4fe94345cbcc709a6f90f3491a50f)
:::spoiler Version 1
```c
bool q_delete_dup(struct list_head *head)
{
// https://leetcode.com/problems/remove-duplicates-from-sorted-list-ii/
if (!head)
return false;
struct list_head *node, *save, *tmp;
list_for_each_safe (node, save, head) {
for (tmp = save; tmp != head; tmp = tmp->next)
if (!strcmp(list_entry(node, element_t, list)->value,
list_entry(tmp, element_t, list)->value)) {
list_del(node);
q_release_element(list_entry(node, element_t, list));
break;
}
}
return true;
}
```
:::
:::danger
:bug: Bugs
隨後仔細閱讀 ```queue.h``` 發現 ```q_delete_dup``` 的要求是刪除「所有」重複的節點,
Version 1 會留下1個節點。
:::
#### Debug
加入 ```is_dup``` 判斷是否有連續的節點重複,但程式碼應該還能再精簡。
commit: [03d9191](https://github.com/willow11235811/lab0-c/commit/03d91915a6151531ec3aa3532ea86d60622e33f2)
:::info
:notebook_with_decorative_cover: ToDo
精簡 ```q_delete_dup```
:::
:::spoiler Version 2
```c
bool q_delete_dup(struct list_head *head)
{
// https://leetcode.com/problems/remove-duplicates-from-sorted-list-ii/
if (!head)
return false;
struct list_head *node, *save;
bool is_dup = false;
list_for_each_safe (node, save, head) {
if (node->next != head &&
!strcmp(list_entry(node, element_t, list)->value,
list_entry(save, element_t, list)->value)) {
is_dup = true;
list_del(node);
q_release_element(list_entry(node, element_t, list));
} else if (is_dup) {
is_dup = false;
list_del(node);
q_release_element(list_entry(node, element_t, list));
}
}
return true;
}
```
:::
### q_swap
利用 indirect pointer 的技巧,直接操作要互換節點的指標。
commit: [25817e5](https://github.com/willow11235811/lab0-c/commit/25817e521436b7196bafcc8c5a7abe768220b7bf)
:::spoiler Version 1
```c
void q_swap(struct list_head *head)
{
// https://leetcode.com/problems/swap-nodes-in-pairs/
if (!head)
return;
for (struct list_head **l = &head->next; *l != head && (*l)->next != head;
l = &(*l)->next) {
struct list_head *first = head, *second = head->next;
first->prev = second;
first->next = second->next;
second->prev = first->prev;
second->next = first;
*l = first;
}
}
```
:::
:::danger
:bug: Bugs
1. 出現無限迴圈
2. 指標操作應可用 ```list.h``` 提供的程式精簡
:::
#### Debug
使用 ```list_move``` 增加可讀性,並避免無限迴圈。
commit: [b9be753](https://github.com/willow11235811/lab0-c/commit/b9be753f88c2f9287de409f06061cf869b2aa8ea)
:::spoiler Version 2
```c
void q_swap(struct list_head *head)
{
// https://leetcode.com/problems/swap-nodes-in-pairs/
if (!head)
return;
struct list_head *node, *save;
for (node = head->next, save = node->next; node != head && save != head;
node = node->next, save = node->next) {
list_move(node, save);
}
}
```
:::
### q_reverse
```list.h``` 提供的 ```list_move``` 可將節點移到頭部,但操作時會刪除節點,
因此使用 ```list_for_each_safe``` 走訪所有節點。
commit: [df3a1e7](https://github.com/willow11235811/lab0-c/commit/df3a1e7c66323a697abfdff7195883236756657e)
:::spoiler
```c
void q_reverse(struct list_head *head)
{
if (!head || list_empty(head))
return;
struct list_head *node, *save;
list_for_each_safe (node, save, head)
list_move(node, head);
}
```
:::
### q_reverseK
要反轉 $K$ 個元素,會使用到 ```list.h``` 提供的 ```list_cut_position``` 和 ```list_splice``` 來切斷與接合反轉後的佇列。
先讓 ```node``` 前進 $K-1$ 個節點,再利用 ```tmp``` 作為儲存切斷後佇列的 buffer ,並反覆迭代直到尾部。
commit: [9070c81](https://github.com/willow11235811/lab0-c/commit/9070c817252848a832b72c20c6227eba072cfedc)
:::spoiler Version 1
```c
void q_reverseK(struct list_head *head, int k)
{
// https://leetcode.com/problems/reverse-nodes-in-k-group/
if (!head || list_empty(head) || list_is_singular(head) || k <= 1)
return;
int i = k;
struct list_head *node, *save, *cut = head;
list_for_each_safe (node, save, head) {
if (--i)
continue;
i = k;
LIST_HEAD(tmp);
list_cut_position(&tmp, cut, head);
q_reverse(&tmp);
list_splice(&tmp, cut);
cut = save->prev;
}
}
```
:::
:::danger
:bug: Bugs
雖可執行,但反轉後的佇列順序不對。
> 提交 `qtest` 檢查機制的強化改進?
> :notes: jserv
:::
#### Debug
```list_cut_position``` 應使用 ```node``` 帶入,否則切點不對。
commit: [c2b25d1](https://github.com/willow11235811/lab0-c/commit/c2b25d1c229d9ec2700924b4f54e0f612cbe7a35)
:::spoiler Version 2
```c
void q_reverseK(struct list_head *head, int k)
{
// https://leetcode.com/problems/reverse-nodes-in-k-group/
if (!head || list_empty(head) || list_is_singular(head) || k <= 1)
return;
int i = k;
struct list_head *node, *save, *cut = head;
list_for_each_safe (node, save, head) {
if (--i)
continue;
i = k;
LIST_HEAD(tmp);
list_cut_position(&tmp, cut, node);
q_reverse(&tmp);
list_splice(&tmp, cut);
cut = save->prev;
}
}
```
:::
### q_sort
使用 Merge Sort 演算法加上遞迴的形式進行實作。
利用快慢指標演算法 (Tortoise and Hare Algorithm) ,將佇列切成兩段。
在實作 ```merge_list``` 用以合併佇列,參考[@ItisCaleb](https://hackmd.io/@ItisCaleb/S1v9IQpai#q_sort)所實作的函數。
commit: [252b5b7](https://github.com/willow11235811/lab0-c/commit/252b5b7d335b7a1cde4f44f929752fe61a449a17)
:::spoiler Version 1
```c
static inline void merge_list(struct list_head *head,
struct list_head *left,
struct list_head *right)
{
while (!list_empty(left) && !list_empty(right)) {
if (strcmp(list_entry(left->next, element_t, list)->value,
list_entry(right->next, element_t, list)->value) < 0) {
list_move_tail(left->next, head);
} else {
list_move_tail(right->next, head);
}
}
if (!list_empty(left)) {
list_splice_tail_init(left, head);
} else {
list_splice_tail_init(right, head);
}
}
void q_sort(struct list_head *head)
{
/* Using merge sort */
if (!head || list_empty(head) || list_is_singular(head))
return;
/* Find the middle point */
struct list_head *fast, *slow;
for (fast = head, slow = head; fast != head && fast->next != head;) {
fast = fast->next->next;
slow = slow->next;
}
/* Divide into left and right parts */
LIST_HEAD(left);
LIST_HEAD(right);
list_splice_tail_init(head, &right);
list_cut_position(&left, &right, slow);
/* Recursion */
q_sort(&left);
q_sort(&right);
merge_list(head, &left, &right);
}
```
:::
:::danger
:bug: Bugs
出現無限迴圈
:::
#### Debug
呼叫 ```list_splice_tail_init``` 時會使 header 被初始化,其 ```*prev``` 和 ```*next``` 不會指向第一個節點,破壞 doubly-linked list 的結構。
改使用兩個 ```list_cut_position``` 。
commit: [522d558](https://github.com/willow11235811/lab0-c/commit/522d5584c371eaec91385c820f8abe637bc6c5ee)
:::spoiler Version2
```c
static inline void merge_list(struct list_head *head,
struct list_head *left,
struct list_head *right)
{
while (!list_empty(left) && !list_empty(right)) {
if (strcmp(list_entry(left->next, element_t, list)->value,
list_entry(right->next, element_t, list)->value) < 0) {
list_move_tail(left->next, head);
} else {
list_move_tail(right->next, head);
}
}
if (!list_empty(left)) {
list_splice_tail_init(left, head);
} else {
list_splice_tail_init(right, head);
}
}
void q_sort(struct list_head *head)
{
/* Using merge sort */
if (!head || list_empty(head) || list_is_singular(head))
return;
/* Find the middle point */
struct list_head *fast, *slow;
fast = head, slow = head;
do {
fast = fast->next->next;
slow = slow->next;
} while (fast != head && fast->next != head);
/* Divide into left and right parts */
LIST_HEAD(left);
LIST_HEAD(right);
list_cut_position(&left, head, slow);
list_cut_position(&right, head, head->prev);
/* Recursion */
q_sort(&left);
q_sort(&right);
merge_list(head, &left, &right);
}
```
:::
### q_descend
要刪除所有節點,只要該節點右側有比自己更大的 value 。
策略是假設尾部有佇列中的最大值 ```best``` ,從尾部往頭部依序搜尋,只要找到更小的值就刪除該節點。
過程中若遇到比現有最大值更大的 value ,就更新佇列的最大值 ```best``` 。
commit: [7379088](https://github.com/willow11235811/lab0-c/commit/73790880e9da6314a54ab24400e9d21607d0c1fa)
:::spoiler
```c
int q_descend(struct list_head *head)
{
// https://leetcode.com/problems/remove-nodes-from-linked-list/
if (!head || list_empty(head))
return 0;
element_t *elem = list_entry(head->prev, element_t, list),
*save = list_entry(elem->list.prev, element_t, list);
char *best = elem->value;
while (&elem->list != head) {
if (strcmp(elem->value, best) < 0) {
list_del(&elem->list);
q_release_element(elem);
} else {
best = elem->value;
}
elem = save;
save = list_entry(elem->list.prev, element_t, list);
}
return q_size(head);
}
```
:::
### q_merge
一開始策略是想利用 Merge sort 演算法,由佇列頭部往尾部兩兩合併佇列。
但實作時一直無法實現程式碼,最後參考 [@paul90317](https://hackmd.io/@paul90317/linux2023-spring-lab0#q_merge) 的 ```q_merge``` 實作。
:::info
:notebook_with_decorative_cover: ToDo
思考其他 ```q_merge``` 實作方式
:::
commit: [d082344](https://github.com/willow11235811/lab0-c/commit/d08234460d077569ab6f1851db561cc4cd3cd57d)
:::spoiler
```c
int q_merge(struct list_head *head)
{
// https://leetcode.com/problems/merge-k-sorted-lists/
if (!head || list_empty(head))
return 0;
int n = q_size(head);
while (n > 1) {
struct list_head *fast = head->next, *slow = head->next;
for (int i = 0; i < n / 2; ++i) {
LIST_HEAD(temp);
merge_list(&temp, container_of(fast, queue_contex_t, chain)->q,
container_of(fast->next, queue_contex_t, chain)->q);
list_splice_tail(&temp,
container_of(slow, queue_contex_t, chain)->q);
fast = fast->next->next;
slow = slow->next;
}
if (n & 1)
list_splice_tail_init(container_of(fast, queue_contex_t, chain)->q,
container_of(slow, queue_contex_t, chain)->q);
n = (n + 1) / 2;
}
return q_size(container_of(head->next, queue_contex_t, chain)->q);
}
```
:::