# 2021q1 Homework1 (lab0)
[作業內容](https://hackmd.io/@sysprog/linux2021-lab0)
[github](https://github.com/ShaneDocument/Linux-Kernel-Internals-lab0-c)
## 7/31
[7/31 commit](https://github.com/ShaneDocument/Linux-Kernel-Internals-lab0-c/commit/ae506bc8900257ce2bcbe5950aa2e2b1b2c51a3b)
### queue.c
#### **queue_t \*q_new()**
如果 malloc return NULL,則代表 q 被配置NULL,依照原文註解要求,直接回傳NULL即可
```c=
queue_t *q_new()
{
queue_t *q = malloc(sizeof(queue_t));
/* TODO: What if malloc returned NULL? */
if (q == NULL) {
return NULL;
}
q->head = NULL;
q->tail = NULL;
q->size = 0;
return q;
}
```
#### **void q_free(queue_t \*q)**
* 利用 while loop 釋放所走 element 與其 string 的記憶體
* 利用tmp來存放 queue head 的 next,釋放 queue head 後,再把queue head 指向 tmp
:::info
第 3 行的判斷一開始沒有加上去,後來跑 make check 有出現 segmentation fault,才知道這一塊有錯誤
:::
```c=
void q_free(queue_t *q)
{
if (!q)
return;
/* TODO: How about freeing the list elements and the strings? */
while (q->head != NULL) {
list_ele_t *tmp = q->head->next;
free(q->head->value);
free(q->head);
q->head = tmp;
}
/* Free queue structure */
free(q);
}
```
#### **bool q_insert_head(queue_t \*q, char \*s)**
* 首先,若 q 為 NULL,則 return false
* 若 malloc 的值為 NULL,則也要 return false
* 真正開始 insert 了!
* 先計算出傳入的 string 的大小,加上 1 是為了 '\0'
* 配置 strSize 給newh->value
* 判定 tail 是否為 NULL
```c=
bool q_insert_head(queue_t *q, char *s)
{
if (q == NULL) {
return false;
}
list_ele_t *newh;
/* TODO: What should you do if the q is NULL? */
newh = malloc(sizeof(list_ele_t));
if (newh == NULL) {
return false;
}
/* Don't forget to allocate space for the string and copy it */
/* What if either call to malloc returns NULL? */
size_t strSize = strlen(s) + 1;
newh->value = malloc(sizeof(char) * strSize);
snprintf(newh->value, strSize, "%s", s);
q->size++;
if (!q->tail) {
q->tail = newh;
}
newh->next = q->head;
q->head = newh;
return true;
}
```
### 心得
老實說,雖然我是資工系的碩士生,但對底層還是一竅不通。必須承認以上的程式碼有部分參考之前修課的大神們。希望以後可以自己寫出漂亮的c code
### 問題
1. 為何在 q_insert_head 中,要先 allocate memory 給 newh->value 呢?為何不能直接把 s 值傳遞給newh->value?
```c=
newh->value = s;
```
2. snprintf 與 sprintf 有何差異?
## 8/1
[8/1 commit](https://github.com/ShaneDocument/Linux-Kernel-Internals-lab0-c/commit/14ec8b94221f99b824cb6865f714c78c98ae9b71)
### queue.c
#### **bool q_insert_tail(queue_t \*q, char \*s)**
一開始作法跟 q_insert_head 很像。程式碼內有我自己打的註解,如果 q->tail = NULL,表示與 insert_head 是一樣的步驟,所以直接把結果寫在 if 判斷式內。
後來的作法與 insert_head 有一點不太一樣,這部分可以對照著程式碼去思考。
```c=
bool q_insert_tail(queue_t *q, char *s)
{
return false;
if (!q)
return false;
list_ele_t *newt;
newt = malloc(sizeof(list_ele_t));
if (!newt)
return false;
size_t strSize = strlen(s) + 1;
newt->value = malloc(sizeof(char) * strSize);
snprintf(newt->value, strSize, "%s", s);
q->size++;
if (!q->tail) {
q->tail = newt;
newt->next = q->head;
q->head = newt;
/* If the tail is NULL, the whole queue is empty. So, inserting tail is
* same as inserting head. */
return true;
}
q->tail->next = newt;
q->tail = newt;
q->tail->next = NULL;
return true;
}
```
#### **bool q_remove_head(queue_t \*q, char \*sp, size_t bufsize)**
特別把這個 function 的註解貼上來,主要是因為自己不太懂他的要求是什麼。
一開始看大概知道是要把 remove 的內容貼到 sp 上,可能是要實作出類似 stack 的 pop 之類的功能( remove 之後可以取到值),但那個 buffer 有點不是太明白,因此這個部分參考了之前修課的同學的 code ,大概懂意思之後,就自己寫出來了。
```c=
/*
* Attempt to remove element from head of queue.
* Return true if successful.
* 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.)
* The space used by the list element and the string should be freed.
*/
bool q_remove_head(queue_t *q, char *sp, size_t bufsize)
{
if (!q || q->size == 0)
return false;
list_ele_t *rmvtarget = q->head;
q->head = q->head->next;
if (sp) {
size_t slen = strlen(rmvtarget->value);
size_t spSize = (bufsize > slen) ? bufsize - 1 : slen;
memset(sp, '\0', spSize + 1);
snprintf(sp, spSize, "%s", rmvtarget->value);
}
free(rmvtarget->value);
free(rmvtarget);
q->size--;
return true;
}
```
#### **int q_size(queue_t \*q)**
要注意,如果 q 為 NULL,還是回傳個 0
```c=
int q_size(queue_t *q)
{
if (!q)
return 0;
return q->size;
}
```
#### **void q_reverse(queue_t \*q)**
前陣子刷 leetcode、codility 有解過一樣的題目,不過當時是用 recursion 去解的,所以當下思考了很久。後來參考了大神的解法,果然用 iteration 就可以輕鬆解出來。
用三個 pointer 分別指向:前一個( prev )、現在( current )、下一個( next ),有點像過去、現在、緯來的概念。然後 current 往回指向 prev,接著他們三個都往前移動並重複上述的作法,即可完成 reverse。
要注意的是,第一次忘記加上第 21 行,會造成錯誤。也許其他強者有更好的作法。
```c=
void q_reverse(queue_t *q)
{
if (!q)
return;
if (q->size <= 1)
return;
list_ele_t *prev = q->head;
list_ele_t *current = q->head->next;
list_ele_t *next = q->head->next->next;
do {
current->next = prev;
if (!next)
break;
prev = current;
current = next;
next = next->next;
} while (current);
list_ele_t *tmp = q->head;
q->head = q->tail;
q->tail = tmp;
q->tail->next = NULL;
}
```
### 心得
今天實作出四個 function,難度不高,我並沒有參考太多,大概只有 remove 那裡有偷看一下強者們是怎麼理解這塊的。
每一次實作完一個 function 後,都會用 qtest 去測試,大多沒什麼大問題,功能性都有出來。不過,我用 make test 去測試,只拿到 59 分。其實不太知道剩下 sort 沒做完的話,實際會拿到幾分,但感覺有點太低了,因此我認為我的功能上應該有瑕疵,最大的可能就是 remove_head 那裡。
然後,我有使用到 **Address Sanitizer** 來幫我分析,卻出現了 global-buffer-overflow。看起來是 console.c 那裡有狀況,但我沒動到他,不太清楚原因。這幾天先搞懂 **Address Sanitizer** 好讓我知道它的報告怎麼看。
## 8/2
[8/2 commit](https://github.com/ShaneDocument/Linux-Kernel-Internals-lab0-c/commit/60dda4f636b2dcdde566624e1cde326577fb1e56)
### queue.c
#### insert 系列
使用 make test 後發現 insert head、inset tail 有錯誤,一開始抓不太到原因,後來才知道是幫新的 head 或 tail 的 **value** malloc 的時候,沒有去針對 NULL 的部分做處理。
於是,在 q_insert_head ( Line 12-15 ) 與 q_insert_tail ( Line 11-14 ) 新增了判斷。
要注意的是,如果 value 確實被指派為 NULL,則整個 list_ele_t 要被釋放( free )。
```c=
bool q_insert_head(queue_t *q, char *s)
{
if (q == NULL) {
return false;
}
list_ele_t *newh = malloc(sizeof(list_ele_t));
if (newh == NULL) {
return false;
}
size_t strSize = strlen(s) + 1;
newh->value = malloc(sizeof(char) * strSize);
if (!newh->value) {
free(newh);
return false;
}
snprintf(newh->value, strSize, "%s", s);
q->size++;
if (!q->tail) {
q->tail = newh;
}
newh->next = q->head;
q->head = newh;
return true;
}
```
```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;
size_t strSize = strlen(s) + 1;
newt->value = malloc(sizeof(char) * strSize);
if (!newt->value) {
free(newt);
return false;
}
snprintf(newt->value, strSize, "%s", s);
q->size++;
if (!q->tail) {
q->tail = newt;
newt->next = q->head;
q->head = newt;
/* If the tail is NULL, the whole queue is empty. So, inserting tail is
* same as inserting head. */
return true;
}
q->tail->next = newt;
q->tail = newt;
q->tail->next = NULL;
return true;
}
```
#### **bool q_remove_head(queue_t \*q, char \*sp, size_t bufsize)**
因為在 make test 的時候,也發現 remove_head 有問題,所以看一下。發現第 17 行的邏輯有問題,後來發現是英文理解力不夠。
根據 comment ,大概是說:sp 的大小最大就是 bufsize-1 個char,再加上一個空字串。
於是稍微改變一下第17行的邏輯:如果 bufsize 比較大,則使用 target 的 size 即可(夠用),如果 target 的 size 比較大,則因為最大就是使用 bufsize,所以指派給 sp 的量為 bufsize。
```c=
/*
* Attempt to remove element from head of queue.
* Return true if successful.
* 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.)
* The space used by the list element and the string should be freed.
*/
bool q_remove_head(queue_t *q, char *sp, size_t bufsize)
{
if (!q || q->size == 0)
return false;
list_ele_t *rmvtarget = q->head;
q->head = q->head->next;
if (sp) {
size_t slen = strlen(rmvtarget->value) + 1;
size_t spSize = (bufsize > slen) ? slen : bufsize - 1;
memset(sp, '\0', spSize + 1);
snprintf(sp, spSize + 1, "%s", rmvtarget->value);
}
free(rmvtarget->value);
free(rmvtarget);
q->size--;
return true;
}
```
#### **void q_sort(queue_t \*q)**
這部分我分成三個 function 來實作,分別是:merge、mergeSort 和原本的 q_sort。
**q_sort**
這部分很簡單,就只是先初步的判定 q 是否為 NULL 以及是否只有 0 或 1 個 element (不必sort了)
之後直接 call mergeSort!
```c=
/*
* Sort elements of queue in ascending order
* No effect if q is NULL or empty. In addition, if q has only one
* element, do nothing.
*/
void q_sort(queue_t *q)
{
if (!q)
return;
if (q->size <= 1)
return;
q->head = mergeSort(q, q->head);
}
```
每個人的實作方法好像都不一樣,先說說我的想法:因為這個 queue 還需要去維護 head 和 tail,所以傳參數的時候我很直觀的覺得,應該把 q 給傳進去,這樣在排序過程中才可以更新 tail 和 head 的部分。
**mergeSort**
這個 function 我寫的算是蠻 general 的 mergeSort。先判斷進來的 head 有幾個 element,如果只有 1 個或 0 個,那就直接 return 這個 head 即可。
之後利用 fast、slow 的方法,切割出兩個 list。
要注意的是,slow->next 才是指向中間 node 的,而第 11 行的作法就是在記錄這個node,並在第 12 行把上半部的 list 的尾端指向 NULL,以完成切割的動作。(我覺得這塊需要一點點想像力)
切割後,就是一般的 mergeSort 作法了。傳進去兩個 list,並期待回傳的 headA、headB 是兩個已經 sort 好的 list 的 head。
最後再將這兩個 head 帶進去 merge。
```c=
list_ele_t *mergeSort(queue_t *q, list_ele_t *head)
{
if (!head || !head->next)
return head;
list_ele_t *fast = head->next;
list_ele_t *slow = head;
while (fast && fast->next) {
fast = fast->next->next;
slow = slow->next;
}
fast = slow->next;
slow->next = NULL;
list_ele_t *headA = mergeSort(q, head);
list_ele_t *headB = mergeSort(q, fast);
return merge(q, headA, headB);
}
```
**merge**
因為要維護 head、tail,所以一樣要傳入 q。
先說說 tmp 的作用。它比較像是一個 pseudo node 的概念,總是指向較小值並 tmp = tmp -> next
但為什麼不是使用 malloc 來 new 一個新的 list_ele_t 呢?這題如果這樣做,邏輯上是對的,但會被 **harness.h** 這個外部參考給擋下來。這部分老師上課有提到。擋下來的大意是:**不允許使用 malloc**。所以這題老師的意思應該是不能使用額外的空間來達到 sort 的目的。因此才出現了 3-12 行。目的在於,我要先知道誰是 merge 完的 list 的 head,也因此我要先知道誰是比較小的值。
相信到第 23 行應該都沒什麼問題,就只是一般的 linked list 的 merge sort 會出現的伎倆。而第 24-36 行則是在把剩下來的 node 接上去,並且找到誰才是 tail。
```c=
list_ele_t *merge(queue_t *q, list_ele_t *headA, list_ele_t *headB)
{
list_ele_t *tmp;
list_ele_t *head;
if (strcmp(headB->value, headA->value) > 0) {
tmp = headA;
headA = headA->next;
} else {
tmp = headB;
headB = headB->next;
}
head = tmp;
while (headA && headB) {
if (strcmp(headB->value, headA->value) > 0) {
tmp->next = headA;
tmp = tmp->next;
headA = headA->next;
} else {
tmp->next = headB;
tmp = tmp->next;
headB = headB->next;
}
}
if (headA) {
tmp->next = headA;
while (headA->next) {
headA = headA->next;
}
q->tail = headA;
} else if (headB) {
tmp->next = headB;
while (headB->next) {
headB = headB->next;
}
q->tail = headB;
}
return head;
}
```
心得:
老實說,這份作業的演算法難度不高,但是非常吃細心程度。不幸的是,我是一個蠻粗心的傢伙。這份作業很強調 malloc 的保護(如果 NULL 了怎麼辦)等等,至少我在記憶體配置吃蠻多苦的,常常 bug 抓好幾個小時甚至一整天就是卡在這裡想不透。
我一直認為底層相當困難而沒有去碰過,資工系的學生似乎很少這麼不愛底層的。但修了這堂課,讓我對底層的印象稍微改觀。沒錯,他還是相當困難,但他其實相當有趣,相當 freedom。