--- tags: 進階電腦系統理論與實作, NCKU Linux Kernel Internals, 作業系統 --- # 2020q3 Homework1 (lab0) ###### tags: `RusselCK` contributed by <`RusselCK`> - ==[進階電腦系統理論與實作 lab0](https://hackmd.io/@sysprog/2020-lab0)== - [作業繳交區](https://hackmd.io/@sysprog/2020-homework3) ## 環境 * 看環境 ``` $ uname -a ``` * 看規格說明 ( Linux Programmer's Manual ) ``` $ man * ``` ## [開發工具](https://hackmd.io/@sysprog/gnu-linux-dev/) #### [Vim](https://hackmd.io/@sysprog/gnu-linux-dev/https%3A%2F%2Fhackmd.io%2Fs%2FHJv9naEwl) * 安裝 Vim `$ sudo apt-get install vim` * [相關指令](http://linux.vbird.org/linux_basic/0310vi.php) ``` * 編輯 Vim 設定檔(在家目錄下): $ vim .vimrc * 切換游標至左側欄:ctrl + w + h * 切換游標至右側欄:ctrl + w + l * 下拉選單往下選:ctrl + n * 下拉選單往下選:ctrl + p ``` ##### 使用方式 * 開啟想編輯的檔案 ``` $ vi *.[ch] ``` * 存檔 ``` :wq ``` * 編譯 ``` $ gcc -o * *.c ``` * 看 Code ``` $ cat *.[ch] ``` ![](https://i.imgur.com/oeqPF2l.png) ## 提升程式碼品質 #### 一致的程式撰寫風格 * 安裝 [clang-format](https://clang.llvm.org/docs/ClangFormat.html) `$ sudo apt install clang-format ` * 執行 ( 在有程式的資料夾下 ) ``` $ clang-format -i *.[ch] ``` #### [Git Hooks](https://www.atlassian.com/git/tutorials/git-hooks) 自動程式碼排版檢查 * 安裝 [pre-commit](https://pre-commit.com/#install) `$ sudo snap install pre-commit --classic` * 安裝 tig ,更便利地瀏覽 git repository 資訊 `$ sudo apt install tig` #### 好的 Git commit message 應符合七條規則: 1. 用一行空白行分隔標題與內容 1. 限制標題最多只有 50 字元 1. 標題開頭要大寫 1. 標題不以句點結尾 1. 以祈使句撰寫標題 1. 內文每行最多 72 字 1. 用內文解釋 what 以及 why vs. how ## 閱讀 [C Programming Lab](https://www.cs.cmu.edu/afs/cs/academic/class/15213-s20/www/labs/cprogramminglab.pdf) #### Overview * `queue.h`:Queue structure ```c= /* Linked list element */ typedef struct ELE { char *value; struct ELE *next; } list_ele_t; /* Queue structure */ typedef struct { list_ele_t *head; /* Linked list of elements */ } queue_t; ``` ![](https://i.imgur.com/uXzI1Gz.png) * Linked-list implementation of a queue. Each list element has a value field, pointing to an array of characters (C’s representation of strings), and a next field pointing to the next list element. Characters are encoded according to the ASCII encoding (shown in hexadecimal.) #### Task * Your task is to modify the code in `queue.h` and `queue.c` to fully implement the following functions. * `q_new`: Create a new, empty queue. * `q_free`: Free all storage used by a queue. * `q_insert_head`: Attempt to insert a new element at the head of the queue (LIFO discipline). * `q_insert_tail`: Attempt to insert a new element at the tail of the queue (FIFO discipline). * `q_remove_head`: Attempt to remove the element at the head of the queue. * `q_size`: Compute the number of elements in the queue. > 下列程式碼的都簡單的註記功能 #### 1. `queue.h` * You will need to add more fields to this structure to efficiently implement `q_size` and `q_insert_tail`. ```c= typedef struct ELE { char *value; struct ELE *next; } list_ele_t; typedef struct { list_ele_t *head; //queue can know its tail list_ele_t *tail; //queue can know its size int size; } queue_t; ``` #### 2. `q_new` * What if malloc returned NULL? ```c= queue_t *q_new() { queue_t *q = malloc(sizeof(queue_t)); /* TODO: What if malloc returned NULL? */ if(!q) return NULL; q->head = NULL; q->tail = NULL; q->size = 0; return q; } ``` #### 3. `q_free` * How about freeing the list elements and the strings? ```c= void q_free(queue_t *q) { if(!q) return; /* TODO: How about freeing the list elements and the strings? */ while (q->head) { list_ele_t *tmp = q->head; q->head = q->head->next; free(tmp->value); free(tmp); } /* Free queue structure */ free(q); } ``` #### 4. `q_insert_head` * What should you do if the q is NULL? * What if either call to malloc returns NULL? ```c= bool q_insert_head(queue_t *q, char *s) { /* TODO: What should you do if the q is NULL? */ if (!q) return false; list_ele_t *newh; newh = malloc(sizeof(list_ele_t)); if (!newh) return false; /* Don't forget to allocate space for the string and copy it */ size_t length = strlen(s) + 1; newh->value = (char *) malloc(length * sizeof(char)); if (!newh->value) { free(newh); return false; } newh->next = NULL; // memset(newh->value, '\0', strlen(s) + 1); // strncpy(newh->value, s, strlen(s)); // memcpy(newh->value, s, strlen(s) + 1); snprintf(newh->value, length, "%s", s); // insert head newh->next = q->head; q->head = newh; /* What if either call to malloc returns NULL? */ if (!q->tail) q->tail = newh; (q->size)++; return true; } ``` :::info * **`void * memset ( void * ptr, int value, size_t num );`** * **Fill block of memory** * Sets the first `num` bytes of the block of memory pointed by `ptr` to the specified `value` (interpreted as an unsigned char). * **`char * strncpy ( char * destination, const char * source, size_t num );`** * **Copy characters from string** * Copies the first `num` characters of `source` to `destination`. * If the end of the source C string (which is signaled by a null-character) is found before num characters have been copied, destination is padded with zeros until a total of num characters have been written to it. * **`void * memcpy ( void * destination, const void * source, size_t num );`** * **Copy block of memory** * Copies the values of `num` bytes from the location pointed to by `source` directly to the memory block pointed to by `destination`. * **`int snprintf ( char * s, size_t n, const char * format, ... );`** * **Write formatted output to sized buffer** * Composes a string with the same text that would be printed if `format` was used on printf, but instead of being printed, the content is stored as a C string in the buffer pointed by `s` (taking `n` as the maximum buffer capacity to fill). * **`size_t`** * **Unsigned integral type** * Alias of one of the fundamental unsigned integer types * Source: http://www.cplusplus.com ::: #### 5. `q_insert_tail` * It should operate in $O(1)$ time ```c= bool q_insert_tail(queue_t *q, char *s) { if (!q) return false; list_ele_t *newt; newt = malloc(sizeof(list_ele_t)); if (!newt) return false; /* Don't forget to allocate space for the string and copy it */ size_t length = strlen(s) + 1; newt->value = (char *) malloc(length * sizeof(char)); if (!newt->value) { free(newt); return false; } snprintf(newt->value, length, "%s", s); // insert tail newt->next = NULL; if (!q->tail) { q->head = newt; q->tail = newt; return true; } q->tail->next = newt; q->tail = newt; (q->size)++; return true; } ``` #### 6. `q_remove_head` * Return false if queue is NULL or empty. * If `sp` is non-NULL and an element is removed, copy the removed string to `*sp` (up to a maximum of bufsize-1 characters, plus a null terminator.) ```c= bool q_remove_head(queue_t *q, char *sp, size_t bufsize) { /*Return false if queue is NULL or empty.*/ if (!q || !q->head) return false; /* *If sp is non-NULL and an element is removed, copy the removed string to **sp (up to a maximum of bufsize-1 characters, plus a null terminator.) */ list_ele_t *tmp = q->head; if (sp){ // strncpy(sp, tmp->value, bufsize - 1); // sp[bufsize - 1] = '\0'; snprintf(sp, bufsize, "%s", tmp->value); } // remove head q->head = q->head->next; free(tmp->value); free(tmp); q->size--; return true; } ``` :::warning * `sp[bufsize-1] = '\0';` ::: #### 7. `q_size` * Return number of elements in queue. * Return 0 if q is NULL or empty * It should operate in $O(1)$ time ```c= int q_size(queue_t *q) { /* Remember: It should operate in O(1) time */ // return !q ? 0 : q->size; if (!q) { return 0; } return q->size; } ``` #### 8. `q_reverse` * Reverse elements in queue * No effect if `q` is `NULL` or empty ```c= void q_reverse(queue_t *q) { if (!q || !q->head || q->size == 1) return; q->tail = q->head; list_ele_t *cursor = NULL; list_ele_t *next = NULL; while (q->head) { next = q->head->next; q->head->next = cursor; cursor = q->head; q->head = next; } q->head = cursor; q->tail->next = NULL; } ``` :::warning * 用 pointer to pointer 較好 * 使用者可以知道 執行完之後 head的位址 * 2 個 element 可以用更簡單的方式完成 ::: #### 9. `q_sort` * Sort elements of queue in ascending order ```c= void split_list(list_ele_t **head, list_ele_t **list1, list_ele_t **list2) { list_ele_t **slow = list1; list_ele_t **fast = list2; while ((*fast) && (*fast)->next) { *slow = (*slow)->next; *fast = (*fast)->next->next; } // slow is at the midnode *list2 = (*slow)->next; // Split actually (*slow)->next = NULL; *list1 = *head; } void merge_list(list_ele_t **head, list_ele_t **list1, list_ele_t **list2) { list_ele_t **cursor = head; while ((*list1) && (*list2)) { if (strcmp((*list1)->value, (*list2)->value) < 0) { *cursor = *list1; *list1 = (*list1)->next; } else { *cursor = *list2; *list2 = (*list2)->next; } cursor = &((*cursor)->next); } // avoid dropped off if (*list1) *cursor = *list1; if (*list2) *cursor = *list2; } void merge_sort(list_ele_t **head) { // Base case if (!(*head) || !(*head)->next) return; // Splitting list list_ele_t *list1 = (*head)->next; list_ele_t *list2 = *head; split_list(head, &list1, &list2); // Recursive sorting two list merge_sort(&list1); merge_sort(&list2); // Merge sorted lists merge_list(head, &list1, &list2); } void q_sort(queue_t *q) { /* No effect if q is NULL or empty.*/ if (!q || !q->head) return; merge_sort(&q->head); while (q->tail->next) q->tail = q->tail->next; } ``` :::info * **`int strcmp ( const char * str1, const char * str2 );`** * **Compare two strings** * Compares the C string `str1` to the C string `str2`. | return value | indicates | | ------------ |:-------------------------------------------------------------------------------- | | <0 | the first character that does not match has a lower value in ptr1 than in ptr2 | | 0 | the contents of both strings are equal | | >0 | the first character that does not match has a greater value in ptr1 than in ptr2 | ::: :::warning * 若 input 只有 2 個,不應該進入 merge_sort * 也許,沒有到一定的數量,就不要使用 merge_sort ::: ## 自動評分程式 #### 指令 * `$ make test` * `$ clang-format -i *.[ch]` * `$ make valgrind` * `$ valgrind ./test` * `$ make SANITIZER=1` * `$ ./qtest -f traces/trace-15-perf.cmd` #### `$ make test` 評分成果 ``` --- Trace Points +++ TESTING trace trace-01-ops: # Test of insert_head and remove_head --- trace-01-ops 6/6 +++ TESTING trace trace-02-ops: # Test of insert_head, insert_tail, and remove_head --- 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, and sort --- trace-04-ops 6/6 +++ TESTING trace trace-05-ops: # Test of insert_head, insert_tail, remove_head, reverse, size, and sort --- trace-05-ops 5/5 +++ TESTING trace trace-06-string: # Test of truncated strings --- trace-06-string 6/6 +++ TESTING trace trace-07-robust: # Test operations on NULL queue --- trace-07-robust 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-malloc: # Test of malloc failure on new --- trace-10-malloc 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-perf: # Test performance of insert_tail --- trace-13-perf 6/6 +++ TESTING trace trace-14-perf: # Test performance of size --- trace-14-perf 6/6 +++ TESTING trace trace-15-perf: # Test performance of insert_tail, size, reverse, and sort --- trace-15-perf 6/6 +++ TESTING trace trace-16-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-16-perf 6/6 +++ TESTING trace trace-17-complexity: # Test if q_insert_tail and q_size is constant time complexity Probably constant time Probably constant time --- trace-17-complexity 5/5 --- TOTAL 100/100 ``` ## 運用 Valgrind 排除 `qtest` 實作的記憶體錯誤,並透過 Massif 視覺化 “simulation” 過程中的記憶體使用量 #### [Valgrind](https://valgrind.org/) * 安裝 [Valgrind](https://valgrind.org/) `$ sudo snap install valgrind --classic` * Valgrind 是個在使用者層級 (user space) 對程式在執行時期的行為提供動態分析的系統軟體框架,具備多種工具,可以用來追蹤及分析程式效能,最為人們所熟知的特性就是幫助使用者偵測記憶體錯誤,諸如使用未初始化的記憶體、不當的記憶體配置、或取消配置記憶體,並加以分析。所有工具都包含在 valgrind 套件中,可以透過以下命令執行: ``` $ valgrind --tool=<toolname> <program> ``` ``` $ valgrind -q --leak-check=full ./<program> ``` * 由於 Valgrind 是動態追蹤,會從給定的程式一路追蹤到 [glibc](https://www.gnu.org/software/libc/) (GNU C library),為了更好的分析效果,需要安裝對應包含除錯訊息的套件: `$ sudo apt install libc6-dbg` * 看更多 * [Valgrind User Manual](https://valgrind.org/docs/manual/manual.html) #### [Massif](https://valgrind.org/docs/manual/ms-manual.html) * 安裝 Massif `$ sudo apt install massif-visualizer ` * Valgrind 可支援多種工具,其中分析記憶體使用狀況的工具叫做 Massif 可分析以下: * Heap blocks; * Heap administration blocks; * Stack sizes. #### 視覺化結果 * 執行 ``` $ make valgrind $ valgrind --tool=massif ./<prog> $ ms_print outputfile ``` ![](https://i.imgur.com/wAe5q8u.png)