--- tags: linux kernel --- # 2022q1 Homework1 (lab0) contributed by < `huang-me` > :::danger 注意作業書寫規範! :notes: jserv ::: # Prerequisites ```shell $ sudo apt install build-essential git clang-format aspell colordiff valgrind ``` ### Build cppcheck Download cppcheck source code from [GitHub](https://github.com/danmar/cppcheck/releases). ```shell # unzip file tar -xf <cppcheck-version.tar.gz> cd cppcheck-<version> # build file and install sudo make CFGDIR=/usr/share/cppcheck/ FILESDIR=/usr/bin/ -j4 sudo make install CFGDIR=/usr/share/cppcheck/ FILESDIR=/usr/bin -j4 ``` # Environment :::danger Your gcc was too old. Upgrade your Linux distribution as soon as possible, otherwise you will encounter some unexpected issues later. :notes: jserv ::: ```shell $ gcc --version gcc (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0 $ lscpu Architecture: x86_64 CPU op-mode(s): 32-bit, 64-bit Byte Order: Little Endian CPU(s): 8 On-line CPU(s) list: 0-7 Thread(s) per core: 2 Core(s) per socket: 4 Socket(s): 1 NUMA node(s): 1 Vendor ID: GenuineIntel CPU family: 6 Model: 142 Model name: Intel(R) Core(TM) i7-8550U CPU @ 1.80GHz Stepping: 10 CPU MHz: 899.969 CPU max MHz: 4000.0000 CPU min MHz: 400.0000 BogoMIPS: 3999.93 Virtualization: VT-x L1d cache: 32K L1i cache: 32K L2 cache: 256K L3 cache: 8192K NUMA node0 CPU(s): 0-7 ``` # Development ## queue_new - Use `malloc` to acquire memory space, return NULL if no space available. Initialize list_head with `INTI_LIST_HEAD` defined in `list.h` ```c struct list_head *q_new() { struct list_head *head = malloc(sizeof(*head)); if (!head) return NULL; INIT_LIST_HEAD(head); return head; } ``` ## queue_free - Delete first node until list have no element, and delete list_head. ```c void q_free(struct list_head *l) { if (!l) return; while (!list_empty(l)) { element_t *node = list_first_entry(l, element_t, list); q_remove_head(l, NULL, 0); free(node->value); free(node); } free(l); } ``` - Use `list_for_each_entry_safe` defined in `list.h` to iterate through all nodes in list, remove them from list and release the memory. ```c void q_free(struct list_head *l) { if (!l) return; element_t *e, *s; list_for_each_entry_safe (e, s, l, list) { list_del(&(e->list)); q_release_element(e); } free(l); } ``` ## q_insert_head - Use `malloc` allocate memory, `return false` if no memory can be allocated. Use `memset` initialize the value of `newNode` and copy input to `newNode->value` ```c bool q_insert_head(struct list_head *head, char *s) { element_t *newNode = malloc(sizeof(element_t)); if (!newNode) return false; newNode->value = malloc(sizeof(char) * (strlen(s) + 1)); if(!newNode->value) { q_release_element(newNode); return false; } memset(newNode->value, '\0', strlen(s) + 1); strncpy(newNode->value, s, strlen(s)); list_add(&newNode->list, head); return true; } ``` ## q_insert_tail - Most operations are same as `q_insert_head`, the only difference is replace `list_add` with `list_add_tail`. ```c bool q_insert_tail(struct list_head *head, char *s) { element_t *newNode = malloc(sizeof(element_t)); if (!newNode) return false; newNode->value = malloc(sizeof(char) * (strlen(s) + 1)); if (!newNode->value) { q_release_element(newNode); return false; } memset(newNode->value, '\0', strlen(s) + 1); strncpy(newNode->value, s, strlen(s)); list_add_tail(&newNode->list, head); return true; } ``` ## q_remove_head - Use `list_entry` to get the address of the entry. Copy the entry value to sp if sp is given. ```c element_t *q_remove_head(struct list_head *head, char *sp, size_t bufsize) { if (!head || list_empty(head)) return NULL; element_t *node = list_entry(head->next, element_t, list); if (sp && node->value) { strncpy(sp, node->value, bufsize - 1); *(sp + bufsize - 1) = '\0'; } list_del(head->next); return node; } ``` ## q_remove_tail - Most operations are same as `q_remove_head`, the only difference is the node position is changed from `head->next` to `head->prev`. ```c element_t *q_remove_tail(struct list_head *head, char *sp, size_t bufsize) { if (!head || list_empty(head)) return NULL; element_t *node = list_entry(head->prev, element_t, list); if (sp && node->value) { strncpy(sp, node->value, bufsize - 1); *(sp + bufsize - 1) = '\0'; } list_del(head->prev); return node; } ``` ## q_release_element - Release the memory of `e->value` and `e` if they were defined. ```c void q_release_element(element_t *e) { if (!e) return; if (e->value) free(e->value); free(e); } ``` ## q_size - Iterate through the queue with `list_for_each` and count the number of elements. ```c int q_size(struct list_head *head) { if (!head) return -1; int cnt = 0; struct list_head *e; list_for_each (e, head) cnt++; return cnt; } ``` ## q_delete_mid - Use `fast/slow` pointer find middle element of linked list and delete it. ```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 *fast, *slow; fast = slow = head->next; while (slow->next != head && slow->next->next != head) { slow = slow->next->next; fast = fast->next; } element_t *e = list_entry(fast, element_t, list); list_del(fast); q_release_element(e); return true; } ``` ## q_delete_dup - Iterate through all list nodes, delete the node if value is same as last node. ```c bool q_delete_dup(struct list_head *head) { // https://leetcode.com/problems/remove-duplicates-from-sorted-list-ii/ if (!head || list_empty(head)) return false; element_t *e, *s; char *last = NULL; list_for_each_entry_safe (e, s, head, list) { if (last && strcmp(last, e->value) == 0) { list_del(&e->list); q_release_element(e); } else { last = strdup(e->value); } } return true; } ``` :::success `strdup` may lead to memory leak since the space is never released. Rewrite `q_delete_dup` as following code to prevent copying data. ```c bool q_delete_dup(struct list_head *head) { // https://leetcode.com/problems/remove-duplicates-from-sorted-list-ii/ if (!head || list_empty(head)) return false; element_t *e, *s; char *last = NULL; list_for_each_entry_safe (e, s, head, list) { if (last && strcmp(last, e->value) == 0) { list_del(&e->list); q_release_element(e); } else { last = e->value; } } return true; } ``` ::: ## q_swap - Reassign all pointers between list nodes. ```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 *e = head->next; while (e != head && e->next != head) { struct list_head *n; n = e->next; n->next->prev = e; e->next = n->next; n->next = e; n->prev = e->prev; e->prev->next = n; e->prev = n; e = e->next; } } ``` :::success Shorten code with use of list.h functions. ```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 *e = head->next; while (e != head && e->next != head) { struct list_head *n = e->next; list_del(n); list_add(n, e->prev); e = e->next; } } ``` ::: ## q_reverse - Exchange all node's `next` and `prev` pointer to achieve reverse. ```c void q_reverse(struct list_head *head) { if (!head || list_empty(head)) return; struct list_head *e, *s; list_for_each_safe (e, s, head) { e->next = e->prev; e->prev = s; } struct list_head *tmp = head->next; head->next = head->prev; head->prev = tmp; } ``` ## q_sort - Split list into two subqueues, sort them separately and Merge into one queue. Time complexity: `O(nlogn)` ```c void q_sort(struct list_head *head) { if (!head || list_empty(head) || list_is_singular(head)) return; struct list_head *fast, *slow; fast = slow = head->next; while (fast != head && fast->next != head) { fast = fast->next->next; slow = slow->next; } if (fast == head) fast = fast->prev; struct list_head *mid; mid = slow->prev; head->prev = mid; mid->next = head; q_sort(head); struct list_head *front, *last; // ptr for 1st subqueue front = head->next; last = head->prev; head->next = slow; slow->prev = head; head->prev = fast; q_sort(head); struct list_head *fr; // ptr for 2nd subqueue fr = head->next; last->next = fr; fr->prev = last; head->next = front; front->prev = head; // merge two subqueue struct list_head *p1 = front, *p2 = last->next; while (p1 != last->next && p2 != head) { char *v1 = list_entry(p1, element_t, list)->value, *v2 = list_entry(p2, element_t, list)->value; if (strcmp(v1, v2) <= 0) { p1 = p1->next; } else { struct list_head *tmp = p2->next; list_del(p2); p2->next = p1; p2->prev = p1->prev; p1->prev = p2; p2->prev->next = p2; p2 = tmp; } } } ``` :::success Shorten merge code with use of list.h function. ```c struct list_head *p1 = front, *p2 = last->next; while (p1 != last->next && p2 != head) { char *v1 = list_entry(p1, element_t, list)->value, *v2 = list_entry(p2, element_t, list)->value; if (strcmp(v1, v2) <= 0) { p1 = p1->next; } else { struct list_head *tmp = p2->next; list_del(p2); list_add(p2, p1->prev); p2 = tmp; } } ``` ::: # Result ```shell make test scripts/driver.py -c --- Trace Points +++ TESTING trace trace-01-ops: # Test of insert_head and remove_head --- trace-01-ops 5/5 +++ TESTING trace trace-02-ops: # Test of insert_head, insert_tail, remove_head, remove_tail, and delete_mid --- trace-02-ops 6/6 +++ TESTING trace trace-03-ops: # Test of insert_head, insert_tail, reverse, and remove_head --- trace-03-ops 6/6 +++ TESTING trace trace-04-ops: # Test of insert_head, insert_tail, size, swap, and sort --- trace-04-ops 6/6 +++ TESTING trace trace-05-ops: # Test of insert_head, insert_tail, remove_head, reverse, size, swap, and sort --- trace-05-ops 6/6 +++ TESTING trace trace-06-ops: # Test of insert_head, insert_tail, delete duplicate, and sort --- trace-06-ops 6/6 +++ TESTING trace trace-07-string: # Test of truncated strings --- trace-07-string 6/6 +++ TESTING trace trace-08-robust: # Test operations on empty queue --- trace-08-robust 6/6 +++ TESTING trace trace-09-robust: # Test remove_head with NULL argument --- trace-09-robust 6/6 +++ TESTING trace trace-10-robust: # Test operations on NULL queue --- trace-10-robust 6/6 +++ TESTING trace trace-11-malloc: # Test of malloc failure on insert_head --- trace-11-malloc 6/6 +++ TESTING trace trace-12-malloc: # Test of malloc failure on insert_tail --- trace-12-malloc 6/6 +++ TESTING trace trace-13-malloc: # Test of malloc failure on new --- trace-13-malloc 6/6 +++ TESTING trace trace-14-perf: # Test performance of insert_tail, reverse, and sort --- trace-14-perf 6/6 +++ TESTING trace trace-15-perf: # Test performance of sort with random and descending orders # 10000: all correct sorting algorithms are expected pass # 50000: sorting algorithms with O(n^2) time complexity are expected failed # 100000: sorting algorithms with O(nlogn) time complexity are expected pass --- trace-15-perf 6/6 +++ TESTING trace trace-16-perf: # Test performance of insert_tail --- trace-16-perf 6/6 +++ TESTING trace trace-17-complexity: # Test if time complexity of q_insert_tail, q_insert_head, q_remove_tail, and q_remove_head is constant Probably constant time Probably constant time Probably constant time Probably constant time --- trace-17-complexity 5/5 --- TOTAL 100/100 ``` :::danger Insert/Remove time complexity test doesn't always pass in GitHub CI. > You should read the paper and recognize the root cause. > :notes: jserv ::: # Fisher-Yates shuffle - Add `q_shuffle` into `queue.c` - Fisher-Yates shuffle steps 1. Randomly choose a node and move it to tail of the queue. 2. Repeat step 1 for `q_size` times ```c void q_shuffle(struct list_head *head) { if(!head || list_empty(head)) return; struct list_head *node; for(int cnt=q_size(head); cnt>0; cnt--) { int x = rand() % cnt; node = head->next; for(int i=0; i<x; i++) node = node->next; list_del(node); list_add_tail(node, head); } return; } ``` - Add `q_shuffle` definition into queue.h header file. ```c void q_shuffle(struct list_head *head); ``` - Add `do_shuffle` in `qtest.c`. ```c static bool do_shuffle(int argc, char *argv[]) { if (argc != 1) { report(1, "%s takes no arguments", argv[0]); return false; } q_shuffle(l_meta.l); show_queue(3); return !error_check(); } ``` - Add command shuffle to console ```c ADD_COMMAND(shuffle, " | Shuffle the queue"); ```