# 2022q1 Homework1 (lab0) contributed by <`jttony`> ## 開發環境 ```bash $ gcc --version gcc (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0 $ 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): 1 On-line CPU(s) list: 0 Vendor ID: GenuineIntel Model name: 12th Gen Intel(R) Core(TM) i5-12500H CPU family: 6 Model: 154 Thread(s) per core: 1 Core(s) per socket: 1 Socket(s): 1 Stepping: 3 BogoMIPS: 6220.80 Flags: fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov p at pse36 clflush mmx fxsr sse sse2 ht syscall nx rdtscp lm cons tant_tsc rep_good nopl xtopology nonstop_tsc cpuid tsc_known_fr eq pni pclmulqdq monitor ssse3 cx16 sse4_1 sse4_2 x2apic movbe popcnt aes xsave avx rdrand hypervisor lahf_lm abm 3dnowprefetc h fsgsbase bmi1 avx2 bmi2 invpcid rdseed clflushopt md_clear fl ush_l1d arch_capabilities Virtualization features: Hypervisor vendor: KVM Virtualization type: full Caches (sum of all): L1d: 48 KiB (1 instance) L1i: 32 KiB (1 instance) L2: 1.3 MiB (1 instance) L3: 18 MiB (1 instance) NUMA: NUMA node(s): 1 NUMA node0 CPU(s): 0 Vulnerabilities: Gather data sampling: Not affected Itlb multihit: Not affected L1tf: Not affected Mds: Not affected Meltdown: Not affected Mmio stale data: Not affected Retbleed: Not affected Spec store bypass: Vulnerable Spectre v1: Mitigation; usercopy/swapgs barriers and __user pointer sanitiz ation Spectre v2: Mitigation; Retpolines, STIBP disabled, RSB filling, PBRSB-eIBR S Not affected Srbds: Not affected Tsx async abort: Not affected ``` ## 開發過程 > refer to the <`alanjian85`>, <`yanjiew1`> homework ### Linked list in linux kernel Linked list in linux kernel is a circular linked list as below ref ->[你所不知道的 C 語言: linked list 和非連續記憶體](https://hackmd.io/@sysprog/c-linked-list) ![](https://hackmd.io/_uploads/rySf1Fcya.png) ![](https://hackmd.io/_uploads/SJGXyFq16.png) ### Preview 1. All the functions below would refer to the data structure of linked list in linux kernel 2. Study the `list.h` in the lab0-c project and [The Linux Kernel API](https://www.kernel.org/doc/html/latest/core-api/kernel-api.html) first 3. Study the [你所不知道的 C 語言: linked list 和非連續記憶體](https://hackmd.io/@sysprog/c-linked-list) and the pointer in C ### `q_new()` Create an empty queue whose next and prev pointer point to itself, return `NULL` when allocated failed 1. Linux Kernel API provides `INIT_LIST_HEAD` function with initializes the list_head to point to itself. 2. When malloc encounters an error, it returns NULL; otherwise it returns a pointer to the allocated memory [[malloc(3)]](https://man7.org/linux/man-pages/man3/malloc.3.html#top_of_page) ```c struct list_head *q_new() { struct list_head *head = malloc(sizeof(struct list_head)); if (head) INIT_LIST_HEAD(head); return head; } ``` ### `q_free()` Free all storage used by queue, no effect if header is `NULL` 1. Linux Kernel API provides `list_for_each_entry_safe` function, the function have properties as below * iterate through a doubly linked list of entries * it ensures you don't lose track of the next entry even if the current entry is removed from the list. ```c void q_free(struct list_head *l) { if (!l) return; element_t *entry, *safe; list_for_each_entry_safe (entry, safe, l, list) q_release_element(entry); free(l); ``` ### `q_insert_head()` Insert an element in the head 1. Linux Kernel API provides `list_add` function that insert a new entry after the specified head. 2. why using `element->value = strdup(s);` instead of `element->value = s;` * Avoid undefined behavior: 1. May have undefined bevahior if the s string gets free * String Ownership: 1. creating a new copy of the string s on the heap memory. 2. ensures that the string's data remains valid and unchanged for the lifetime ```c bool q_insert_head(struct list_head *head, char *s) { if (!head) return false; element_t *element = malloc(sizeof(element_t)); if (!element) return false; element->value = strdup(s); if (!element->value) { free(element); return false; } list_add(&element->list, head); return true; } ``` ### `q_insert_tail()` Insert an element in the tail 1. Linux Kernel API provides `list_add_tail` function that insert a new entry before the specified head 2. use `strdup` function would be more robust when dealing with strings in C ```c bool q_insert_tail(struct list_head *head, char *s) { if (!head) return false; element_t *element = malloc(sizeof(element_t)); if (!element) return false; element->value = strdup(s); if (!element->value) { free(element); return false; } list_add_tail(&element->list, head); return true; } ``` ### `q_remove_head()` Remove the element from head of queue and return the pointer to element, `NULL` if queue is `NULL` or empty. 1. Linux Kernel API provides the function as below * `list_empty` -> tests whether a list is empty * `list_first_entry` -> that get the first element from a list * `list_del` -> deletes entry from list. 2. copy the removed string to *sp ```c element_t *q_remove_head(struct list_head *head, char *sp, size_t bufsize) { if (!head || list_empty(head)) return NULL; element_t *elem = list_first_entry(head, element_t, list); list_del(&elem->list); if (sp) { for (char *i = elem->value; bufsize > 1 && *i; sp++, i++, bufsize--) *sp = *i; *sp = '\0'; } return elem; } ``` ### `q_remove_tail()` Remove the element from tail of queue and return the pointer to element, `NULL` if queue is `NULL` or empty. 1. Linux Kernel API provides the function as below * `list_empty` -> tests whether a list is empty * `list_last_entry` -> that get the first element from a list * `list_del` -> deletes entry from list. 2. copy the removed string to *sp ```c element_t *q_remove_tail(struct list_head *head, char *sp, size_t bufsize) { if (!head || list_empty(head)) return NULL; element_t *elem = list_last_entry(head, element_t, list); list_del(&elem->list); if (sp) { for (char *i = elem->value; bufsize > 1 && *i; sp++, i++, bufsize--) *sp = *i; *sp = '\0'; } return elem; } ``` ### `q_size()` Get the size of the queue 1. Linux Kernel API provides the `list_for_each` function that iterate over a list ```c int q_size(struct list_head *head) { if (!head || list_empty(head)) return 0; int size = 0; struct list_head *node; list_for_each (node, head) size++; return size; } ``` ### `q_delete_mid()` Delete the middle node in queue 1. Linux Kernel API provides the `list_entry` function get the struct for this entry 2. use the slow and fast pointer to calcuate the middle position concept as below ``` init slow | v 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> NULL ^ | fast ``` ``` fast move 2 step slow move 1 step slow | v 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> NULL ^ | fast ``` ``` fast move 2 step slow move 1 step slow | v 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> NULL ^ | fast ``` ``` fast move 2 step -> arrive NULL slow pointer would not move slow | v 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> NULL ^ | fast ``` ```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, *fast; slow = fast = head->next; for (; fast != head && (fast = fast->next) != head; fast = fast->next) slow = slow->next; element_t *elem = list_entry(slow, element_t, list); list_del(&elem->list); q_release_element(elem); return true; } ``` ### `q_delete_dup()` Delete all nodes that have duplicate string, leaving only distinct strings from the original queue. 1. refer to <`alanjian85`> algorithm 2. use the prev pointer to compare the prev node value and current node value 3. initialize a bool var `dup` in order to delete the last duplicate string ```c bool q_delete_dup(struct list_head *head) { if (!head) return false; bool dup = false; element_t *prev = NULL, *curr; list_for_each_entry (curr, head, list) { bool equal = prev && !strcmp(prev->value, curr->value); if (equal || dup) { list_del(&prev->list); q_release_element(prev); dup = equal; } prev = curr; } if (dup) { list_del(&prev->list); q_release_element(prev); } return true; } ``` ### `q_swap()` Swap every two adjacent nodes 1. Linux Kernel API provides the `list_move` function 2. The concept would as below ![](https://hackmd.io/_uploads/r1Ytckhka.png) ```c void q_swap(struct list_head *head) { // https://leetcode.com/problems/swap-nodes-in-pairs/ if (!head || list_empty(head)) return; struct list_head *node; list_for_each(node, head) { if (node->next == head) break; list_move(node, node->next); } } ``` ### `q_reverse()` Reverse elements in queue ![](https://hackmd.io/_uploads/HJZcfGnJT.png) ```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) list_move(node, head); } ``` ### `q_reverseK()` Given the head of a linked list, reverse the nodes of the list k at a time. ```c void q_reverseK(struct list_head *head, int k) { // https://leetcode.com/problems/reverse-nodes-in-k-group/ if (!head || list_empty(head)) return; struct list_head *node = head->next; for (;;) { struct list_head *safe = node, *start = node->prev; for (int i = 0; i < k; i++, node = node->next) { if (node == head) return; } node = safe; safe = node->next; for (int i = 0; i < k; i++, node = safe, safe = safe->next) list_move(node, start); } } ``` ### `q_sort()` / `q_mergeTwo()` * use the recursive merge sort method to sort the linked list * use the slow fast pointer to find the middle point and divide the linked list into two subset. ```c void q_sort(struct list_head *head, bool descend) { if (!head || list_empty(head) || list_is_singular(head)) { return; // Nothing to sort } struct list_head *slow = head, *fast = head->next; for (; fast != head && fast->next != head; fast = fast->next->next) slow = slow->next; struct list_head left; list_cut_position(&left, head, slow); q_sort(&left, descend); q_sort(head, descend); q_merge_two(head, &left, descend); } static int q_merge_two(struct list_head *first, struct list_head *second, bool descend) { if (!first || !second) return 0; int size = 0; LIST_HEAD(tmp); while (!list_empty(first) && !list_empty(second)) { element_t *f = list_first_entry(first, element_t, list); element_t *s = list_first_entry(second, element_t, list); if ((descend && strcmp(f->value, s->value) > 0) || (!descend && strcmp(f->value, s->value) <= 0)) list_move_tail(&f->list, &tmp); else list_move_tail(&s->list, &tmp); size++; } size += q_size(first); list_splice_tail_init(first, &tmp); size += q_size(second); list_splice_tail_init(second, &tmp); list_splice(&tmp, first); return size; } ``` ### q_ascend() ```c int q_ascend(struct list_head *head) { // https://leetcode.com/problems/remove-nodes-from-linked-list/ if (!head || list_empty(head)) return 0; element_t *entry = NULL; int size = 0; list_for_each_entry (entry, head, list) { struct list_head *prev = entry->list.prev, *safe = prev->prev; for (; prev != head; prev = safe, safe = safe->prev) { element_t *prev_entry = list_entry(prev, element_t, list); if (strcmp(prev_entry->value, entry->value) <= 0) break; size--; list_del(prev); q_release_element(prev_entry); } size++; } return size; } ``` ### q_descend() ```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 *entry = NULL; int size = 0; list_for_each_entry (entry, head, list) { struct list_head *prev = entry->list.prev, *safe = prev->prev; for (; prev != head; prev = safe, safe = safe->prev) { element_t *prev_entry = list_entry(prev, element_t, list); if (strcmp(prev_entry->value, entry->value) >= 0) break; size--; list_del(prev); q_release_element(prev_entry); } size++; } return size; } ``` ### q_merge() ```c int q_merge(struct list_head *head, bool descend) { // https://leetcode.com/problems/merge-k-sorted-lists/ if (!head || list_empty(head)) return 0; if (list_is_singular(head)) return q_size(list_first_entry(head, queue_contex_t, chain)->q); int queue_size = 0; queue_contex_t *first, *second; first = list_first_entry(head, queue_contex_t, chain), second = list_entry(first->chain.next, queue_contex_t, chain); while (second->q) { queue_size = q_merge_two(first->q, second->q, descend); second->q = NULL; list_move_tail(&second->chain, head); second = list_entry(first->chain.next, queue_contex_t, chain); } return queue_size; } ```