# 2023q1 Homework1(lab0)
contributed by < `vax-r` >
## 開發環境
```bash
$ gcc --version
gcc (Ubuntu 9.4.0-1ubuntu1~20.04.1) 9.4.0
$ lscpu
Architecture: x86_64
CPU op-mode(s): 32-bit, 64-bit
Byte Order: Little Endian
Address sizes: 39 bits physical, 48 bits virtual
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) i5-8265U CPU @ 1.60GHz
Stepping: 11
CPU MHz: 1800.000
CPU max MHz: 3900.0000
CPU min MHz: 400.0000
BogoMIPS: 3600.00
Virtualization: VT-x
L1d cache: 128 KiB
L1i cache: 128 KiB
L2 cache: 1 MiB
L3 cache: 6 MiB
NUMA node0 CPU(s): 0-7
Vulnerability Itlb multihit: KVM: Mitigation: VMX disabled
Vulnerability L1tf: Not affected
Vulnerability Mds: Mitigation; Clear CPU buffers; SMT vulnerable
Vulnerability Meltdown: Not affected
Vulnerability Mmio stale data: Mitigation; Clear CPU buffers; SMT vulnerable
Vulnerability Retbleed: Mitigation; IBRS
Vulnerability Spec store bypass: Mitigation; Speculative Store Bypass disabled v
ia prctl and seccomp
Vulnerability Spectre v1: Mitigation; usercopy/swapgs barriers and __user
pointer sanitization
Vulnerability Spectre v2: Mitigation; IBRS, IBPB conditional, RSB filling
, PBRSB-eIBRS Not affected
Vulnerability Srbds: Mitigation; Microcode
Vulnerability Tsx async abort: Not affected
Flags: fpu vme de pse tsc msr pae mce cx8 apic sep mtr
r pge mca cmov pat pse36 clflush dts acpi mmx f
xsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rd
tscp lm constant_tsc art arch_perfmon pebs bts
rep_good nopl xtopology nonstop_tsc cpuid aperf
mperf pni pclmulqdq dtes64 monitor ds_cpl vmx e
st tm2 ssse3 sdbg fma cx16 xtpr pdcm pcid sse4_
1 sse4_2 x2apic movbe popcnt tsc_deadline_timer
aes xsave avx f16c rdrand lahf_lm abm 3dnowpre
fetch cpuid_fault epb invpcid_single ssbd ibrs
ibpb stibp tpr_shadow vnmi flexpriority ept vpi
d ept_ad fsgsbase tsc_adjust bmi1 avx2 smep bmi
2 erms invpcid mpx rdseed adx smap clflushopt i
ntel_pt xsaveopt xsavec xgetbv1 xsaves dtherm i
da arat pln pts hwp hwp_notify hwp_act_window h
wp_epp md_clear flush_l1d arch_capabilities
```
## 開發過程
### q_free
本來的做法是直接 `free(l)`
但在make check的時候會碰到error
發現應該要將每個element逐個釋放, 並且value和list也該分開釋放
於是我使用 `list_for_each()`此api將queue中的element逐個走訪並釋放
但後來參閱其他學員的作業發現他人使用的是`list_for_each_safe()`
我在閱讀linux kernel api時有看到數種不同api都是用來iterate over the list
但不確定每種api各自的使用時機
於是我研究了每個api和他們的使用時機
https://stackoverflow.com/questions/9207850/why-do-we-need-list-for-each-safe-in-for-deleting-nodes-in-kernel-linked-list
make之後發現不能使用list_for_each_safe() 應該使用的是list_for_each_entry_safe()
因為我們的queue當中其實每個node都是一個element_t, 在其中使用struct list_head來串起每個node, 使用list_for_each_entry_safe()才能iterate over我們定義的型別
最後的問題是本來在此函式的最後, 我使用list_del(l)來將此queue的head給釋放, 但在check的時候會遇到error告訴我還有一個block沒被釋放
將其改成free(l)
原來list_del並不會釋放該空間, 只會將該node從list當中移除
```clike
void q_free(struct list_head *l)
{
if (!l)
return;
element_t *cur, *tmp;
list_for_each_entry_safe (cur, tmp, l, list)
q_release_element(cur);
free(l);
}
```
### q_insert_head
剛完成的程式碼如下
```clike
bool q_insert_head(struct list_head *head, char *s)
{
element_t *new_element = malloc(sizeof(element_t));
INIT_LIST_HEAD(&(new_element->list));
new_element->value = s;
list_add(&(new_element->list), head);
return true;
}
```
進行make check之後會得到以下error
```bash
ERROR: Need to allocate and copy string for new queue element
```
透過gdb檢查記憶體配置出現什麼錯誤
值得注意的是若直接使用gdb開始分析function
應該每次都會跳進一個叫做exception_setup()的function
原來在harness.c當中有定義此函式
經過分析後 在執行q_insert_head之前 是先呼叫queue_insert這個function
而command line輸入的字串則是透過`argv[1]` 來取得
若沒有為`new_element->value` 配置新的空間再把輸入的字串放入
則有可能因為`argv[1]`位置的內容被改寫而影響到結果
把程式碼改寫成以下後 結果看似正確
```clike
bool q_insert_head(struct list_head *head, char *s)
{
element_t *new_element = malloc(sizeof(element_t));
INIT_LIST_HEAD(&(new_element->list));
new_element->value = malloc(sizeof(char) * (strlen(s) + 1));
strcpy(new_element->value, s);
new_element->value = strdup(s);
list_add(&(new_element->list), head);
return true;
}
```
進行make check檢查後q_insert_head結果正確
於是我嘗試提交git commit
結果它回覆我
```bash
Dangerous function detected in /home/yi-shin-cheng/linux-internals/lab0-c/queue.c
39: strcpy(new_element->value, s);
```
同時叫我去查看 https://security.web.cern.ch/recommendations/en/codetools/c.shtml
原來`strcpy, strcmp, strcat`等functions都不會檢查buffer length所以很容易把超過destination能容納的範圍寫入destination
最好能使用`strlcpy`
但必須事先知道source的大小否則很難配置空間
每次都配置很大的記憶體空間又很浪費
改為使用memcpy就通過了
### q_remove_head
實做此部份沒有遇到太大的困難
主要是在把字串複製進入sp的部份, 因為需要保證sp當中的字串是NULL terminated
本來想使用strscpy_pad 但本次作業所使用的library不包含此API
所以使用strncpy, 但strncpy不保證destination為NULL terminated
所以我先計算出destination的長度, 並在sp的結尾塞入`\0`
```clike
element_t *q_remove_head(struct list_head *head, char *sp, size_t bufsize)
{
if (!head || list_empty(head))
return NULL;
element_t *removed_element = list_first_entry(head, element_t, list);
list_del(&removed_element->list);
int len = strlen(removed_element->value) < bufsize
? strlen(removed_element->value) + 1
: bufsize;
if (sp) {
strncpy(sp, removed_element->value, len);
*(sp + len - 1) = '\0';
}
return removed_element;
}
```
### q_delete_mid
此題為刪除queue中位於中間的結點
假設有n個element, 則刪除第$\lfloor n/2 \rfloor$個element
本來想說要用fast, slow pointer, 但想到這是一個circular linked list
若用fast, slow pointer不僅要先計算queue的size, 還得把整個queue繞一圈
想到若一個pointer往前走, 一個往後走, 則兩個pointer相撞的位置剛好會在中間
```clike
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 *forward, *backward;
forward = head;
backward = head;
do {
forward = forward->next;
backward = backward->prev;
} while (forward != backward &&
(forward->next != backward && backward->prev != forward));
list_del(forward);
return true;
}
```
以上的程式碼依然存在問題, 在進行trace-02-ops.cmd裡面的測試時, 全部執行結果都正確不過最後free queue的時候會跳出error告訴你還有4個blocks沒有被free
原來問題出在我沒有正確的將整個entry給delete掉, list_del僅僅是將queue的node裡面用來串接相鄰的node的struct list_head給刪掉,整個element_t並沒有被刪除
使用list_entry來找到forward被哪個element_t給包住並將它們正確的移除並刪除
```diff
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 *forward, *backward;
forward = head;
backward = head;
do {
forward = forward->next;
backward = backward->prev;
} while (forward != backward &&
(forward->next != backward && backward->prev != forward));
+ element_t *entry = list_entry(forward, element_t, list);
list_del(forward);
+ q_release_element(entry);
return true;
}
```
### q_delete_dup
此函式要求刪除在queue當中所有含有重複字串的node
leetcode的範例中對於重複字串的node會留下一個
但此作業中要求全部刪除, 因此需要額外紀錄目前走訪的node是否是之前重複過的
```clike
bool q_delete_dup(struct list_head *head)
{
// https://leetcode.com/problems/remove-duplicates-from-sorted-list-ii/
if (!head)
return false;
element_t *current = NULL, *tmp1 = NULL;
struct list_head *cursor;
int dup = 0;
list_for_each_entry_safe (current, tmp1, head, list) {
cursor = current->list.next;
if (cursor != head && !strcmp(current->value, list_entry(cursor, element_t, list)->value)) {
list_del(&(current->list));
q_release_element(current);
dup = 1;
}
else if (dup) {
list_del(&(current->list));
q_release_element(current);
dup = 0;
}
}
return true;
}
```
### q_swap
本來想利用linux kernel api當中的list_swap來實做, 但library中沒有此function, 所以我參考其他學長的作法, 利用list_move的方式, 換個思維方式也能達到swap的效果
值得注意的是, 當我使用list_for_each_safe來做iterate的時候,會出現無窮迴圈的現象, 使用list_for_each就不會, 這是為什麼呢?
```clike
void q_swap(struct list_head *head)
{
// https://leetcode.com/problems/swap-nodes-in-pairs/
if (!head || list_empty(head))
return;
struct list_head *cursor;
list_for_each(cursor, head) {
if ( cursor->next == head )
break;
list_move(cursor, cursor->next);
}
}
```
### q_reverse
瀏覽kernel api後沒有注意到適合實做此function的api, 於是我利用以下方法將整個queue給反轉, iterate整個list的時候保存當下節點的next, 並變更current->next和current->prev
```clike
void q_reverse(struct list_head *head)
{
if (!head || list_empty(head))
return;
struct list_head *current, *next;
current = head;
do {
next = current->next;
current->next = current->prev;
current->prev = next;
current = next;
} while (current != head);
}
```
### q_reverseK
解題耗時較長, 不能另外增開空間所以作法必須是in-place的
我的作法是將k個node先做成一個list, 接著將此list丟入q_reverse進行反轉後再和原本的list接起來
```clike
void q_reverseK(struct list_head *head, int k)
{
// https://leetcode.com/problems/reverse-nodes-in-k-group/
if (!head || list_empty(head))
return;
int times = q_size(head) / k;
int i, j, remain = 0;
if (times*k != q_size(head))
remain = q_size(head) - times*k;
struct list_head *cur_h, *next_h, *tmp;
next_h = head->next;
head->next = head;
head->prev = head;
for ( i=0; i<times; i++ ) {
cur_h = next_h;
for (j=0;j<k;j++)//find the next head
next_h = next_h->next;
//make a sub-list
cur_h->prev = next_h->prev;
next_h->prev->next = cur_h;
q_reverse(cur_h);
cur_h = cur_h->next;
head->prev->next = cur_h;
cur_h->prev->next = head;
tmp = head->prev;
head->prev = cur_h->prev;
cur_h->prev = tmp;
}
if (remain) {
cur_h = next_h;
for (j=0;j<remain-1;j++)
next_h = next_h->next;
cur_h->prev = next_h;
next_h->next = cur_h;
head->prev->next = cur_h;
cur_h->prev->next = head;
tmp = head->prev;
head->prev = cur_h->prev;
cur_h->prev = tmp;
}
}
```
### q_sort
我參考 @alanjian85 的作法並改寫成自己的風格
使用遞迴版本merge sort的作法
未來可以改進成非遞迴版本以避免stack overflow
```clike
void q_merge_two(struct list_head *list1, struct list_head *list2, bool descend)
{
//assume to be merge in descending order
if (!list1 || !list2)
return;
struct list_head temp_head;
INIT_LIST_HEAD(&temp_head);
element_t *entry1, *entry2, *entry_selected;
while (!list_empty(list1) && !list_empty(list2)) {
entry1 = list_first_entry(list1, element_t, list);
entry2 = list_first_entry(list2, element_t, list);
bool cond;
if (descend)
cond = strcmp(entry1->value, entry2->value) > 0;
else
cond = strcmp(entry1->value, entry2->value) < 0;
entry_selected = cond ? entry1 : entry2;
list_move_tail(&entry_selected->list, &temp_head);
}
list_splice_tail_init(list1, &temp_head);
list_splice_tail_init(list2, &temp_head);
list_splice(&temp_head, list1);
}
/* Sort elements of queue in ascending/descending order */
void q_sort(struct list_head *head, bool descend)
{
if (!head || list_empty(head) || list_is_singular(head))
return;
struct list_head *forward, *backward;
forward = head;
backward = head;
do {
forward = forward->next;
backward = backward->prev;
} while (forward != backward &&
(forward->next != backward && backward->prev != forward));
struct list_head left;
list_cut_position(&left, head, forward);
q_sort(&left, descend);
q_sort(head, descend);
q_merge_two(head, &left, descend);
}
```
### q_ascend / q_descend
本來我的想法是利用雙層迴圈順向走訪, 並將外層迴圈走到的node和內層迴圈比較, 想法很直觀, 但這樣實做出來的程式碼可讀性跟效率都較差
調整過後利用反向走訪, 同樣是雙層迴圈但外層迴圈走訪的node不是一個一個走訪, 而是只走訪比當前所在之node->value還小(還大)之節點
內層迴圈用來拜訪當前node所有左邊的節點並與node->value比較
此處若能用list_for_each_entry_reverse來改寫會更好, 但library中沒有此api
```clike
/* Remove every node which has a node with a strictly less value anywhere to
* the right side of it */
int q_ascend(struct list_head *head)
{
// https://leetcode.com/problems/remove-nodes-from-linked-list/
if (!head || list_empty(head) || q_size(head) == 1)
return q_size(head);
struct list_head *rr, *r_cursor = head->prev, *safe;
element_t *pivot_entry, *tmp_entry;
while (r_cursor != head) {
pivot_entry = list_entry(r_cursor, element_t, list);
for(rr=r_cursor->prev, safe = rr->prev; rr!=head; rr=safe, safe = safe->prev) {
tmp_entry = list_entry(rr, element_t, list);
if (strcmp(pivot_entry->value, tmp_entry->value) >= 0)
break;
list_del(rr);
q_release_element(tmp_entry);
}
r_cursor = rr;
}
return q_size(head);
}
/* Remove every node which has a node with a strictly greater value anywhere to
* the right side of it */
int q_descend(struct list_head *head)
{
// https://leetcode.com/problems/remove-nodes-from-linked-list/
if (!head || list_empty(head) || q_size(head) == 1)
return q_size(head);
struct list_head *rr, *r_cursor = head->prev, *safe;
element_t *pivot_entry, *tmp_entry;
while (r_cursor != head) {
pivot_entry = list_entry(r_cursor, element_t, list);
for(rr=r_cursor->prev, safe = rr->prev; rr!=head; rr=safe, safe = safe->prev) {
tmp_entry = list_entry(rr, element_t, list);
if (strcmp(pivot_entry->value, tmp_entry->value) <= 0)
break;
list_del(rr);
q_release_element(tmp_entry);
}
r_cursor = rr;
}
return q_size(head);
}
```
### q_merge
此處為參考學長們作法的實做, 未來應作為改進的項目
```clike
int q_merge(struct list_head *head, bool descend)
{
// https://leetcode.com/problems/merge-k-sorted-lists/
if (!head || list_empty(head))
return 0;
queue_contex_t *first = list_first_entry(head, queue_contex_t, chain);
int size = q_size(first->q);
if (list_is_singular(head))
return size;
queue_contex_t *second =
list_entry(first->chain.next, queue_contex_t, chain);
queue_contex_t *end = NULL;
while (second != end) {
size += q_size(second->q);
q_merge_two(first->q, second->q, descend);
if (!end)
end = second;
list_move_tail(&second->chain, head);
second = list_entry(first->chain.next, queue_contex_t, chain);
}
return size;
}
void q_shuffle(struct list_head *head)
{
if (!head || list_empty(head))
return;
int i = q_size(head) - 1;
struct list_head *node;
for (node = head->prev; node != head; node = node->prev, i--) {
struct list_head *other = head->next;
for (int j = rand() % (i + 1); j > 0; j--)
other = other->next;
if (node == other)
continue;
struct list_head *temp = other->prev;
list_move(other, node);
list_move(node, temp);
node = other;
}
}
```
## q_insert_head, q_insert_tail, q_remove_head, q_remove_tail time complexity testing
第17個測驗檔案是用來測試以上四個函式的時間複雜度是否是constant time
目前提交無法通過
還找不到原因, 每個function都各自通過測試過