<!-- # 可以呈現的內容 --> <!-- - [ ] 實作: 解決 memory leak、segmentation fault、execution time - 看、理解、善用 Linux Kernel API <!-- - `for` 類型 `define` 使用 1. 減少重複冗長的 code 重複寫 $\rightarrow$ 提升效率 --> <!-- - project 開發中檔案的管理: .c、.h --> <!-- - [ ] 延伸研究、memory leak、segmentation fault --> --> # Introduction - This project implement the functions of doubly linked list and additional information is available at [here](https://hackmd.io/@sysprog/linux2025-lab0/%2F%40sysprog%2Flinux2025-lab0-a#Git-Hooks-%E9%80%B2%E8%A1%8C%E8%87%AA%E5%8B%95%E7%A8%8B%E5%BC%8F%E7%A2%BC%E6%8E%92%E7%89%88%E6%AA%A2%E6%9F%A5). - This project is available at this [GitHub Repository](https://github.com/PoChuan994/lab0-c). # Implementation ## Doubly Link List Data Structure - The **doubly linked list** follows a **three-layer structure**. - The **first layer** is used to manage all lists. ```c= typedef struct { struct list_head *q; struct list_head chain; int size; int id; } queue_contex_t; ``` - The **second layer** is the fundamental structure of a **doubly linked list**, defined as bellow: ```c= struct list_head { struct list_head *prev; struct list_head *next; } ``` - The **third layer** associates a node with a **character string**, defined as follows: ```c= typedef struct { char *val; struct list_head list; }element_t; ``` <!-- - The high-level architecture of the the doubly linked list is illustrated below. ![architecture](https://hackmd.io/_uploads/By7YQr-nT.png) - This graph is refered from [here](https://hackmd.io/@sysprog/linux2025-lab0/%2F%40sysprog%2Flinux2025-lab0-a#Git-Hooks-%E9%80%B2%E8%A1%8C%E8%87%AA%E5%8B%95%E7%A8%8B%E5%BC%8F%E7%A2%BC%E6%8E%92%E7%89%88%E6%AA%A2%E6%9F%A5) --> ## Generic Function Implementation Pattern - Implement **memory allocation** operations when **concatenating**, **creating**, **removing**, or **rearranging** nodes using the **Linux Kernel-style API**. - By utilizing the list manipulation functions defined in the Linux kernel-style API, we can avoid redundant code, enhance reusability, and improve tracking of list operations. - Check if the **input is null** to **prevent segmentation faults** due to *null pointer dereference* in sequential statements. ```c= if (!head) return 0; // or if (!head) return; ``` - `if (!head) return 0;` or `if (!head) return;` - Return `false` if dynamic memory allocation fails. ```c= element_t *new = malloc(sizeof(element_t)); if (!new) return false; ``` - **Usage of different** `for` **loop macros** in the **Linux Kernel API**: 1. `list_for_each`: ```c= #define list_for_each(node, head) \ for (node = (head)->next; node != (head); node = node->next) ``` - Used to iterate through **every node** in a list **without removing or deleting** nodes operations. 2. `list_for_each_safe`: ```c= #define list_for_each_safe(node, safe, head) \ for (node = (head)->next, safe = node->next; node != (head); \ node = safe, safe = node->next) ``` - The `safe` node stores the reference to be **next node** before modifying the current node. - Suitable for **freeing**, **deleting**, or **reversing** nodes in a list. 3. `list_for_each_entry` 4. `list_entry` - Use the `list_entry` macro to retrieve the structure containing the list node: 5. `list_for_first_entry` <!-- ## `q_free` :::spoiler Implementation ```c= #define list_entry(node, type, member) container_of(node, type, member) #ifndef container_of #if __LIST_HAVE_TYPEOF #define container_of(ptr, type, member) \ __extension__({ \ const typeof(((type *) 0)->member) *__pmember = (ptr); \ (type *) ((char *) __pmember - offsetof(type, member)); \ }) #else #define container_of(ptr, type, member) \ ((type *) ((char *) (ptr) - offsetof(type, member))) #endif #endif ``` ::: - `List_for_each_safe` uses the`list_head` structure as an iterator to traverse a list. However, the element to be removed is of type `elemen_t`, not `list_head`. - Use the `list_entry` macro to retrieve the structure containing the list node: --> ## `q_remove_head` and `q_remove_tail` - The *remove operation* breaks the links of a node from the list but does not free its memory. - The removed node should be **reinitialized** according to the [homework requirement](https://hackmd.io/@sysprog/linux2025-lab0/%2F%40sysprog%2Flinux2025-lab0-a#Git-Hooks-%E9%80%B2%E8%A1%8C%E8%87%AA%E5%8B%95%E7%A8%8B%E5%BC%8F%E7%A2%BC%E6%8E%92%E7%89%88%E6%AA%A2%E6%9F%A5). - Modify the stack pointer `sp` using the `list_entry` macro to obtain the correct memory address. - Break and reconnect links using `list_del` macro and reinitialize the removed node. ## `q_delete_mid` ::: spoiler Implementation ```c= bool q_delete_mid(struct list_head *head) { if (!head || list_empty(head)) /* input validation */ return false; struct list_head **indir = &(head->next); const struct list_head *fast = head->next; for (; fast != head && fast->next != head; fast = fast->next->next) indir = &(*indir)->next; element_t *element = list_entry(*indir, element_t, list); list_del(&element->list); q_release_element(element); return true; } ``` ::: - Unlike the *remove operation*, the *delete operation* **frees** the memory of deleted node and **reinitialze** the node similarly. - Use an indirect pointer `indir` to implement a **two pointer approach** for finding the middle node in a list. - Obtain the structure type of the node to be removed using `list_entry` macro. - Break and reconnect the list of the removed node using `list_dele` macro. - Free the allocated memory using `q_release_element`. ## `q_delete_dup` - This function aims to delete all duplicate strings from a list. - The operation concept can be introduced through implementation of singly linked list. ### Singly linked list :::spoiler Implementation ```c= struct ListNode* deleteDuplicates(struct ListNode* head) { struct ListNode **indir = &head; while (*indir) { struct ListNode *curr = *indir; if (curr->next && curr->val == curr->next->val) { while (curr->next && curr->val == curr->next->val) curr = curr->next; *indir = curr->next; } else { indir = &(*indir)->next; } } return head; } ``` ::: - The concept and implementation in a [singly linked list](https://leetcode.com/problems/remove-duplicates-from-sorted-list-ii/) are shown below: - Use two pointers, `**indir`, which points to the link to be updated, and `curr`, which points to the node being checked to determine if the next node's value is equal to `curr`. - If the value of `curr` is equal to `curr->next`, iteratively check further until the next node's value is not equivalent. - Otherwise, update the `**indir` to continue checking. <!-- ### Doubly-linked list - ineffecient version ```shell= $ make test ERROR: Time limit exceeded. Either you are in an infinite loop, or your code is too inefficient ERROR: Calling delete duplicate on null queue ``` ```shell= Performance counter stats for './qtest': 4,306,711,281 branches 4,782,570 branch-misses # 0.11% of all branches 4,080,279 cache-misses 62.632583754 seconds time elapsed 1.079122000 seconds user 0.134089000 seconds sys ``` --> ### Doubly-linked list :::spoiler implementation ```c= bool q_delete_dup(struct list_head *head) { volatile struct list_head *dummy = head; (void) dummy; if (!head || list_empty(head)) /* input validation */ return false; element_t *curr_entry = list_first_entry(head, element_t, list); while (&curr_entry->list != head) { struct list_head *next = curr_entry->list.next; element_t *next_entry = list_entry(next, element_t, list); /* check if there is duplicate element */ bool check = false; while (&next_entry->list != head && !strcmp(curr_entry->value, next_entry->value)) { struct list_head *tmp = next_entry->list.next; list_del(&next_entry->list); q_release_element(next_entry); next_entry = list_entry(tmp, element_t, list); check = true; } if (check) { struct list_head *tmp = curr_entry->list.next; list_del(&curr_entry->list); q_release_element(curr_entry); curr_entry = list_entry(tmp, element_t, list); } else { curr_entry = next_entry; } } return true; } ``` ::: - In this implementation, the next node cannot be accessed directly using the indirect pointer `indir`, so the solution resorts to using only the *Two pointers approach* to traverse and manipulate the list. - The process starts by getting the first node that actually contains a character string using `list_first_entry` (line 9). - The function then traverses the list, checking for duplicates by comparing `curr_entry->value` with `next_entry->value`. - If a duplicate is found, the duplicate nodes are removed using `list_del`, and their memory is released by calling` q_release_element`. - After processing the duplicates, if any were removed, the `curr_entry` pointer is updated to skip over the duplicates, otherwise, it moves to the next node. ## `q_swap` :::spoiler Implementation ```c= struct list_head *first = head->next; struct list_head *second = first->next; for (; first != head && second !=head; first = first->next, second = first->next) { /* * break the links of first node and * reconcatenate its previous and next nodes * */ list_del(first); /* insert first node after second node */ list_add(first, second); } ``` ::: - This function swaps every two adjacent nodes. - Use the *Two Pointers approach* to traverse the list. - Remove the `first` node from the list and reinitialize it using `list_add` function. - Reinsert `first` node after `second` using `list_add` function. - The workflow of `q_swap` is shown in the following pictures. 1. The `first` pointer points to the first node, while the `second` pointer points to the next node of `first`. ![q_swap1.drawio](https://hackmd.io/_uploads/S1DR0ml6Je.png) 2. After executing `list_del(first)`, the node pointed by `first` is temporarily removed from the list and its links which adjacent nodes are broken. ![q_swap2.drawio](https://hackmd.io/_uploads/SyGf1Vxa1e.png) 3. The node originally pointed by `first` node is reinserted immediatly after the `second` using `list_add(first, second)`. ![q_swap3.drawio(2)](https://hackmd.io/_uploads/B1DZx4l6yl.png) 4. In next iteration, `first` moves to the next node in the list (previously the third node), while `second` moves to the node after `first` (previously the fourth node). ![q_swap4.drawio](https://hackmd.io/_uploads/SJ2ak4l6Jg.png) ## `q_reverse` :::spoiler Implementation ```c= void q_reverse(struct list_head *head) { struct list_head *current, *safe; list_for_each_safe (current, safe, head) list_move(current, head); } ``` ::: - This function aims to reverse a list. - Since this function does not allocate additional memory, there is no risk of memory leaks; it only reorders nodes using the Linux kernel-style API. - Avoid pointer invalidation issues when modifying the list during traversal using the`list_for_each_safe` macro. - `list_move` function do following operations: 1. Remove `current` node from a list. 2. Reinitialize `current` node. 3. Reinsert `current` node after `head` node as first node iteratively. ## `q_reverse_k` :::spoiler Implementation ```c= void q_reverseK(struct list_head *head, int k) { // https://leetcode.com/problems/reverse-nodes-in-k-group/ if (!head || head->next == head || k <= 1) return; size_t cnt = 0; struct list_head *curr = NULL, *safe = NULL, *start = head; list_for_each_safe (curr, safe, head) { cnt++; if (cnt == k) { LIST_HEAD(tmp); cnt = 0; list_cut_position(&tmp, start, curr); q_reverse(&tmp); list_splice(&tmp, start); start = safe->prev; } } } ``` ::: - Reverse every `k` nodes in a list. - When every `k` nodes are accumulated during traversal: 1. Create an empty list `tmp` to temporarily store the nodes that will be reversed. 2. Use the `list_cut_position` function to remove the `k` nodes from the list and store them in `tmp`. 3. Reverse the `tmp` list using `q_reverse`. 4. Reinsert the reversed `tmp` list back into the original position using `list_splice`. ## `q_sort` ### First Version :::spoiler Implementation ```c= void q_sort(struct list_head *head, bool descend) { if (!head || list_empty(head) || list_is_singular(head)) return; struct list_head *slow = head->next, *fast = head->next->next; while (fast != head && fast->next != head) { slow = slow->next; fast = fast->next->next; } struct list_head l, r; INIT_LIST_HEAD(&l); INIT_LIST_HEAD(&r); list_cut_position(&l, head, slow); list_splice_init(head, &r); q_sort(&l, descend); q_sort(&r, descend); merge(head, &l, &r, descend); } void merge(struct list_head *head, struct list_head *left, struct list_head *right, bool descend) { while (!list_empty(left) && !list_empty(right)) { const element_t *l = list_entry(left->next, element_t, list); const element_t *r = list_entry(right->next, element_t, list); if (((descend * 2) - 1) * strcmp(l->value, r->value) > 0) list_move_tail(left->next, head); else list_move_tail(right->next, head); } if (list_empty(head)) list_splice_tail(right, head); else list_splice_tail(left, head); } ``` ::: #### `q_sort` - Use the `fast` and `second` pointers to find the middle node `slow`. - Initialize two list `l` and `r` to store halve sub-lists. - Move nodes from`head` to `l`, cutting the list at `slow` using `list_cut_position`. - Move remaining nodes from this `head` to the list `r` using `list_splice_init` function. - Sort both sub-lists recursively using `q_sort`. - Merge the two sorted sub-list using `merge` function. #### `merge` - `while` both sub-lists are not empty, do the followings: - Extract first node in both sub-lists using `list_entry`. - Insert the selected node into tail of the list `head` according to `descend` variable. - Insert remaining nodes into the list `head`. #### Analysis - Memory leaks: - Create two sub-lists `l` and `r` to record reassignment order of list instead and move back all nodes to the list `head` without allocating new memory. - stable: - if `strcmp(l->value, r->value) == 0`, `left->next` node place to tail before `second`. ### Optimized Version ::: spoiler Implementation ```c= void q_sort(struct list_head *head, bool descend) { if (!head || head->next == head || head->prev == head->next) return; struct list_head *slow = head; const struct list_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); } void q_merge_two(struct list_head *first, struct list_head *second, bool descend) { if (!first || !second) return; struct list_head tmp; INIT_LIST_HEAD(&tmp); while (!list_empty(first) && !list_empty(second)) { element_t *first_top = list_first_entry(first, element_t, list); element_t *second_top = list_first_entry(second, element_t, list); const char *first_str = first_top->value, *second_str = second_top->value; bool check; if (descend) check = strcmp(first_str, second_str) > 0; else check = strcmp(first_str, second_str) < 0; element_t *add_first = check ? first_top : second_top; list_move_tail(&add_first->list, &tmp); } list_splice_tail_init(first, &tmp); list_splice_tail_init(second, &tmp); list_splice(&tmp, first); } ``` ::: #### Improvement of `q_sort` implementation - Create only one **extra** sub-list to record first halve original list. - Save time to move remaining element the second extra sub-list using `list_splice_init` and `INIT_LIST_HEAD` two times. - Seperate a list into two sub-lists `left` and `head` with same length. - Sort both sub-lists and merge them using `q_merge_two`. #### `q_merge_two` implementation - `q_merge_two` function: - `while` both sub-lists are not empty, compare their string values and appending the smaller (or larger, based on `descend`) element to the tail of `tmp`. - Appending the remaining elements from both lists to `tmp` using `list_splice_tail_init`. - Move all nodes from `tmp` back into `first` list using `list_splice`. #### Analysis - Memory leaks: - Create a `list` to record reassignment order of list instead and move back all nodes to the list `head` without allocating new memory. - stable: - if `first_str == second_str`, `first` node place to tail before `second`. <!-- ## `q_merge` --> # Reference - [The Linux Kernel API](https://docs.kernel.org/core-api/kernel-api.html) - [2025 年 Linux 核心設計課程作業 —— lab0 (A)](https://hackmd.io/@sysprog/linux2025-lab0/%2F%40sysprog%2Flinux2025-lab0-a#Git-Hooks-%E9%80%B2%E8%A1%8C%E8%87%AA%E5%8B%95%E7%A8%8B%E5%BC%8F%E7%A2%BC%E6%8E%92%E7%89%88%E6%AA%A2%E6%9F%A5)