# 2022q1 Homework1 (lab0)
contributed by < `vacantron` >
## 實作進度
- [x] q_new: 建立新的「空」佇列
- [x] q_free: 釋放佇列所佔用的記憶體
- [x] q_insert_head: 在佇列開頭 (head) 插入 (insert) 給定的新節點 (以 LIFO 準則)
- [x] q_insert_tail: 在佇列尾端 (tail) 插入 (insert) 給定的新節點 (以 FIFO 準則)
- [x] q_remove_head: 在佇列開頭 (head) 移去 (remove) 給定的節點
- [x] q_release_element: 釋放特定節點的記憶體空間
- [x] q_size: 計算目前佇列的節點總量
- [x] q_delete_mid: 移走佇列中間的節點
- [x] q_delete_dup: 在已經排序的狀況,移走佇列中具備重複內容的節點
- [x] q_swap: 交換佇列中鄰近的節點
- [x] q_reverse: 以反向順序重新排列鏈結串列,該函式不該配置或釋放任何鏈結串列節點,換言之,它只能重排已經存在的節點
- [x] q_sort: 以遞增順序來排序鏈結串列的
----
命令列:
- [x] shuffle: 藉由 Fisher–Yates shuffle 演算法,對佇列中所有節點進行洗牌 (shuffle) 操作
- [ ] web: 提供 web 伺服器功能。注意: web 伺服器運作過程中,qtest 仍可接受其他命令
## 實驗環境
```shell
$ gcc --version
gcc (Ubuntu 9.3.0-17ubuntu1~20.04) 9.3.0
$ lscpu
Architecture: x86_64
CPU op-mode(s): 32-bit, 64-bit
Byte Order: Little Endian
CPU(s): 16
On-line CPU(s) list: 0-15
Thread(s) per core: 2
Core(s) per socket: 8
Socket(s): 1
NUMA node(s): 1
Vendor ID: AuthenticAMD
CPU family: 23
Model: 96
Model name: AMD Ryzen 7 4800H with Radeon Graphics
Stepping: 1
Frequency boost: enabled
CPU MHz: 1400.000
CPU max MHz: 2900.0000
CPU min MHz: 1400.0000
BogoMIPS: 5788.90
Virtualization: AMD-V
L1d cache: 256 KiB
L1i cache: 256 KiB
L2 cache: 4 MiB
L3 cache: 8 MiB
NUMA node0 CPU(s): 0-15
```
## 開發過程
#### q_new
```c
struct list_head *q_new()
{
struct list_head *node = malloc(sizeof(struct list_head));
if (!node)
return NULL;
INIT_LIST_HEAD(node);
return node;
}
```
#### q_free
使用 `container_of` 巨集從結構體的成員指標中獲取該結構體的指標
```c
void q_free(struct list_head *l)
{
if (!l)
return;
while (!list_empty(l)) {
struct list_head *curr = l->next;
list_del(curr);
q_release_element(container_of(curr, element_t, list));
}
list_del(l);
free(l);
}
```
#### q_insert_head
最初使用 `strcpy()` 函式複製字串,但是在向 git 提交時顯示 `dangerous function` 提醒,查閱所提供的 [Common vulnerabilities guide for C programmers](https://security.web.cern.ch/security/recommendations/en/codetools/c.shtml) 後,發現如果目的地的空間不足可能會覆寫到非預期的記憶體地址,進而導致錯誤
:::warning
何謂「被貼上」?理工人說話要精準
:notes: jserv
:::
:::info
已改善用詞
:::
之後改用 `strncpy()` 函式,但需要指定要複製的字串長度。因為 C 語言需要一個 `\0` 字元作為字串的結尾,故在計算字元數時需要特別注意。另外因為 `strlen()` 函式所回傳的字串長度並不包含中止字元,故在分配記憶體空間時額外增加一個 byte
```c
bool q_insert_head(struct list_head *head, char *s)
{
if (!head)
return false;
element_t *el = malloc(sizeof(element_t));
if (!el)
return false;
size_t buf = strlen(s) + 1;
el->value = malloc(buf);
if (!el->value) {
free(el);
return false;
}
strncpy(el->value, s, buf);
list_add(&el->list, head);
return true;
}
```
#### q_insert_tail
```c
bool q_insert_tail(struct list_head *head, char *s)
{
if (!head)
return false;
element_t *el = malloc(sizeof(element_t));
if (!el)
return false;
size_t buf = strlen(s) + 1;
el->value = malloc(buf);
if (!el->value) {
free(el);
return false;
}
strncpy(el->value, s, buf);
list_add_tail(&el->list, head);
return true;
}
```
#### q_remove_head
因為有限制回傳字串的長度,故需要切割原本的字串,並在尾端加上中止字元
```c
element_t *q_remove_head(struct list_head *head, char *sp, size_t bufsize)
{
if (!head || list_empty(head))
return NULL;
struct list_head *curr = head->next;
list_del_init(curr);
element_t *el = list_entry(curr, element_t, list);
if (sp) {
strncpy(sp, el->value, bufsize - 1);
sp[bufsize - 1] = '\0';
}
return el;
}
```
#### q_release_element
```c
void q_release_element(element_t *e)
{
free(e->value);
free(e);
}
```
#### q_size
```c
int q_size(struct list_head *head)
{
if (!head || list_empty(head))
return 0;
int cnt = 0;
struct list_head *curr = head->next;
while (curr != head) {
++cnt;
curr = curr->next;
}
return cnt;
}
```
#### q_delete_mid
使用右移運算子取代原本除以 2 並無條件捨去的操作
```c
bool q_delete_mid(struct list_head *head)
{
if (!head || list_empty(head))
return NULL;
int n = q_size(head) >> 1;
struct list_head *curr = head->next;
for (size_t i = 0; i < n; i++) {
curr = curr->next;
}
list_del(curr);
q_release_element(list_entry(curr, element_t, list));
return true;
}
```
#### q_delete_dup
誤會題意,以為是要刪除有重複字元的節點,應該是要刪除擁有相同字串的節點才對
```c
bool q_delete_dup(struct list_head *head)
{
if (!head || list_empty(head))
return NULL;
element_t *s = list_entry(head->next, element_t, list);
element_t *t = list_entry(head->next->next, element_t, list);
while (&t->list != head) {
size_t len = strlen(s->value) > strlen(t->value) ? strlen(t->value)
: strlen(s->value);
if (strncmp(s->value, t->value, ++len) == 0) {
element_t *next = list_entry(t->list.next, element_t, list);
list_del(&t->list);
q_release_element(t);
t = next;
} else {
s = t;
t = list_entry(t->list.next, element_t, list);
}
}
return true;
}
```
#### q_swap
:::spoiler 紀錄
<br>
更正無法處理奇數節點的錯誤
```c
void q_swap(struct list_head *head)
{
int n = q_size(head) >> 1;
bool is_odd = q_size(head) % 2;
struct list_head *prev = head, *curr = head->next;
for (size_t i = 0; i < n; i++) {
struct list_head *next = curr->next, *tmp;
prev->next = next;
tmp = next->next;
next->prev = prev;
next->next = curr;
curr->prev = next;
prev = curr;
curr = tmp;
}
if (is_odd) {
prev->next = head->prev;
head->prev->prev = prev;
} else {
prev->next = head;
head->prev = prev;
}
}
```
:::
<br>
改用 Linux kernel API 的更簡潔的實作
```c
void q_swap(struct list_head *head)
{
struct list_head *node, *safe;
list_for_each_safe (node, safe, head) {
if (node->next == head)
continue;
list_del(node);
list_add(node, safe);
safe = node->next;
}
}
```
#### q_reverse
:::spoiler 紀錄
<br>
```c
void q_reverse(struct list_head *head)
{
if (!head)
return;
if (list_empty(head))
return;
struct list_head *curr = head, *tmp = head->next;
while (tmp != head) {
struct list_head *next = tmp;
curr->prev = next;
tmp = next->next;
next->next = curr;
curr = next;
}
curr->prev = head;
head->next = curr;
}
```
:::
<br>
使用 Linux kernel API 簡化
```c
void q_reverse(struct list_head *head)
{
if (!head || list_empty(head))
return;
struct list_head *node, *safe;
list_for_each_safe (node, safe, head) {
node->next = node->prev;
node->prev = safe;
}
node = head->prev;
head->prev = head->next;
head->next = node;
}
```
#### q_sort
:::spoiler bubble sort 紀錄
```c
void swap(element_t *, element_t *);
void q_sort(struct list_head *head)
{
if (!head)
return;
if (list_empty(head))
return;
bool is_swapped = false;
int displacement = q_size(head) - 1;
do {
is_swapped = false;
element_t *s = list_entry(head->next, element_t, list);
element_t *t = list_entry(head->next->next, element_t, list);
for (size_t i = 0; i < displacement; i++) {
size_t len = strlen(s->value) > strlen(t->value) ? strlen(t->value)
: strlen(s->value);
if (strncmp(s->value, t->value, ++len) > 0) {
swap(s, t);
is_swapped = true;
}
s = t;
t = list_entry((&s->list)->next, element_t, list);
}
if (!is_swapped)
--displacement;
} while (is_swapped);
}
void swap(element_t *s, element_t *t)
{
struct list_head *prev = (&s->list)->prev;
struct list_head *next = (&t->list)->next;
(&s->list)->prev = &t->list;
(&s->list)->next = next;
(&t->list)->prev = prev;
(&t->list)->next = &s->list;
prev->next = &t->list;
next->prev = &s->list;
}
```
:::
<br>
使用遞迴式實作 merge sort
使用 circular-doubly-linked list 能更方便的找到頭尾而不必再走訪整個鏈結串列,但被限制無法在 `q_sort` 內配置額外的記憶體,故改變原本的鏈結串列結構,將 `head` 指標暫時從串列中移除
因為以上的操作造成部份 API 無法使用,故新增函式方便操作
```c
int pseudo_size(struct list_head *pseudo_head)
{
int cnt = 1;
struct list_head *curr = pseudo_head->next;
while (curr != pseudo_head) {
++cnt;
curr = curr->next;
}
return cnt;
}
```
```c
void q_sort(struct list_head *head)
{
if (!head || list_empty(head))
return;
list_del(head);
struct list_head *pseudo_head = merge_sort(head->next);
pseudo_head->prev->next = head;
head->prev = pseudo_head->prev;
pseudo_head->prev = head;
head->next = pseudo_head;
}
struct list_head *merge_sort(struct list_head *head)
{
size_t len = pseudo_size(head);
if (len == 1)
return head;
len = len >> 1;
struct list_head *lhs = head, *rhs = head;
for (size_t i = 0; i < len; i++) {
rhs = rhs->next;
}
struct list_head *tail = head->prev;
lhs->prev = rhs->prev;
lhs->prev->next = lhs;
rhs->prev = tail;
rhs->prev->next = rhs;
return merge(merge_sort(lhs), merge_sort(rhs));
}
struct list_head *merge(struct list_head *l, struct list_head *r)
{
struct list_head *iterator = l, *curr = r;
for (; iterator; curr = curr->next) {
char *l_char = list_entry(l, element_t, list)->value;
char *curr_char = list_entry(curr, element_t, list)->value;
size_t len = strlen(l_char) > strlen(curr_char) ? strlen(curr_char)
: strlen(l_char);
if (strncmp(l_char, curr_char, ++len) <= 0) {
l->next->prev = l->prev;
l->prev->next = l->next;
iterator = l->next;
if (iterator == l)
iterator = NULL;
curr->prev->next = l;
l->prev = curr->prev;
curr->prev = l;
l->next = curr;
if (curr == r)
r = l;
curr = curr->prev;
l = iterator;
} else if (curr == r->prev) {
struct list_head *tail = iterator->prev;
iterator->prev = r->prev;
r->prev->next = iterator;
r->prev = tail;
tail->next = r;
return r;
}
}
return r;
}
```
## 命令列
#### shuffle
修改 `qtest.c` 檔案,新增 `shuffle` 命命
```diff=808
static void console_init()
{
......
+ ADD_COMMAND(shuffle,
+ " | Shuffle all queue elements with "
+ "Fisher-Yates shuffle algorithm");
......
}
```
新增 `do_suffle` 函式。其中可以藉由 `argc` 檢查是否有輸入多餘的參數
```c
void q_shuffle(struct list_head *head)
{
if (!head || list_empty(head))
return;
size_t len = q_size(head), idx = 0;
for (size_t i = 0; i < len - 1; i++, idx++) {
long location = random() % (len - idx);
struct list_head *node = head->next;
for (size_t j = 0; j < location; j++) {
node = node->next;
}
list_del(node);
list_add_tail(node, head);
}
}
static bool do_shuffle(int argc, char *argv[])
{
if (argc != 1) {
report(1, "%s takes no arguments", argv[0]);
return false;
}
if (!l_meta.l)
report(3, "Warning: Calling insert head on null queue");
error_check();
if (exception_setup(true))
q_shuffle(l_meta.l);
exception_cancel();
show_queue(3);
return !error_check();
}
```