# 2024q1 Homework1 (lab0)
contributed by < [`ICARUSHERALD96500`](https://github.com/ICARUSHERALD96500/lab0-c) >
:::danger
詳閱作業說明,指定的格式中有空白字元,留意各式細節,唯有重視小處並步步為營,方可挑戰原始程式碼規模超越三千七百萬行的 Linux 核心
:::
## 開發環境
```shell
$ gcc --version
gcc (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0
$ lscpu
Architecture: x86_64
CPU op-mode(s): 32-bit, 64-bit
Address sizes: 48 bits physical, 48 bits virtual
Byte Order: Little Endian
CPU(s): 16
On-line CPU(s) list: 0-15
Vendor ID: AuthenticAMD
Model name: AMD Ryzen 7 6800H with Radeon Graphics
CPU family: 25
Model: 68
Thread(s) per core: 2
Core(s) per socket: 8
Socket(s): 1
Stepping: 1
CPU max MHz: 4785.0000
CPU min MHz: 400.0000
BogoMIPS: 6388.18
```
## 指定的佇列操作
### 結構體
```c
struct list_head {
struct list_head *prev;
struct list_head *next;
};
typedef struct {
char *value;
struct list_head list;
} element_t;
```
### `q_new`
原先對 Linux Kernel API 不熟悉,寫出 `q_new`
- 流程
1.`malloc` 分配`sizeof(struct list_head)` 大小的記憶體空間。
2.若所分配的位址 `q` 失敗,為 `NULL` ,則直接 `return q` ,`q_new`函式就會回傳 `NULL` 。
```diff
struct list_head *q = malloc(sizeof(struct list_head));
if (q) {
q->prev = q;
q->next = q;
}
return q;
```
後來發現 `list.h` 當中就有 `INIT_LIST_HEAD` 故更改為下列
```c
static inline void INIT_LIST_HEAD(struct list_head *head)
{
head->next = head->prev = head;
}
```
### `q_free`
```c
void q_free(struct list_head *head)
{
if (head == NULL){
return;
}
while (list_empty(head)){
struct list_head *n = head ->next;
list_del(n);
free(n);
}
free(head);
}
```
### `q_insert_head`
```diff
bool q_insert_head(struct list_head *head, char *s)
{
if (head && s){
element_t * new_element = malloc(sizeof(element_t));
if (new_element){
new_element->value = strdup(s);
if (new_element->value){
head->next = new_element;
return true;
}
else {
free(new_element);
}
}
}
else{
return false;
}
}
```
:::danger
使用 `git diff` 或 `diff` 命令來產生程式碼之間的差異列表,不要憑感覺填寫,注意位移量。
:::
對於指標是否被成功分配記憶體的條件判斷的寫法,關於 `if (head != NULL)` 以及 `if (head)` 二者間的差別,雖然效果相同,後者更為簡潔,但為求可讀與維護性。
:::danger
1. 明確指出你參考的 GitHub 帳號
2. 什麼叫做「多數會選擇」?我們本該做出更精簡更有效的程式碼,不是去迎合「多數」
:::
### `q_remove_head`
起初對於 `*head` 意思與題目誤解,導致誤以為是移除對於 `head` 前一節點的元素,寫出下列的程式碼。
導致於使用 `./qtest` 測試時反而產生把 link list tail 移除的效果。
```c
element_t *q_remove_head(struct list_head *head, char *sp, size_t bufsize)
{
if (!head || !list_empty(head))
return NULL;
struct list_head *rm_list_head = head->prev;
struct list_head *dummy_prev = rm_list_head->prev;
struct list_head *dummy_next = rm_list_head->next;
dummy_prev->next = dummy_next;
dummy_next->prev = dummy_prev;
element_t *rm_element = list_entry(rm_list_head, element_t, list);
if (!sp || !(rm_element->value))
return NULL;
strncpy(sp, rm_element->value, bufsize);
sp[bufsize -1] = '\0';
return rm_element;
}
```
並修正下列錯誤
```diff
- struct list_head *rm_list_head = head->prev;
+ struct list_head *rm_list_head = head->next;
```
> [commit bb0dbb1](https://github.com/sysprog21/lab0-c/commit/bb0dbb1a6afcebfff7398da067caeacbf6a0b29d)
熟悉linux kernel API並修正函式中不必要的 `list` 宣告
在當中發現 `list_del`
```diff
@@ -76,16 +74,18 @@ element_t *q_remove_head(struct list_head *head, char *sp, size_t bufsize)
{
if (!head || list_empty(head))
return NULL;
+ element_t *f = list_first_entry(head, element_t, list);
+ list_del(&f->list);
- struct list_head *rm_list_head = head->next;
- struct list_head *dummy_prev = rm_list_head->prev;
- struct list_head *dummy_next = rm_list_head->next;
- dummy_prev->next = dummy_next;
- dummy_next->prev = dummy_prev;
+ if (sp) {
+ size_t copy_size =
+ strlen(f->value) < (bufsize - 1) ? strlen(f->value) : (bufsize - 1);
+ strncpy(sp, f->value, copy_size);
+ sp[copy_size] = '\0';
+ }
+ return f;
- element_t *rm_element = list_entry(rm_list_head, element_t, list);
- if (!sp || !(rm_element->value))
- return NULL;
- strncpy(sp, rm_element->value, bufsize);
- sp[bufsize - 1] = '\0';
- return rm_element;
}
```
### `q_remove_tail`
使用與 `q_remove_head` 相同邏輯
```c
if (!head || !list_empty(head))
return NULL;
struct list_head *rm_list_head = head->prev;
struct list_head *dummy_prev = rm_list_head->prev;
struct list_head *dummy_next = rm_list_head->next;
dummy_prev->next = dummy_next;
dummy_next->prev = dummy_prev;
element_t *rm_element = list_entry(rm_list_head, element_t, list);
if (!sp || !(rm_element->value))
return NULL;
strncpy(sp, rm_element->value, bufsize);
sp[bufsize -1] = '\0';
return rm_element;
```
> [commit bb0dbb1](https://github.com/sysprog21/lab0-c/commit/bb0dbb1a6afcebfff7398da067caeacbf6a0b29d)
同樣修正與函式`q_remove_head`中同樣不必要的 `list` 宣告
```diff
@@ -93,16 +93,18 @@ element_t *q_remove_tail(struct list_head *head, char *sp, size_t bufsize)
{
if (!head || list_empty(head))
return NULL;
+ element_t *f = list_first_entry(head, element_t, list);
+ list_del(&f->list);
- struct list_head *rm_list_head = head->next;
- struct list_head *dummy_prev = rm_list_head->prev;
- struct list_head *dummy_next = rm_list_head->next;
- dummy_prev->next = dummy_next;
- dummy_next->prev = dummy_prev;
+ if (sp) {
+ size_t copy_size =
+ strlen(f->value) < (bufsize - 1) ? strlen(f->value) : (bufsize - 1);
+ strncpy(sp, f->value, copy_size);
+ sp[copy_size] = '\0';
+ }
+ return f;
- element_t *rm_element = list_entry(rm_list_head, element_t, list);
- if (!sp || !(rm_element->value))
- return NULL;
- strncpy(sp, rm_element->value, bufsize);
- sp[bufsize - 1] = '\0';
- return rm_element;
}
```
### `q_delete_mid`
在 `make test` 中出現 `ERROR: Removed value gerbil != expected value bear`。發現是在走訪到中間節點過程中使用 `for()` 迴圈使用錯誤導致函數輸出不為所求。修正確定其為走訪到 mid point,並改為使用 `while` 使之更為簡短。
```diff=
bool q_delete_mid(struct list_head *head)
{
if (head == NULL || list_empty(head))
return false;
int count = q_size(head) / 2;
struct list_head *temp = head->next;
- for (int i = 0; i <= count; i++) {
+ while (count--) {
temp = temp->next;
}
list_del(temp);
q_release_element(list_entry(temp, element_t, list));
return true;
}
```
### `q_swap`
首先我嘗試的方法使用,在每兩個節點中進行 `next` 與 `prev` 之間的關係互換
```c
if (!head || list_empty(head))
return;
int count = 0;
struct list_head *node, *prev_node, *dummy_prev, *dummy_next;
for (node = (head)->next; node != (head); node = node->next) {
count++;
if (count % 2 == 0) {
prev_node = node->prev;
//
dummy_prev = prev_node->prev;
dummy_next = node->next;
dummy_prev->next = node;
dummy_next->prev = prev_node;
//
node->prev = dummy_prev;
node->next = prev_node;
//
prev_node->prev = node;
prev_node->next = dummy_next;
}
}
```
但在該方法下會產生的問題是 head 所指向的節點會因為 prev 與 next 關係重組而被排列到最後。效果如下:
```
new l = [1 2 3 4 5 6 7]
swap l = [2 3 4 3 7 6 1]
```
熟悉kernel api後改進:
> commit [b18ff64]()
```diff
@@ -226,22 +226,13 @@ void q_swap(struct list_head *head)
if (!head || list_empty(head))
return;
int count = 0;
- struct list_head *node, *prev_node, *dummy_prev, *dummy_next;
- for (node = (head)->next; node != (head); node = node->next) {
+ struct list_head *node, *prev = head;
+ list_for_each (node, head) {
count++;
if (count % 2 == 0) {
- prev_node = node->prev;
- //
- dummy_prev = prev_node->prev;
- dummy_next = node->next;
- dummy_prev->next = node;
- dummy_next->prev = prev_node;
- //
- node->prev = dummy_prev;
- node->next = prev_node;
```
### `q_reverse`
### Valgrind + 自動測試程式
閱讀 'qtest命令直譯器的實作',發現其中展示qtest原理,展示在 qtest.c 當中的 `init_cmd()` 添加:
```c
bool do_hello(int argc, char *argv[])
{
return (bool) printf("Hello, World\n");
}
```
結果 `make` 後產生下列錯誤代碼:
```bash
/usr/bin/ld: queue.o: in function `do_hello':
/home/b37265417/linux2024/lab0-c/queue.c:390: multiple definition of `do_hello'; console.o:/home/b37265417/linux2024/lab0-c/console.c:417: first defined here
collect2: error: ld returned 1 exit status
make: *** [Makefile:49: qtest] Error 1
```
將 `bool do_hello(int argc, char *argv[])` 改為 `static bool do_hello(int argc, char *argv[])` 後解決。
## Fisher–Yates shuffle
### 將 shuffle 引入 `qtest` 命令中
參照自動測試程式在qtest新增shuffle功能,在 `console.h` 的巨集中:
```c
/* Add a new command */
void add_cmd(char *name, cmd_func_t operation, char *summary, char *parameter);
#define ADD_COMMAND(cmd, msg, param) add_cmd(#cmd, do_##cmd, msg, param)
```
要添加新命令需要透過在 `console.c` 底下的 `console_init` 當中使用 `add_cmd` 函式。該函式需要輸入三個引數 `"<instruction>"`、`<do_*>`、`"<document>"`。以呼叫同樣宣告在`console.c` 底下命名為 `do_<instruction>` 的函式(因為在巨集中 `do_##cmd` 將所要呼叫的函式名稱 concatinate 為 cmd 前加上 `do_`)。
以 `q_new` 函式被呼叫的流程為例。首先 `qtest.c` 的主函式中, `init_cmd()` 使用 `ADD_COMMAND` 將 `q_new()` 加入。`add_cmd` 接收的第二項引數是型別為 `cmd_func_t` 的函式指標。該指標指向 `console.c` 的 Build-in funciton 或 `qtest.c` 的 `do_*` 函式,如 `do_new()`,再由該函式中呼叫在 `queue.c` 中所撰寫的各項 `q_*` 函式。
將 `shuffle` 分別新增在 `init_cmd` 與 `console_init` 底下的的差別似乎不大,同樣都可以執行。但最後決定添加在 `console` 底下,因為其他鏈結串列的操作同樣新增在這個類別。
**qtest.c**
```c
static void consloe_init()
{...
ADD_COMMAND(shuffle, "Fisher-Yates shrffle", "")
...
}
```
```c
static bool do_shuffle(int argc, char *argv[])
{
if (!current || !current->q)
return;
if (q_size(current->q) < 2)
return;
if (exception_setup(true))
q_shuffle(current->q);
q_show(3);
return !error_check();
}
```
**queue.h**
```c
void q_shuffle(struct list_head *head) ;
```
**queue.c**
```c
static inline void swap(struct list_head *node_1, struct list_head *node_2)
{
if (node_1 == node_2)
return;
struct list_head *node_1_prev = node_1->prev;
struct list_head *node_2_prev = node_2->prev;
if (node_1->prev != node_2) list_move(node_2, node_1_prev);
list_move(node_1, node_2_prev);
}
void q_shuffle(struct list_head *head)
{
if (!head || list_is_singular(head))
return;
struct list_head *last = head;
int size = q_size(head);
while (size > 0) {
int index = rand() % size;
struct list_head *new = last->prev;
struct list_head *old = new;
while (index--)
old = old->prev;
swap(new, old);
last = last->prev;
size--;
}
return;
}
```
### `shuffle`
![FisherYatesAnim](https://hackmd.io/_uploads/SkP3sj606.gif =50%x)
## 研讀 Linux 核心原始程式碼 list_sort.c
- [ ] 研讀 Linux 核心原始程式碼 list_sort.c
- [ ] 嘗試引入 Linux 核心原始程式碼到 lab0-c 專案
- [ ] 比較自己實做的merge sort 和 Linux 核心程式碼之間的效能落差
### 引入 Linux list_sort.c 到 lab0-c
修改檔案: `qtest.c`、`Makefile`
新增檔案: `list_sort.c`、`list_sort.h`
**list_sort.c**
為了要讓 `./qtest` command line 當中的 `time` 指令可以執行 linux `list_sort` 的方法,利用首先在 lab0-c中新增 [`list_sort.c`](https://github.com/torvalds/linux/blob/master/lib/list_sort.c) ,並移除不會用到的部份:
:::spoiler
```c
// SPDX-License-Identifier: GPL-2.0
// #include <linux/bug.h>
// #include <linux/compiler.h>
// #include <linux/export.h>
// #include <linux/string.h>
// #include <linux/list_sort.h>
// #include <linux/list.h>
...
// EXPORT_SYMBOL(list_sort);
```
:::
**list_sort.h**
其中包含 `list_sort.c` 所需要的定義
:::spoiler
```c
#ifndef _LIST_SORT_H
#define _LIST_SORT_H
#include <stdint.h>
#include "list.h"
#define USE_LINUX_SORT 0
#define likely(x) __builtin_expect(!!(x), 1)
#define unlikely(x) __builtin_expect(!!(x), 0)
typedef int __attribute__((nonnull(2,3))) (*list_cmp_func_t)(void *, const struct list_head *, const struct list_head *);
void list_sort(void *priv, struct list_head *head, list_cmp_func_t cmp);
#endif /* End of _LIST_SORT_H_ */
```
:::
**Makefile**
為了使 `make` 時連同 `list_sort` 加入源代碼文件的目標編譯文件,所需更改:
```
OBJS := qtest.o report.o console.o harness.o queue.o list_sort.o \
random.o dudect/constant.o dudect/fixture.o dudect/ttest.o \
shannon_entropy.o \
linenoise.o web.o \
```
**qtest.c**
在 `qtest.c` 中建立函式:
```c
__attribute__((nonnull(2,3)))
int cmp(void *priv, const struct list_head *list1, const struct list_head *list2)
{
element_t *list1_entry = list_entry(list1, element_t, list);
element_t *list2_entry = list_entry(list2, element_t, list);
return strcmp(list1_entry->value, list2_entry->value) < 0 ? 0 : 1;
}
```
### 比較自己的merge sort 和 Linux 核心程式碼之間的效能落差
新增一筆測資 `trace-sort.cmd` 於 `trace/` 中:
```
option fail 0
option malloc 0
new
ih RAND 10000
time
sort
time
```
並將 `Makefile` 中 check 的部份改為下列。執行 `make check` 時,便會使用自己定義的 `trace-sort.cmd` 測資。
```
check: qtest
./$< -v 3 -f traces/trace-sort.cmd
```
結果如下:
| 資料總數 | q_sort() 測試1(s) | q_sort() 測試2(s) | q_sort() 測試3(s) | q_sort() 測試4(s) | q_sort() 測試5(s) |
| -------- | ----------------- | ----------------- | ----------------- | ----------------- | ----------------- |
| 100000 | 0.08 | 0.076 | 0.078 | 0.075 | 0.076 |
| 300000 | 0.302 | 0.309 | 0.316 | 0.309 | 0.313 |
| 500000 | 0.614 | 0.585 | 0.578 | 0.576 | 0.585 |
| 資料總數 | list_sort() 測試1(s) | list_sort() 測試2(s) | list_sort() 測試3(s) | list_sort() 測試4(s) | list_sort() 測試5(s) |
| -------- | ----------------- | ----------------- | ----------------- | ----------------- | ----------------- |
| 100000 | 0.108 | 0.055 | 0.054 | 0.056 | 0.055 |
| 300000 | 0.180 | 0.178 | 0.179 | 0.178 | 0.180 |
| 500000 | 0.303 | 0.311 | 0.305 | 0.304 | 0.308 |
比較 `q_sort` 、 `list_sort` 性能差異:
## 閱讀論文 Dude, is my code constant time?
論文提出一種測試程式執行時間是否為 constant time 的工具 dudect,這個方法不需要硬體模型,以消弭不同硬體架構下對測量的影響。目的是驗證理論上時間複雜度為 constant time ,但實則存在 time leakage 的問題,如此可以藉由程式處理不同輸入時的響應時間差,藉此推敲程式內部細節,引發資安疑慮。
文中取 '字串比較' 典型場景。普遍作法為逐字元比較,若一但遇到不相符者即刻中斷比較並回傳`false` ,如此便可以透過從輸入字元到回傳的時間推敲是第幾個字元錯誤。較佳的作法為,就算遭遇不相符,也同樣比對完剩餘字元,以此避免time leakage。
作者採取 Students' T test 其中 Two-sample T test 進行實驗。使用兩種測資,第一種為全數固定為某值的固定測資、第二種為亂數的隨機測資。在虛無假設下進行 Fix-vs-Random test。
## 參考資訊
* [Risheng1128](https://hackmd.io/@Risheng/linux2022-lab0)
* [Laneser](https://hackmd.io/@laneser/linux2022_lab0)