owned this note
owned this note
Published
Linked with GitHub
# 2022q1 Homework1 (lab0)
contributed by < [`Pepitaw`](https://github.com/Pepitaw/lab0-c) >
###### tags: `linux2022`
## 實驗環境
```shell
$ gcc --version
gcc (Ubuntu 9.3.0-17ubuntu1~20.04) 9.3.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): 12
On-line CPU(s) list: 0-11
Thread(s) per core: 2
Core(s) per socket: 6
Socket(s): 1
NUMA node(s): 1
Vendor ID: GenuineIntel
CPU family: 6
Model: 158
Model name: Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz
Stepping: 10
CPU MHz: 2600.000
CPU max MHz: 4500.0000
CPU min MHz: 800.0000
BogoMIPS: 5199.98
L1d cache: 192 KiB
L1i cache: 192 KiB
L2 cache: 1.5 MiB
L3 cache: 12 MiB
NUMA node0 CPU(s): 0-11
```
## 開發紀錄
:::info
作業要求
- [x] q_new: 建立新的「空」佇列
- [x] q_free: 釋放佇列所佔用的記憶體
- [x] q_insert_head: 在佇列開頭 (head) 插入 (insert) 給定的新節點 (以 LIFO 準則)
- [x] q_insert_tail: 在佇列尾端 (tail) 插入 (insert) 給定的新節點 (以 FIFO 準則)
- [x] q_remove_head: 在佇列開頭 (head) 移去 (remove) 給定的節點
- [x] q_release_element: 釋放特定節點的記憶體空間
- [x] q_size: 計算目前佇列的節點總量
- [x] q_delete_mid: 移走佇列中間的節點
- [x] q_delete_dup: 在已經排序的狀況,移走佇列中具備重複內容的節點
- [x] q_swap: 交換佇列中鄰近的節點
- [x] q_reverse: 以反向順序重新排列鏈結串列,該函式不該配置或釋放任何鏈結串列節點,換言之,它只能重排已經存在的節點
- [x] q_sort: 以遞增順序來排序鏈結串列的節點
:::
### q_new
根據 `list.h` 參考函式 `INIT_LIST_HEAD` 初始化 q ,將 q 的 `next` 與 `prev` 指向自己。
:::spoiler 實際程式碼
```clike
struct list_head *q_new()
{
struct list_head *q = malloc(sizeof(struct list_head));
if (!q)
return NULL;
INIT_LIST_HEAD(q);
return q;
}
```
:::
- 實作流程
1. 使用 `malloc` 分配記憶體空間給指標 q
2. 如果 `malloc` 分配空間失敗,會回傳 `NULL`
3. 使用 `INIT_LIST_HEAD` 初始化指標 q
### q_free
根據 `list.h` 參考函式 `list_for_each_entry_safe` ,藉由副本遍歷各個 `entry` ,不會受到移除 `entry` 的影響
:::spoiler 實際程式碼
```clike
void q_free(struct list_head *l)
{
if (!l)
return;
element_t *pos, *n;
list_for_each_entry_safe (pos, n, l, list)
q_release_element(pos);
free(l);
}
```
:::
- 實作流程
1. 若 `head` 指向 NULL 就 `return`
2. 走訪整個 `linked list` ,對每個 `entry` 呼叫 `q_release_element` 釋放其空間
3. 釋放 `head` 的記憶體空間
### q_insert_head
根據 `list.h` 參考函式 `list_add` ,將新增的 `element_t` 加到 `head` 的 `next`
:::spoiler 實際程式碼
```clike
bool q_insert_head(struct list_head *head, char *s)
{
if (!head)
return false;
element_t *e = malloc(sizeof(element_t));
if (!e)
return false;
e->value = strdup(s);
if (!e->value) {
free(e);
return false;
}
list_add(&e->list, head);
return true;
}
```
:::
- 實作流程
1. 若 `head` 指向 NULL 就回傳 `false`
2. 使用 `malloc()` 分配 `element_t` 大小的記憶體空間給 e ,若分配失敗則回傳 `false`
3. 利用 `strdup` 會用 `malloc` 分配記憶體儲存字串的特性,直接指派給 e->value
4. 若 `strdup` 分配記憶體失敗就清除 e 與回傳 `false`
5. 加入新 `entry` 到list
:::spoiler 過去嘗試與心路歷程
1. 不知道在 queue.h 有定義 element_t ,自行定義 node
```clike
bool q_insert_head(struct list_head *head, char *s)
{
struct node{
char *str;
struct list_head list;
};
struct node *n = malloc(sizeof(struct node));
strcpy(n->str, s);
list_add(&n->list, head);
return true;
}
```
2. 遇到以下問題 Segmentation fault occurred. You dereferenced a NULL or invalid pointer
發現是 e->value 未配置記憶體,利用 strdup 配置記憶體與複製 *s (規格書中提到 Memory for the new string is obtained with malloc ,有malloc仍有配置失敗的時候)
```clike
element_t *e = malloc(sizeof(element_t));
if(!e)
return false;
strcpy(e->value, s);
list_add(&e->list, head);
return true;
```
3. 去看 list_add 加入 node 的位置
```clike
struct list_head *tmp = head->next;
list_del(tmp);
list_add(tmp, head->next->next);
```
由以下結果得知,可以直接插入指定位置的下一個 `entry`
```shell
cmd> ih ant
l = [ant dolphin dolphin dolphin gerbil bear dolphin meerkat bear bear bear cat]
cmd> reverse
l = [dolphin dolphin ant dolphin gerbil bear dolphin meerkat bear bear bear cat]
```
:::
### q_insert_tail
根據 `list.h` 參考函式 `list_add_tail` ,將新增的 `element_t` 加到 `head` 的 `prev` ,其餘同 `q_insert_head`
:::spoiler 實際程式碼
```clike
bool q_insert_tail(struct list_head *head, char *s)
{
if (!head)
return false;
element_t *e = malloc(sizeof(element_t));
if (!e)
return false;
e->value = strdup(s);
if (!e->value) {
free(e);
return false;
}
list_add_tail(&e->list, head);
return true;
}
```
:::
### q_remove_head
根據 `list.h` 參考函式 `list_first_entry` ,找到第一個 `entry` ,也參考函式 `list_del` 將目標 `entry` remove ,成為單一節點
:::spoiler 實際程式碼
```clike
element_t *q_remove_head(struct list_head *head, char *sp, size_t bufsize)
{
if (!head || head->next == head)
return NULL;
element_t *e = list_first_entry(head, element_t, list);
list_del(&e->list);
if (sp) {
strncpy(sp, e->value, bufsize - 1);
sp[bufsize - 1] = '\0';
}
return e;
}
```
:::
- 實作流程
1. 如果 `head` 指向 NULL 或只有 `head` 就回傳 `NULL`
2. 利用 `list_first_entry` 找到第一個 `entry`
3. 再用`list_del` 將目標 `entry` remove ,成為單一節點
4. 若 sp 存在,複製 e->value 到 sp 並補結尾 `\0`
:::spoiler 過去嘗試與心路歷程(有問題待解)
1. 想說用 strdup 修改 sp 不但有配置記憶體,還會自動補 `\0` ,但卻報錯還不知道原因。
```clike
if(sp){
sp = strndup(e->value, bufsize - 1);
}
```
ERROR: Failed to store removed value
:::
### q_remove_tail
根據 `list.h` 參考函式 `list_last_entry` ,找到最後一個 `entry` ,其餘與 `q_remove_head` 相同
:::spoiler 實際程式碼
```clike
element_t *q_remove_head(struct list_head *head, char *sp, size_t bufsize)
{
if (!head || head->next == head)
return NULL;
element_t *e = list_last_entry(head, element_t, list);
list_del(&e->list);
if (sp) {
strncpy(sp, e->value, bufsize - 1);
sp[bufsize - 1] = '\0';
}
return e;
}
```
:::
### q_size
根據 `list.h` 參考函式 `list_for_each()` 遍歷各個 `entry`
:::spoiler 實際程式碼
```clike
int q_size(struct list_head *head)
{
if (!head)
return false;
int i = 0;
struct list_head *lh;
list_for_each (lh, head)
i++;
return i;
}
```
:::
- 實作流程
1. 若傳入的 `head` 指向 NULL 就回傳 `false`
2. 每經過一個 `entry` i 就加一,最後即為size
### q_delete_mid
依據「龜兔賽跑」[Floyd’s Cycle detection](https://en.wikipedia.org/wiki/Cycle_detection),當 `rabbit` 跑完 list 時, `turtle` 跑到一半的特性,來尋找中間的節點
:::spoiler 實際程式碼
```clike
bool q_delete_mid(struct list_head *head)
{
struct list_head *fast = head->next, *slow = head->next;
for (; fast != head->prev && fast->next != head->prev;
fast = fast->next->next, slow = slow->next)
;
/*while(fast != NULL && fast->next != NULL){
fast = fast->next->next;
slow = slow->next;
}*/
slow->prev->next = slow->next;
slow->next->prev = slow->prev;
q_release_element(list_entry(slow, element_t, list));
return true;
}
```
:::
- 實作流程
1. 設置兩個 pointer , fast 一次跑一個 `entry` , slow 跑兩個 `entry`
2. 兩 pointer 開始遍歷 list ,當其中一個 pointer 跑完 list 時結束
3. 利用 q_release_element 對 slow 做 delete
:::spoiler 過去嘗試與心路歷程
1. 實做龜兔賽跑找中間節點,遇到以下問題
ERROR: Time limit exceeded. Either you are in an infinite loop, or your code is too inefficient
因為迴圈結束方式是判斷是否為 NULL ,但 circular linked list 是頭尾相接不會 NULL
```clike
for(struct list_head *fast = head, *slow = head; fast && fast->next; \
fast = fast->next->next, slow = slow->next);
```
:::
### q_delete_dup
:::spoiler 實際程式碼
```clike
bool q_delete_dup(struct list_head *head)
{
if (!head)
return false;
element_t *pos, *n;
bool used = false;
list_for_each_entry_safe (pos, n, head, list) {
if (pos->list.next != head &&
!strcmp(pos->value,
list_entry(pos->list.next, element_t, list)->value)) {
list_del(&pos->list);
q_release_element(pos);
used = true;
} else if (used) {
list_del(&pos->list);
q_release_element(pos);
used = false;
}
}
return true;
}
```
:::
- 實作流程
1. 如果 `head` 指向 NULL 就回傳 `false`
2. 利用 `list_for_each_entry_safe` 遍歷各個 `entry`
3. 利用 `strcmp` 比較 pos 跟下一個 `entry` 的 value 是否相同
4. 若 value 相同且 pos 不是結尾,就將 pos delete
5. 用 use 去紀錄該 `entry` 是否跟以刪除 `entry` 有相同的 value
### q_swap
根據 `list.h` 參考函式 `list_move_tail` 將 `entry` 放到 `head->prev`
:::spoiler 實際程式碼
```clike
void q_swap(struct list_head *head)
{
bool odd = q_size(head) & 0x01;
for (int i = q_size(head) >> 1; i > 0; i--) {
list_move_tail(head->next->next, head);
list_move_tail(head->next, head);
}
if (odd)
list_move_tail(head->next, head);
}
```
:::
- 實作流程
1. 先將第二個的 `entry` 放到尾巴
2. 再將第一個的 `entry` 放到尾巴
3. 總共做"總數的一半"次
4. 若總數為奇數,就要再做一次流程二
:::spoiler 過去嘗試與心路歷程 (有問題待解)
1. 想使用 `list.h` 函式 `list_swap` ,但發現未定義
我是在 linux kernel 版本 5.13.0 查到的函式,與我的 linux kernel 5.13.0-30-generic 相符,但還是找不到原因
```clike
void q_shuffle(struct list_head *head)
{
srand( time(NULL) );
for (int i = q_size(head); i > 0; i--) {
struct list_head *tmp = head->next;
for (int x = rand()%i; x > 0; x--){
tmp = tmp->next;
}
list_swap(tmp, head->prev);
//list_move_tail(tmp, head);
}
}
```
```shell
queue.c: In function ‘q_shuffle’:
queue.c:347:2: error: implicit declaration of function ‘list_swap’ [-Werror=implicit-function-declaration]
347 | list_swap(tmp, head->prev);
| ^~~~~~~~~
cc1: all warnings being treated as errors
make: *** [Makefile:50:queue.o] 錯誤 1
```
:::
### q_reverse
:::spoiler 實際程式碼
```clike
void q_reverse(struct list_head *head)
{
if (!head || head->next->next == head)
return;
struct list_head *pos = head->prev;
for (int i = q_size(head); i > 1; i--)
list_move_tail(pos->prev, head);
}
```
:::
- 實作流程
1. 若 `head` 指向 NULL 或 list 只有一個 `entry` 就回傳 NULL
2. 從尾巴開始反向遍歷,將 `entry` 依序放到尾巴
### q_sort
:::spoiler 實際程式碼
```clike
struct list_head *mergesort(struct list_head *node)
{
if (!node || !node->next)
return node;
struct list_head *fast = node, *slow = node;
for (; fast->next && fast->next->next;
fast = fast->next->next, slow = slow->next)
;
fast = slow->next;
slow->next = NULL;
struct list_head *l1 = mergesort(node);
struct list_head *l2 = mergesort(fast);
struct list_head t_head, *tmp = &t_head;
while (l1 && l2) {
if (strcmp(list_entry(l1, element_t, list)->value,
list_entry(l2, element_t, list)->value) > 0) {
tmp->next = l2;
l2 = l2->next;
} else {
tmp->next = l1;
l1 = l1->next;
}
tmp = tmp->next;
}
if (l1)
tmp->next = l1;
else
tmp->next = l2;
return t_head.next;
}
void q_sort(struct list_head *head)
{
if (!head || !head->next)
return;
head->prev->next = NULL;
struct list_head *list = head->next;
list = mergesort(list);
head->next = list;
struct list_head *i = head;
while (i->next != NULL) {
i->next->prev = i;
i = i->next;
}
head->prev = i;
i->next = head;
}
```
:::
- 實作流程
1. 若 `head` 指向 NULL 或 list 沒有 `entry` 就 return
2. 將尾巴的 next 指向 NULL,切斷 next 方向的 circular
3. 將去除 `head` 的 list 進行 mergesort
- 以下為 mergesort
4. 利用「龜兔賽跑」將 list 從中間一分為二
5. 再用 `strcmp` 比較大小,依小到大排列
6. 將 `head` 指向 排序好的 list
7. 最後利用 `list` 的 `next->prev` 將所有 `prev` 重新排列
## 理解 [Linux 核心中的排序實作](https://github.com/torvalds/linux/blob/master/lib/list_sort.c)
:::spoiler merge (pointer to pointer version)
```clike
static struct list_head *merge(void *priv, list_cmp_func_t cmp,
struct list_head *a, struct list_head *b)
{
struct list_head *head, **tail = &head;
for (;;) {
/* if equal, take 'a' -- important for sort stability */
if (cmp(priv, a, b) <= 0) {
*tail = a;
tail = &a->next;
a = a->next;
if (!a) {
*tail = b;
break;
}
} else {
*tail = b;
tail = &b->next;
b = b->next;
if (!b) {
*tail = a;
break;
}
}
}
return head;
}
```
:::
- 程式碼理解
1. 用 pointer to pointer 指向 a 與 b 之間較小的
2. 但沒有處理 prev 的部份
:::spoiler merge_final (pointer version)
```clike
static void merge_final(void *priv, list_cmp_func_t cmp, struct list_head *head,
struct list_head *a, struct list_head *b)
{
struct list_head *tail = head;
u8 count = 0;
for (;;) {
/* if equal, take 'a' -- important for sort stability */
if (cmp(priv, a, b) <= 0) {
tail->next = a;
a->prev = tail;
tail = a;
a = a->next;
if (!a)
break;
} else {
tail->next = b;
b->prev = tail;
tail = b;
b = b->next;
if (!b) {
b = a;
break;
}
}
}
/* Finish linking remainder of list b on to tail */
tail->next = b;
do {
/*
* If the merge is highly unbalanced (e.g. the input is
* already sorted), this loop may run many iterations.
* Continue callbacks to the client even though no
* element comparison is needed, so the client's cmp()
* routine can invoke cond_resched() periodically.
*/
if (unlikely(!++count))
cmp(priv, b, b);
b->prev = tail;
tail = b;
b = b->next;
} while (b);
/* And the final links to make a circular doubly-linked list */
tail->next = head;
head->prev = tail;
}
```
:::
- 程式碼理解
1. 比較 a 跟 b 的大小,用 tail 的 next 指向比較小的 entry
2. 一路遍歷兩個 linked list 直到其中一個 linked list 結束(a 或 b 指向 NULL)
3. 用 b 指向還未走到尾巴的 linked list (未經過的 entry)
4. 用 tail 的 next 指向剩下的 entry
5. 遍歷完剩下的 entry ,並用 count 紀錄剩下的 entry 數量
6. 利用 unlikely 將 count 轉成 bool
7. 頭尾相接成為 circular
- 程式碼差異
1. 在每次 merge 時就處理好 prev 與 next 的 link
我則是只看 next ,將 linked list 用 next 排列完後,再一次將所有的 prev 處理好
2. 計算兩個 linked list 的 merge 是否 entry 數量相近,我則沒有考慮
:::spoiler list_sort
```clike
void list_sort(void *priv, struct list_head *head, list_cmp_func_t cmp)
{
struct list_head *list = head->next, *pending = NULL;
size_t count = 0; /* Count of pending */
if (list == head->prev) /* Zero or one elements */
return;
/* Convert to a null-terminated singly-linked list. */
head->prev->next = NULL;
do {
size_t bits;
struct list_head **tail = &pending;
/* Find the least-significant clear bit in count */
for (bits = count; bits & 1; bits >>= 1)
tail = &(*tail)->prev;
/* Do the indicated merge */
if (likely(bits)) {
struct list_head *a = *tail, *b = a->prev;
a = merge(priv, cmp, b, a);
/* Install the merged result in place of the inputs */
a->prev = b->prev;
*tail = a;
}
/* Move one element from input list to pending */
list->prev = pending;
pending = list;
list = list->next;
pending->next = NULL;
count++;
} while (list);
/* End of input; merge together all the pending lists. */
list = pending;
pending = pending->prev;
for (;;) {
struct list_head *next = pending->prev;
if (!next)
break;
list = merge(priv, cmp, pending, list);
pending = next;
}
/* The final merge, rebuilding prev links */
merge_final(priv, cmp, head, pending, list);
}
```
:::
- 程式碼理解
1. 用 head->next == head->prev 去判斷是否只有一個 entry 或沒有 entry
2. 將 circular 的 next 部份斷開
- 程式碼差異
## 運用 Valgrind 排除 qtest 實作的記憶體錯誤
:::info
作業目標
- [ ] 運用 Valgrind 排除 qtest 實作的記憶體錯誤,並透過 Massif 視覺化 “simulation” 過程中的記憶體使用量,需要設計對應的實驗
:::
==更新中==
:::spoiler
```shell
$ valgrind -q --leak-check=full ./qtest -f traces/trace-01-ops.cmd
==2951== 5 bytes in 1 blocks are still reachable in loss record 1 of 2
==2951== at 0x483B7F3: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==2951== by 0x4A5250E: strdup (strdup.c:42)
==2951== by 0x1108BE: linenoiseHistoryAdd (linenoise.c:1236)
==2951== by 0x111451: linenoiseHistoryLoad (linenoise.c:1325)
==2951== by 0x10C6B0: main (qtest.c:951)
==2951==
==2951== 160 bytes in 1 blocks are still reachable in loss record 2 of 2
==2951== at 0x483B7F3: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==2951== by 0x11087E: linenoiseHistoryAdd (linenoise.c:1224)
==2951== by 0x111451: linenoiseHistoryLoad (linenoise.c:1325)
==2951== by 0x10C6B0: main (qtest.c:951)
```
```clike
linecopy = strdup(line);
```
:::
## 在 qtest 提供新的命令 shuffle
:::info
作業目標
- [x] 在 `qtest` 提供新的命令 `shuffle` ,允許藉由 [Fisher–Yates shuffle](https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle) 演算法,對佇列中所有節點進行洗牌 (shuffle) 操作
- [ ] 解釋 [select](https://man7.org/linux/man-pages/man2/select.2.html) 系統呼叫在本程式的使用方式,並分析 console.c 的實作,說明其中運用 CS:APP [RIO](http://csapp.cs.cmu.edu/2e/ch10-preview.pdf) 套件 的原理和考量點。可對照參考 CS:APP [第 10 章重點提示](https://hackmd.io/@sysprog/H1TtmVTTz)
:::
:::spoiler 程式碼
```clike
void q_shuffle(struct list_head *head)
{
srand(time(NULL));
for (int i = q_size(head); i > 0; i--) {
struct list_head *tmp = head->next, *tail = head->prev;
for (int x = rand() % i; x > 0; x--)
tmp = tmp->next;
list_del(tail);
list_add(tail, tmp->prev);
list_move_tail(tmp, head);
}
}
```
:::
- 實作流程
1. 將 `linked list` 的 size 不斷減一,作為隨機變數的範圍
2. 遍歷去找隨機變數所指定的 `entry`
3. 用 `list_del` 將尾巴成為獨立的 `entry` ,將尾巴用 `list_add` 加到目標 `entry` 的下一個 `entry` ,再將目標 `entry` 移動到尾巴,作為交換
## 在 qtest 提供新的命令 web
## 參考資料
- HackMD 模板參考 [`Risheng1128`](https://hackmd.io/kZtEa_LdSGKVQGg8jczrLQ?view)
- Mergesort 分割部分 參考 [`Risheng1128`](https://hackmd.io/kZtEa_LdSGKVQGg8jczrLQ?view)
- [K01: lab0](https://hackmd.io/@sysprog/linux2022-lab0)
- [你所不知道的 C 語言: linked list 和非連續記憶體](https://hackmd.io/@sysprog/c-linked-list)
- [Floyd’s Cycle detection](https://en.wikipedia.org/wiki/Cycle_detection)
- [quick-sort.c](https://github.com/sysprog21/linux-list/blob/master/examples/quick-sort.c)