---
# System prepended metadata

title: 2022q1 Homework1 (lab0)
tags: [linux2022]

---

---
tags: linux2022
---

# 2022q1 Homework1 (lab0)
contributed by < `zxcj04` >

> [作業要求](https://hackmd.io/@sysprog/linux2022-lab0)

## 實驗環境
```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:                   43 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:                       AuthenticAMD
CPU family:                      23
Model:                           1
Model name:                      AMD Ryzen 5 1600 Six-Core Processor
Stepping:                        1
Frequency boost:                 enabled
CPU MHz:                         1377.939
CPU max MHz:                     3200.0000
CPU min MHz:                     1550.0000
BogoMIPS:                        6387.35
Virtualization:                  AMD-V
L1d cache:                       192 KiB
L1i cache:                       384 KiB
L2 cache:                        3 MiB
L3 cache:                        16 MiB
NUMA node0 CPU(s):               0-11
```

## 開發紀錄

### q_new
```c
struct list_head *q_new()
{
    struct list_head *head =
        (struct list_head *) malloc(sizeof(struct list_head));
    if (!head)
        return NULL;
    INIT_LIST_HEAD(head);
    return head;
}
```

### q_free
逐一走訪鏈結串列的節點前，先檢查輸入的指標是否有效。
由於涉及到 list entry 的 delete 以及 node 的 remove 因此使用`list_for_each_entry_safe`
一開始忽略了 entry 中的 value 也需要 free 所以卡了一陣子。
```c
void q_free(struct list_head *l)
{
    if (!l)
        return;

    element_t *entry, *safe;
    list_for_each_entry_safe (entry, safe, l, list) {
        free(entry->value);
        free(entry);
    }
    free(l);
}
```

### q_insert_head
對 Linux 核心風格的 circular doubly-linked list 不夠熟悉。看了〈[你所不知道的 C 語言: linked list 和非連續記憶體](https://hackmd.io/@sysprog/c-linked-list#Linked-list-%E5%9C%A8-Linux-%E6%A0%B8%E5%BF%83%E5%8E%9F%E5%A7%8B%E7%A8%8B%E5%BC%8F%E7%A2%BC)〉及其錄影兩遍後才感覺有點理解這種設計的思路。
使用 Linux 核心風格 API 的程式碼是在參考了 [@jasperlin1996-q_insert_head](https://hackmd.io/@jasperlin1996/linux2022-lab0#q_insert_head) 的方法後寫出來。
```c
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;
    size_t len = strlen(s) + 1;
    node->value = (char *) malloc(sizeof(char) * len);
    if (!node->value) {
        free(node);
        return false;
    }
    strncpy(node->value, s, len);

    LIST_HEAD(list);
    node->list = list;
    list_add(&node->list, head);
    return true;
}
```

### q_remove_head
逐一走訪鏈結串列的節點前，先檢查輸入的指標是否有效以及串列中是否存在節點。
使用 `strnlen` 以確保即將被複製的字串長度為 bufsize
```c
element_t *q_remove_head(struct list_head *head, char *sp, size_t bufsize)
{
    if (!head || list_empty(head))
        return NULL;

    element_t *del_entry = list_first_entry(head, element_t, list);
    size_t len = strnlen(del_entry->value, bufsize - 1) + 1;
    strncpy(sp, del_entry->value, len);

    list_del(&del_entry->list);
    return del_entry;
}
```

後來發現不需要使用 `strnlen` 來檢查長度
只要用 `strncpy` 並在字串結尾補上 `\0` 即可
(參考 @laneser 及 @japerlin1996 的筆記)
```c
element_t *q_remove_head(struct list_head *head, char *sp, size_t bufsize)
{
    if (!head || list_empty(head))
        return NULL;

    element_t *del_entry = list_first_entry(head, element_t, list);
    if (sp) {
        strncpy(sp, del_entry->value, bufsize);
        sp[bufsize - 1] = '\0';
    }

    list_del(&del_entry->list);
    return del_entry;
}
```


### q_remove_tail
```c
element_t *q_remove_tail(struct list_head *head, char *sp, size_t bufsize)
{
    if (!head || list_empty(head))
        return NULL;

    element_t *del_entry = list_last_entry(head, element_t, list);
    if (sp) {
        strncpy(sp, del_entry->value, bufsize);
        sp[bufsize - 1] = '\0';
    }

    list_del(&del_entry->list);
    return del_entry;
}
```

### q_delete_mid
使用了 〈[你所不知道的 C 語言: linked list 和非連續記憶體](https://hackmd.io/@sysprog/c-linked-list)〉中的快慢指標技巧找到中間的節點，並從串列中刪除後釋放記憶體。
```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 *slow = head->next, *fast = 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_swap
這邊我原本是先取的要交換的兩個節點，並且去調整前後節點的 next 以及 prev 來完成交換。
但覺得不太優雅，因此在參考了 @laneser 的作法後發現實際上是作一樣的動作，但可以利用 Linux 核心風格 API 來簡化程式碼。 
```c
void q_swap(struct list_head *head)
{
    // https://leetcode.com/problems/swap-nodes-in-pairs/
    if (!head || list_empty(head) || list_is_singular(head))
        return;

    struct list_head *now;
    for (now = head->next; now != head && now->next != head; now = now->next) {
        struct list_head *next = now->next;
        list_del(now);
        list_add(now, next);
    }
}
```

### q_delete_dup
一開始誤會題意，以為是要把重複的留下一個，仔細看過後發現要把所有重複的節點都刪掉會有點麻煩，嘗試過用一個 flag 來辨別，也試過用另一個字串來紀錄重複的字串，最後還是選擇使用 flag 的方式，會繼續思考有沒有辦法簡化現在的程式碼。
```c
bool q_delete_dup(struct list_head *head)
{
    // https://leetcode.com/problems/remove-duplicates-from-sorted-list-ii/
    if (!head)
        return false;
    if (list_empty(head) || list_is_singular(head))
        return true;

    element_t *now, *next;
    bool is_dup = false;
    list_for_each_entry_safe (now, next, head, list) {
        if (now->list.next != head && strcmp(now->value, next->value) == 0) {
            list_del(&now->list);
            q_release_element(now);
            is_dup = true;
        } else if (is_dup) {
            list_del(&now->list);
            q_release_element(now);
            is_dup = false;
        }
    }

    return true;
}
```

### q_reverse
逐一走訪鏈結串列的節點，將所有節點的 next 及 prev 交換。
```c
void q_reverse(struct list_head *head)
{
    if (!head || list_empty(head) || list_is_singular(head))
        return;

    struct list_head *now = head;
    do {
        struct list_head *tmp = now->next;
        now->next = now->prev;
        now->prev = tmp;

        now = now->next;
    } while (now != head);
}
```

### q_sort
使用了我比較熟悉的 merge sort 來實作，但其中要去確保節點的 next 及 prev 都是正確的讓程式碼多了很多不優雅的部份。
參考了 @laneser 的方法之後豁然開朗，原來可以在一開始斷開 head 的 prev ，然後直接當成 singly-linked list 來做，最後再重建 prev。

目前這個版本在 make test 的 trace-14-perf 會不通過。
```c
struct list_head *merge_sorted(struct list_head *list1, struct list_head *list2)
{
    if (!list1)
        return list2;
    if (!list2)
        return list1;

    element_t *cmp1 = list_entry(list1, element_t, list);
    element_t *cmp2 = list_entry(list2, element_t, list);
    if (strcmp(cmp1->value, cmp2->value) <= 0) {
        list1->next = merge_sorted(list1->next, list2);
        return list1;
    } else {
        list2->next = merge_sorted(list1, list2->next);
        return list2;
    }
}

struct list_head *merge_sort(struct list_head *head)
{
    if (!head || !head->next)
        return head;
    struct list_head *slow = head, *fast = head->next;
    while (fast && fast->next) {
        slow = slow->next;
        fast = fast->next->next;
    }
    fast = slow->next;
    slow->next = NULL;
    return merge_sorted(merge_sort(head), merge_sort(fast));
}

void q_sort(struct list_head *head)
{
    if (!head || list_empty(head) || list_is_singular(head))
        return;
    struct list_head *list = head->next;
    head->prev->next = NULL;
    list = merge_sort(list);
    head->next = list;

    // rebuild prev link
    struct list_head *i = head;
    while (i->next != NULL) {
        i->next->prev = i;
        i = i->next;
    }
    head->prev = i;
    i->next = head;
}
```

將 merge_sorted 中的遞迴改成疊代後終於通過測試了。

```c
struct list_head *merge_sorted(struct list_head *list1, struct list_head *list2)
{
    struct list_head *result = NULL, *now = result;
    while (true) {
        element_t *cmp1 = list_entry(list1, element_t, list);
        element_t *cmp2 = list_entry(list2, element_t, list);
        if (strcmp(cmp1->value, cmp2->value) <= 0) {
            if (result == NULL) {
                result = list1;
                now = result;
            } else {
                now->next = list1;
                now = now->next;
            }
            list1 = list1->next;
            if (!list1) {
                now->next = list2;
                break;
            }
        } else {
            if (result == NULL) {
                result = list2;
                now = result;
            } else {
                now->next = list2;
                now = now->next;
            }
            list2 = list2->next;
            if (!list2) {
                now->next = list1;
                break;
            }
        }
    }
    return result;
}
```

## 在 qtest 提供新的命令 shuffle

在 qtest.c 的 `console_init` 中加入
```c
ADD_COMMAND(shuffle, "                | Shuffle queue");
```

並實作 `do_shuffle` 函式
```c
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: Try to access null queue");
    error_check();

    srand((unsigned int) (time(NULL)));
    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();
}
```

在 queue.h 及 queue.c 中加入 `q_shuffle` 的定義並實作

```c
void swap_node(struct list_head *node1, struct list_head *node2)
{
    struct list_head *tmp = node1->prev;
    list_move(node1, node2);
    list_move(node2, tmp);
}

struct list_head *list_idx(struct list_head *head, int idx)
{
    do {
        head = head->next;
    } while (idx--);
    return head;
}

void q_shuffle(struct list_head *head)
{
    if (!head || list_empty(head) || list_is_singular(head))
        return;

    int len = q_size(head);
    for (int i = len - 1; i > 0; i--) {
        int idx = rand() % (i + 1);
        swap_node(list_idx(head, idx), list_idx(head, i));
    }
}
```

結果發現因為修改到了 queue.h ，所以在 git commit 的時候會被擋下來，因此放棄在 queue.c 中實作，改為直接將 `q_shuffle` 放在 qtest.c 中。

