---
tag: 2022linuxclass
---
# 2022q1 Homework1 (lab0)
contributed by < `Wallmountain` >
## 實驗環境
```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
Address sizes: 39 bits physical, 48 bits virtual
CPU(s): 12
On-line CPU(s) list: 0-11
Thread(s) per core: 2
Core(s) per socket: 6
Socket(s): 1
NUMA node(s): 1
Vendor ID: GenuineIntel
CPU family: 6
Model: 158
Model name: Intel(R) Core(TM) i7-8750H CPU @ 2.20GHz
Stepping: 10
CPU MHz: 1637.560
CPU max MHz: 4100.0000
CPU min MHz: 800.0000
BogoMIPS: 4399.99
Virtualization: VT-x
L1d cache: 192 KiB
L1i cache: 192 KiB
L2 cache: 1.5 MiB
L3 cache: 9 MiB
NUMA node0 CPU(s): 0-11
```
## 作業要求
閱讀 [lab0](https://hackmd.io/@sysprog/linux2022-lab0) 說明,依據指示著手修改 queue.[ch] 和連帶的檔案,測試後用 Git 管理各項修改,並利用 [Cppcheck](http://cppcheck.sourceforge.net/) 和 [Valgrind](https://valgrind.org/) 作程式評估。
* `q_new`: 建立新的「空」佇列
* `q_free`: 釋放佇列所佔用的記憶體
* `q_insert_head`: 在佇列開頭 (head) 插入 (insert) 給定的新節點 (以 LIFO 準則)
* `q_insert_tail`: 在佇列尾端 (tail) 插入 (insert) 給定的新節點 (以 FIFO 準則)
* `q_remove_head`: 在佇列開頭 (head) 移去 (remove) 給定的節點
* `q_release_element`: 釋放特定節點的記憶體空間
* `q_size`: 計算目前佇列的節點總量
* `q_delete_mid`: 移走佇列中間的節點
* `q_delete_dup`: 在已經排序的狀況,移走佇列中具備重複內容的節點
* `q_swap`: 交換佇列中鄰近的節點
* `q_reverse`: 以反向順序重新排列鏈結串列,該函式不該配置或釋放任何鏈結串列節點,換言之,它只能重排已經存在的節點
* `q_sort`: 以==遞增順序==來排序鏈結串列的節點
## 開發過程
### q_new
建立空佇列,檢查 malloc 是否正常運作
```cpp
struct list_head *q_new()
{
struct list_head *tmp =
(struct list_head *) malloc(sizeof(struct list_head));
if (!tmp) {
return NULL;
}
INIT_LIST_HEAD(tmp);
return tmp;
}
```
### q_free
釋放所有佇列所佔記憶體
```cpp
void q_free(struct list_head *l)
{
if (!l) {
return;
}
element_t *entry, *safe;
list_for_each_entry_safe (entry, safe, l, list) {
list_del_init(&entry->list);
q_release_element(entry);
}
free(l);
}
```
### q_insert_head
檢查 malloc 有沒有給指定大小的記憶體,字串結尾要加 ``'\0'``
```cpp
bool q_insert_head(struct list_head *head, char *s)
{
if (!head) {
return false;
}
element_t *node = (element_t *) malloc(sizeof(element_t));
if (!node) {
return false;
}
int length = strlen(s);
node->value = (char *) malloc(length + 1);
if (!node->value) {
q_release_element(node);
return false;
}
for (int i = 0; i < length; i++) {
node->value[i] = s[i];
}
node->value[length] = '\0';
list_add(&node->list, head);
return true;
}
```
### q_insert_tail
跟 insert head 基本相同
```cpp
bool q_insert_tail(struct list_head *head, char *s)
{
if (!head) {
return false;
}
element_t *node = (element_t *) malloc(sizeof(element_t));
if (!node) {
return false;
}
int length = strlen(s);
node->value = (char *) malloc(length + 1);
if (!node->value) {
q_release_element(node);
return false;
}
for (int i = 0; i < length; i++) {
node->value[i] = s[i];
}
node->value[length] = '\0';
list_add_tail(&node->list, head);
return true;
}
```
### q_size
計算目前佇列的節點總量
```cpp
int q_size(struct list_head *head)
{
if (!head || list_empty(head)) {
return 0;
}
struct list_head *node;
int size = 0;
list_for_each (node, head) {
size++;
}
return size;
}
```
### q_delete_mid
移走佇列中間的節點, 使用前面的 q_size 得到佇列長度
```cpp
bool q_delete_mid(struct list_head *head)
{
if (!head || list_empty(head)) {
return false;
}
struct list_head *node = head->next;
int size = q_size(head) / 2;
while (size--) {
node = node->next;
}
list_del_init(node);
q_release_element(list_entry(node, element_t, list));
return true;
}
```
### q_delete_dup
刪除值重複的 node
```cpp
bool q_delete_dup(struct list_head *head)
{
if (!head) {
return false;
}
struct list_head *node = head->next;
while (node->next != head) {
element_t *tmp = list_entry(node->next, element_t, list);
if (strcmp(list_entry(node, element_t, list)->value, tmp->value) == 0) {
list_del_init(node->next);
q_release_element(tmp);
} else {
node = node->next;
}
}
return true;
}
```
### q_swap
交換佇列中鄰近的節點,不用額外指標便可 swap
```cpp
void q_swap(struct list_head *head)
{
if (!head) {
return;
}
struct list_head *node = head->next;
while (node->next != head) {
// swap
node->next->prev = node->prev;
node->prev->next = node->next;
node->next->next->prev = node;
node->prev = node->next;
node->next = node->next->next;
node->prev->next = node;
node = node->next;
}
}
```
### q_reverse
以反向順序重新排列鏈結串列, trace 過整個佇列一個一個把他們的 prev 和 next 交換
```cpp
void q_reverse(struct list_head *head)
{
if (!head || list_empty(head)) {
return;
}
struct list_head *tmp = head->next;
head->next = head->prev;
head->prev = tmp;
struct list_head *node;
list_for_each (node, head) {
tmp = node->next;
node->next = node->prev;
node->prev = tmp;
}
}
```
### q_sort
一開始,下意識以為就是要作 quicksort, 然後打完才發現, 而且 quicksort 的 worst case 為 $O(n^2)$,故下面就繼續實作老師上課提到的 mergesort
#### quicksort
```cpp
void q_sort(struct list_head *head)
{
if (!head || head->next == head->prev) {
return;
}
struct list_head *left, *right, *pivot;
left = q_new();
right = q_new();
pivot = head->next;
list_del(pivot);
// partition
while (!list_empty(head)) {
struct list_head *tmp, *t_head;
tmp = head->next;
list_del(tmp);
t_head = (strcmp(list_entry(pivot, element_t, list)->value,
list_entry(tmp, element_t, list)->value) < 0)
? right
: left;
list_add_tail(tmp, t_head);
}
q_sort(left);
q_sort(right);
list_add_tail(pivot, left);
list_splice_tail(right, left);
q_release_element(list_entry(right, element_t, list));
}
```
#### mergesort
參照 jserv 的實作,在 mergesort 當中當作queue為單向的
```cpp
struct list_head *mergetwoqueues(struct list_head *left,
struct list_head *right)
{
struct list_head *head = NULL;
struct list_head **ptr = &head, **node = NULL;
for (; left && right; *node = (*node)->next) {
node = (strcmp(list_entry(left, element_t, list)->value,
list_entry(right, element_t, list)->value) < 0)
? &left
: &right;
*ptr = *node;
ptr = &(*ptr)->next;
}
*ptr = (struct list_head *) ((uintptr_t) left | (uintptr_t) right);
return head;
}
struct list_head *mergesort_list(struct list_head *head)
{
if (!head || !head->next) {
return head;
}
struct list_head *slow = head, *right;
for (struct list_head *fast = head->next; fast && fast->next;
fast = fast->next->next)
slow = slow->next;
right = slow->next;
slow->next = NULL;
head = mergesort_list(head);
right = mergesort_list(right);
return mergetwoqueues(head, right);
}
/*
* Sort elements of queue in ascending order
* No effect if q is NULL or empty. In addition, if q has only one
* element, do nothing.
*/
void q_sort(struct list_head *head)
{
if (!head || list_empty(head) || list_is_singular(head)) {
return;
}
struct list_head *node = head->next;
head->prev->next = NULL;
node = mergesort_list(node);
head->next = node;
for (node = head; node->next; node = node->next) {
node->next->prev = node;
}
node->next = head;
head->prev = node;
}
```
## 提供新的命令 shuffle
觀察如何加入新命令, 找到 ```ADD_COMMAND``` 巨集的定義
``` cpp
#define ADD_COMMAND(cmd, msg) add_cmd(#cmd, do_##cmd, msg)
```
將 ```shuffle``` 加入命令
``` cpp
ADD_COMMAND(shuffle, "| Fisher-Yates shuffle algorithm");
```
模仿 ```do_sort``` 實作 ```do_shuffle```
```cpp
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 sort on null queue");
error_check();
int cnt = q_size(l_meta.l);
if (cnt < 2)
report(3, "Warning: Calling sort on single node");
error_check();
set_noallocate_mode(true);
if (exception_setup(true))
q_shuffle(l_meta.l);
exception_cancel();
set_noallocate_mode(false);
show_queue(3);
return !error_check();
}
```
### q_shuffle
依據 [Fisher–Yates shuffle](https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle) 作出的函式,隨機選出node搬到最後,每次都減少能選取範圍,直到node都已經被選取過
```cpp
void q_shuffle(struct list_head *head)
{
int len = q_size(head);
if (len < 2)
return;
for (; len > 1; len--) {
int n = rand() % len;
struct list_head *cur = head->next;
for (; n; n--) {
cur = cur->next;
}
list_del(cur);
list_add_tail(cur, head);
}
}
```
## 參考資料
* [laneser](https://hackmd.io/@laneser/linux2022_lab0)