# 2025q1 Homework1 (lab0)
contributed by < `liangchingyun` >
### Reviewed by `Urbaner3`
>根據作業 review,檢查事項 1, 5, 8 嘗試聚焦並整理程式碼,並加強改進。
>6.48 Alternate Keywords 此段落,注意表達的因果關係,我不是很容易直接解釋或是想到他與什麼有關聯,請舉例,並以此為例,再檢查作業一二文章表達通順。
>鼓勵根據作業要求、能力,挑戰論文閱讀,設計實驗。為了專題做預備。如果排程器、中斷、線程等名詞有不理解,隨時提出問題,向講師、同學、討論區發問,養成習慣作筆記,保持進步,一定會成功的。
{%hackmd NrmQUGbRQWemgwPfhzXj6g %}
## 開發環境
```shell
$ gcc --version
gcc (Ubuntu 13.3.0-6ubuntu2~24.04) 13.3.0
Copyright (C) 2023 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
$ lscpu
Architecture: x86_64
CPU op-mode(s): 32-bit, 64-bit
Address sizes: 39 bits physical, 48 bits virtual
Byte Order: Little Endian
CPU(s): 12
On-line CPU(s) list: 0-11
Vendor ID: GenuineIntel
Model name: Intel(R) Core(TM) i7-8700 CPU @ 3.20GHz
CPU family: 6
Model: 158
Thread(s) per core: 2
Core(s) per socket: 6
Socket(s): 1
Stepping: 10
CPU(s) scaling MHz: 39%
CPU max MHz: 4600.0000
CPU min MHz: 800.0000
BogoMIPS: 6399.96
Flags: fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_
tsc art arch_perfmon pebs bts rep_good nopl xtopology nonstop_tsc cpuid aperfmperf pni pclmulqdq dtes64 monitor ds_cpl smx est tm2 ssse3 sdbg fma cx16 xtpr pd
cm pcid sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm abm 3dnowprefetch cpuid_fault epb pti ssbd ibrs ibpb stibp fsgs
base tsc_adjust bmi1 avx2 smep bmi2 erms invpcid mpx rdseed adx smap clflushopt intel_pt xsaveopt xsavec xgetbv1 xsaves dtherm ida arat pln pts hwp hwp_notify
hwp_act_window hwp_epp md_clear flush_l1d arch_capabilities
Caches (sum of all):
L1d: 192 KiB (6 instances)
L1i: 192 KiB (6 instances)
L2: 1.5 MiB (6 instances)
L3: 12 MiB (1 instance)
NUMA:
NUMA node(s): 1
NUMA node0 CPU(s): 0-11
Vulnerabilities:
Gather data sampling: Mitigation; Microcode
Itlb multihit: KVM: Mitigation: VMX unsupported
L1tf: Mitigation; PTE Inversion
Mds: Mitigation; Clear CPU buffers; SMT vulnerable
Meltdown: Mitigation; PTI
Mmio stale data: Mitigation; Clear CPU buffers; SMT vulnerable
Reg file data sampling: Not affected
Retbleed: Mitigation; IBRS
Spec rstack overflow: Not affected
Spec store bypass: Mitigation; Speculative Store Bypass disabled via prctl
Spectre v1: Mitigation; usercopy/swapgs barriers and __user pointer sanitization
Spectre v2: Mitigation; IBRS; IBPB conditional; STIBP conditional; RSB filling; PBRSB-eIBRS Not affected; BHI Not affected
Srbds: Mitigation; Microcode
Tsx async abort: Mitigation; TSX disabled
```
## 針對佇列操作的程式碼實作
> [作業要求](https://hackmd.io/@sysprog/linux2025-lab0-a)
### q_size
```c
int q_size(struct list_head *head)
{
if (!head)
return 0;
int len = 0;
struct list_head *pos;
list_for_each (pos, head)
len++;
return len;
}
```
> `list_for_each`: 走訪雙向鏈結串列中的每個節點
### q_new
一開始並沒有初始化`list_head`,這導致分配的記憶體中`list_head`結構的成員未被設置為預期的初始值,顯示錯誤:
`Segmentation fault occurred. You dereferenced a NULL or invalid pointerAborted (core dumped)`
修改後正確初始化`list_head`
```diff
struct list_head *q_new()
{
struct list_head *new_head = malloc(sizeof(struct list_head));
- if (new_head != NULL) {
+ if (new_head) {
+ INIT_LIST_HEAD(new_head);
return new_head;
}
return NULL;
}
```
### q_insert_head & q_insert_tail
`q_insert_head` : 將一個新元素插入到佇列的首部。
`q_insert_tail` : 將一個新元素插入到佇列的尾部。
```c
bool q_insert_head(struct list_head *head, char *s)
{
if (!head)
return false;
element_t *new_ele = malloc(sizeof(element_t));
if (!new_ele)
return false;
INIT_LIST_HEAD(&new_ele->list);
new_ele->value = strdup(s);
if (!new_ele->value) {
free(new_ele);
return false;
}
list_add(&new_ele->list, head);
return true;
}
```
`q_insert_tail` 即是將 `list_add` 改為 `list_add_tail`
> `strdup`: 分配記憶體並將字符串複製到該記憶體中
### q_free
一開始使用 `list_for_each_entry_safe` 巨集指令,在commit時遇到問題:
```
Following files need to be cleaned up:
queue.c
Running static analysis...
queue.c:35:5: error: There is an unknown macro here somewhere. Configuration is required. If list_for_each_entry_safe is a macro then please configure it. [unknownMacro]
list_for_each_entry_safe (entry, safe, l, list)
^
Fail to pass static analysis.
```
因此我去查標頭檔中對`list_for_each_entry_safe` 的定義:
```c
#if __LIST_HAVE_TYPEOF
#define list_for_each_entry_safe(entry, safe, head, member) \
for (entry = list_entry((head)->next, typeof(*entry), member), \
safe = list_entry(entry->member.next, typeof(*entry), member); \
&entry->member != (head); entry = safe, \
safe = list_entry(safe->member.next, typeof(*entry), member))
#endif
```
根據 [3.4 Options Controlling C Dialect](http://gcc.gnu.org/onlinedocs/gcc/C-Dialect-Options.html),使用 `-ansi` 會關閉某些 GCC 特性,因此會禁用 `typeof` 關鍵字。在 [6.48 Alternate Keywords](https://gcc.gnu.org/onlinedocs/gcc/Alternate-Keywords.html#index-_005f_005fextension_005f_005f) 中提到使用替代關鍵字(如 `__asm__` 、`__extension__` 、`__inline__` 和 `__typeof__` )即可在啟用 `-ansi` 時使用。然而,若修改標頭檔會出現錯誤訊息,因此改成不使用巨集指令的寫法:
```diff
void q_free(struct list_head *l)
{
if (!l || list_empty(l)) {
free(l);
return;
}
- element_t *entry, *safe;
+ struct list_head *pos = l->next;
- list_for_each_entry_safe (
- entry, safe, l,
- list)
- q_release_element(entry);
+ while (pos != l) {
+ element_t *entry = list_entry(pos, element_t, list);
+ pos = pos->next;
+ q_release_element(entry);
+ }
free(l);
return;
}
```
此函式完成後 `q_new`, `q_insert_head` 和 `q_insert_tail` 均得以通過 make test
> `struct list_head`: 鏈結串列的節點
> `element_t`: 是包含鏈結節點的結構體
> `list_entry`: 一個巨集,用來從鏈結節點指標(pos)回推「擁有這個節點的結構體」
### q_romove_head & q_romove_tail
`q_romove_head` : 將佇列的首部的元素移除。
`q_romove_tail` : 將佇列的尾部的元素移除。
```diff
element_t *q_remove_head(struct list_head *head, char *sp, size_t bufsize)
{
if (!head || list_empty(head))
return NULL;
element_t *first_entry = list_first_entry(head, element_t, list);
if (sp != NULL) {
- sp = strncpy(sp, first_entry->value, bufsize - 1);
+ strncpy(sp, last_entry->value, bufsize - 1);
sp[bufsize - 1] = '\0'; // Ensure the string is null-terminated
}
list_del(&first_entry->list);
return first_entry;
}
```
`strncpy` 函數已經將 `first_entry->value` 中的內容複製到 `sp` 中,因此不需要將 `sp` 重新賦值。
> `first_entry->list`: first_entry 節點中用來鏈接到其他節點的鏈結串列指針。
然而,此作法無法通過 trace-17-complexity。因為這個函數沒有處理 `first_entry->value` 可能為 `NULL` 的情況。如果 `value` 為 `NULL`,在使用 `strncpy` 時可能會導致未定義行為。
```diff
if (sp != NULL) {
+ if (first_entry->value != NULL) {
strncpy(sp, first_entry->value, bufsize - 1);
sp[bufsize - 1] = '\0'; // Ensure the string is null-terminated
+ } else {
+ sp[0] = '\0';
+ }
}
```
修改後即可通過 trace-17-complexity。
`q_remove_tail` 即是將 `first_entry` 改為 `last_entry` 。
### q_swap
原本的程式碼:
```c
void q_swap(struct list_head *head)
{
if (head == NULL || head->next == head || head->next->next == head)
return;
struct list_head *iterator = head->next;
struct list_head *temp;
while (iterator != head && iterator->next != head) {
temp = iterator->next;
iterator->prev->next = temp;
temp->prev = iterator->prev;
iterator->next = temp->next;
if (temp->next != head) {
temp->next->prev = iterator;
}
temp->next = iterator;
iterator->prev = temp;
iterator = iterator->next->next;
}
}
```
因為 `q_swap` 是 `q_reverseK` 在 `K=2` 時的特例,因此改為以下程式碼:
```c
void q_swap(struct list_head *head)
{
q_reverseK(head, 2);
}
```
### q_delete_mid
將節點從鏈結串列中刪除後,需要手動釋放這些節點所佔用的記憶體,因此做了以下修正:
```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 *ptr_1 = head->next;
struct list_head *ptr_2 = head->next;
while (ptr_1 != head && ptr_1->next != head) {
ptr_1 = ptr_1->next->next;
ptr_2 = ptr_2->next;
}
+ element_t *mid = list_entry(ptr_2, element_t, list);
list_del(ptr_2);
+ free(mid->value);
+ free(mid);
return true;
}
```
### q_delete_dup
參考 [2095. Delete the Middle Node of a Linked List](https://leetcode.com/problems/remove-duplicates-from-sorted-list-ii/) 的其中一個[解法](https://leetcode.com/problems/delete-the-middle-node-of-a-linked-list/solutions/4705282/simple-beginner-friendly-dry-run-two-pointer-approach-time-o-n-space-o-1-gits)
> 使用 `strcmp` 比較兩個節點的 `value` 是否相同
### q_reverse
```c
void q_reverse(struct list_head *head)
{
if (!head || list_empty(head)) {
return;
}
struct list_head *cur = head->next;
struct list_head *temp = NULL;
while (cur != head) {
temp = cur->next;
cur->next = cur->prev;
cur->prev = temp;
cur = temp;
}
temp = head->next;
head->next = head->prev;
head->prev = temp;
}
```
### q_reverseK
```c
void q_reverseK(struct list_head *head, int k)
{
// https://leetcode.com/problems/reverse-nodes-in-k-group/
if (!head) {
return;
}
int split_time = q_size(head) / k;
struct list_head *tail;
LIST_HEAD(tmp);
LIST_HEAD(new_head);
for (int i = 0; i < split_time; i++) {
int j = 0;
list_for_each (tail, head) {
if (j >= k) {
break;
}
j++;
}
list_cut_position(&tmp, head, tail->prev);
q_reverse(&tmp);
list_splice_tail_init(&tmp, &new_head);
}
list_splice_init(&new_head, head);
}
```
### q_sort
參考同學寫法,使用 `MergeTwoLists`
* 將 `descend ^ (strcmp(e1->value, e2->value) < 0)` 定義成 `bool condition`,精簡了 `node` 的指派邏輯。
* 改寫了 `fast` 和 `slow` 指標的初始化方式,並將 `for` 循環改為 `while` 進行快慢指標的移動。
* 因為 `e1` 及 `e2` 的值不會遭變更,因此加上 `const`
```diff
void mergeTwoLists(struct list_head *L1, struct list_head *L2, bool descend)
{
if (!L1 || !L2)
return;
struct list_head head;
INIT_LIST_HEAD(&head);
while (!list_empty(L1) && !list_empty(L2)) {
- element_t *e1 = list_first_entry(L1, element_t, list);
- element_t *e2 = list_first_entry(L2, element_t, list);
- struct list_head *node = (descend ^ (strcmp(e1->value, e2->value) < 0))
- ? L1->next
- : L2->next;
+ const element_t *e1 = list_first_entry(L1, element_t, list);
+ const element_t *e2 = list_first_entry(L2, element_t, list);
+ bool condition = (descend ^ (strcmp(e1->value, e2->value) < 0));
+ struct list_head *node = condition ? L1->next : L2->next;
list_move_tail(node, &head);
}
list_splice_tail_init(list_empty(L1) ? L2 : L1, &head);
list_splice(&head, L1);
}
/* Sort elements of queue in ascending/descending order */
void q_sort(struct list_head *head, bool descend)
{
// https://hackmd.io/IKsnn85aRHGMrNcRP7BJ1Q?view#2024q1-Homework1-lab0
if (!head || list_empty(head) || list_is_singular(head))
return;
struct list_head *slow = head;
- const struct list_head *fast = NULL;
+ struct list_head *fast = head->next;
- for (fast = head->next; fast != head && fast->next != head;
- fast = fast->next->next)
- slow = slow->next;
+ while (fast != head && fast->next != head) {
+ slow = slow->next;
+ fast = fast->next->next;
+ }
struct list_head left;
list_cut_position(&left, head, slow);
q_sort(&left, descend);
q_sort(head, descend);
mergeTwoLists(head, &left, descend);
}
```
### q_ascend & q_descend
簡化了走訪邏輯,直接用 `pos` 來控制位置,讓程式看起來更直觀。
```diff
int q_ascend(struct list_head *head)
{
// https://leetcode.com/problems/remove-nodes-from-linked-list/
if (!head || list_empty(head))
return 0;
- element_t *right = list_entry(head->prev, element_t, list);
- element_t *left = list_entry(head->prev->prev, element_t, list);
- while (&left->list != head) {
+ struct list_head *pos = head->prev;
+ while (pos != head && pos->prev != head) {
+ const element_t *right = list_entry(pos, element_t, list);
+ element_t *left = list_entry(pos->prev, element_t, list);
if (strcmp(right->value, left->value) > 0) {
- left = list_entry(left->list.prev, element_t, list);
- right = list_entry(right->list.prev, element_t, list);
+ pos = pos->prev;
} else {
list_del(&left->list);
free(left->value);
free(left);
- left = list_entry(right->list.prev, element_t, list);
}
}
return q_size(head);
}
}
```
`q_descend` 即是將 `strcmp(right->value, left->value) > 0` 改成 `strcmp(right->value, left->value) < 0`
### q_merge
原本 `queue_to_merge` 是在迴圈外部宣告,但這個變數只在迴圈中使用,把它移進迴圈內。
```diff
int q_merge(struct list_head *head, bool descend)
{
if (!head || list_empty(head)) {
return 0;
}
queue_contex_t *base_queue = list_first_entry(head, queue_contex_t, chain);
if (list_is_singular(head)) {
return base_queue->size;
}
- queue_contex_t *queue_to_merge;
struct list_head *pos, *next;
list_for_each_safe (pos, next, head) {
if (pos == &base_queue->chain) {
continue;
}
- queue_to_merge = list_entry(pos, queue_contex_t, chain);
+ queue_contex_t *queue_to_merge = list_entry(pos, queue_contex_t, chain);
list_splice_tail_init(queue_to_merge->q, base_queue->q);
base_queue->size += queue_to_merge->size;
}
q_sort(base_queue->q, descend);
return base_queue->size;
}
```
## Valgrind 分析記憶體問題
> [作業要求](https://hackmd.io/@sysprog/linux2025-lab0-b)
### 運用 Valgrind 排除 qtest 實作的記憶體錯誤
執行 `$ make valgrind` 後,得到以下訊息:
```
# Explicitly disable sanitizer(s)
make clean SANITIZER=0 qtest
make[1]: Entering directory '/home/liangchingyun/linux2025/lab0-c'
rm -f qtest.o report.o console.o harness.o queue.o random.o dudect/constant.o dudect/fixture.o dudect/ttest.o shannon_entropy.o linenoise.o web.o list_sort.o .qtest.o.d .report.o.d .console.o.d .harness.o.d .queue.o.d .random.o.d .dudect/constant.o.d .dudect/fixture.o.d .dudect/ttest.o.d .shannon_entropy.o.d .linenoise.o.d .web.o.d .list_sort.o.d *~ qtest /tmp/qtest.*
rm -rf .dudect
rm -rf *.dSYM
(cd traces; rm -f *~)
CC qtest.o
CC report.o
CC console.o
CC harness.o
CC queue.o
CC random.o
CC dudect/constant.o
CC dudect/fixture.o
CC dudect/ttest.o
CC shannon_entropy.o
CC linenoise.o
CC web.o
CC list_sort.o
LD qtest
make[1]: Leaving directory '/home/liangchingyun/linux2025/lab0-c'
cp qtest /tmp/qtest.fRLs65
chmod u+x /tmp/qtest.fRLs65
sed -i "s/alarm/isnan/g" /tmp/qtest.fRLs65
scripts/driver.py -p /tmp/qtest.fRLs65 --valgrind
--- Trace Points
+++ TESTING trace trace-01-ops:
# Test of insert_head and remove_head
--- trace-01-ops 5/5
+++ TESTING trace trace-02-ops:
# Test of insert_head, insert_tail, remove_head, remove_tail, and delete_mid
--- trace-02-ops 6/6
+++ TESTING trace trace-03-ops:
# Test of insert_head, insert_tail, remove_head, reverse and merge
--- trace-03-ops 6/6
+++ TESTING trace trace-04-ops:
# Test of insert_head, insert_tail, size, swap, and sort
--- trace-04-ops 6/6
+++ TESTING trace trace-05-ops:
# Test of insert_head, insert_tail, remove_head, reverse, size, swap, and sort
--- trace-05-ops 6/6
+++ TESTING trace trace-06-ops:
# Test of insert_head, insert_tail, delete duplicate, sort, descend and reverseK
--- trace-06-ops 6/6
+++ TESTING trace trace-07-string:
# Test of truncated strings
--- trace-07-string 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-robust:
# Test operations on NULL queue
--- trace-10-robust 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-malloc:
# Test of malloc failure on new
--- trace-13-malloc 6/6
+++ TESTING trace trace-14-perf:
# Test performance of insert_tail, reverse, and sort
Warning: Skip checking the stability of the sort because the number of elements 2000000 is too large, exceeds the limit 100000.
--- trace-14-perf 6/6
+++ TESTING trace trace-15-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-15-perf 6/6
+++ TESTING trace trace-16-perf:
# Test performance of insert_tail
--- trace-16-perf 6/6
+++ TESTING trace trace-17-complexity:
# Test if time complexity of q_insert_tail, q_insert_head, q_remove_tail, and q_remove_head is constant
Probably constant time
Probably constant time
Probably constant time
Probably constant time
--- trace-17-complexity 5/5
--- TOTAL 100/100
Test with specific case by running command:
scripts/driver.py -p /tmp/qtest.fRLs65 --valgrind -t <tid>
```
結果顯示程式通過所有測試,也沒有發生 Memory Leak 、 Invalid Memory Access 等問題。
### 透過 Massif 視覺化 simulation 過程中的記憶體使用量
## 整合網頁伺服器
## 研讀 lib/list_sort.c 並嘗試改進
> [作業要求](https://hackmd.io/@sysprog/linux2025-lab0-e)
這段程式碼涉及到三個核心函數:
1. `merge()` - 合併兩個已排序的單向鏈結串列,並返回排序後的鏈結串列
2. `merge_final()` - 進行最終合併並恢復雙向鏈結串列
3. `list_sort()` - 主排序函數,負責將鏈結串列拆分並合併排序
它使用空結尾的單向鏈結串列來合併子串列,最後再還原為雙向鏈結串列,提高效能。時間複雜度為 O(n log n),適合排序大型鏈結串列。
### 流程舉例
在 `list_sort()` 中,排序過程透過 Bottom-Up 的方式進行合併:
1. 初始狀態:每個節點視為獨立的排序單元(1 個元素的區塊)
e.g., `[4] [2] [5] [3] [1]`
2. 合併相鄰的兩個區塊,確保每個合併後的區塊仍然有序
`[4]` + `[2]` → `[2,4]`
`[5]` + `[3]` → `[3,5]`
`[1]` (單獨保留,因為目前沒有另一個可合併的區塊)
3. 繼續合併已排序的區塊
`[2,4]` + `[3,5]` → `[2,3,4,5]`
`[1]` 保留
4. 最終合併
`[2,3,4,5]` + `[1]` → `[1,2,3,4,5]`
### 將 `list_sort.c` 加入 `lab0-c` 專案
> Commit: [0c1203d](https://github.com/sysprog21/lab0-c/commit/0c1203dc385bfbe40802563912f17ed72a08da16)
將 Linux 核心原始程式碼的 `list_sort.c` 和 `list_sort.h` 複製到 `lab0-c` 專案中,並且為了成功編譯做了以下修改:
* `list_sort.h` 與 `list.h` 路徑修正。
* 刪掉 `EXPORT_SYMBOL(list_sort)`
* 將 `u8` 改成 `uint8_t` 作為 `count`,並加入 `#include <stdint.h>`
* 移除不必要的 `#include`,手動加入 `likely` 和 `unlikely` 的定義。
在 `queue.h` 、 `queue.c` 中加入對應程式碼後,於 Makefile 中新增 `list_sort.o` 。另外,我在 `qtest.c` 中新增 option ksort ,用來切換原本的 `q_sort` 與新的 `q_ksort` 。
```c
static int sort_option = 0;
```
```c
add_param("ksort", &sort_option,
"Whether to use Linux kernel sorting algorithm", NULL);
```
```diff
if (current && exception_setup(true))
- q_sort(current->q, descend);
+ sort_option ? q_ksort(current->q, descend) : q_sort(current->q, descend);
exception_cancel();
```
### 效能分析
我執行 `./qtest` 並使用以下的命令來分析排序效能:
```
new
option ksort 1/0 <= (1: q_ksort; 0: q_sort)
ih RAND 100000
time sort
```
執行結果如下:
| q_sort | q_ksort |
| -------- | -------- |
| 0.111 | 0.089 |
因為不能變更 queue.h ,所以 commit 時將以下程式刪除,並註解掉相關部份:
```c
void q_ksort(struct list_head *head, bool descend);
```
### 開發 Timsort 排序程式
> 參考資料:[Linux 核心專題: 改進 lib/list_sort.c](https://hackmd.io/@sysprog/Hy5hmaKBh#TODO-%E5%BC%95%E5%85%A5-Timsort-%E5%8F%8A%E5%85%B6%E8%AE%8A%E5%BD%A2)、[Timsort 研究與對 Linux 核心貢獻嘗試](https://hackmd.io/@yanjiew/linux2023q1-timsort)、[最貼近現實的排序演算法- Timsort](https://jason18153.medium.com/%E6%9C%80%E8%B2%BC%E8%BF%91%E7%8F%BE%E5%AF%A6%E7%9A%84%E6%8E%92%E5%BA%8F%E6%BC%94%E7%AE%97%E6%B3%95-timsort-a75da75b65a2)、[linux23q1-timsort](https://github.com/visitorckw/linux23q1-timsort)
[liangchingyun/Sorts-main](https://github.com/liangchingyun/Sorts-main)
正確處理空指針的情況:
```diff
size_t get_run_size(struct list_head *run_head)
{
- return run_head ? 0 : (size_t)(run_head)->next->prev;
+ if (run_head == NULL || run_head->next == NULL ||
+ run_head->next->prev == NULL) {
+ return 0;
+ }
+ return (size_t)(run_head->next->prev);
}
```
縮小變數作用範圍:
```diff
static inline void list_rotate_left(struct list_head *head)
{
- struct list_head *first;
if (!list_empty(head)) {
- first = head->next;
+ struct list_head *first = head->next;
list_move_tail(first, head);
}
}
```
將沒有被修改的變數宣告為 `const` 指標
```diff
static inline int list_empty_careful(const struct list_head *head)
{
- struct list_head *next = head->next;
+ const struct list_head *next = head->next;
return (next == head) && (next == head->prev);
}
```
將 `head` 初始化為 `NULL`
```diff
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;
+ struct list_head *head = NULL, **tail = &head;
```
刪掉沒被使用的變數
```diff
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;
- uint8_t count = 0;
```
指標 `tp` 指向 `stk - 1` 導致未定義行為的修正
```diff
void shiverssort(void *priv, struct list_head *head, list_cmp_func_t cmp)
{
struct list_head *list = head->next;
- struct run stk[MAX_MERGE_PENDING], *tp = stk - 1;
+ struct run stk[MAX_MERGE_PENDING], *tp = stk;
if (head == head->prev)
return;
/* Convert to a null-terminated singly-linked list. */
head->prev->next = NULL;
do {
- tp++;
/* Find next run */
tp->list = list;
list = find_run(priv, list, &tp->len, cmp);
tp = merge_collapse(priv, cmp, stk, tp);
+ tp++;
} while (list);
+ tp--;
```
移除冗餘的運算
`(*tp).len` --> `tp->len`
`&(*(tp->next))` --> `->next`
`&(*(tp))` --> `tp`
測試結果:
| Implementation | Elapsed time | Comparisons |
| -------------- | ------------ | ----------- |
| Linux | 184876 | 19646345 |
| shiverssort | 137262 | 14339471 |
| timsort | 152536 | 15254864 |
## 實作 shuffle 命令
> [作業要求](https://hackmd.io/@sysprog/linux2025-lab0-d#%E5%9C%A8-qtest-%E6%8F%90%E4%BE%9B%E6%96%B0%E7%9A%84%E5%91%BD%E4%BB%A4-shuffle)
> Commit: [7023f89](https://github.com/sysprog21/lab0-c/commit/7023f8918e7f876caf681f0c737709fb40e5a570)
### 實作 Fisher-Yates shuffle Algorithm
從鏈結串列的尾端開始反向走訪,`pos` 表示目前尚未被抽取的最後一個節點,而 `pick` 則是從 `pos` 往前數的第 `r` 個節點,最後將 `pos` 和 `pick` 進行位置交換。
```c
static inline void swap(struct list_head *a, struct list_head *b)
{
if (a->prev != b) {
list_move(b, a->prev);
}
list_move(a, b->prev);
}
void q_shuffle(struct list_head *head)
{
if (!head || list_is_singular(head))
return;
for (int len = q_size(head); len > 1; len--) {
struct list_head *pos = head->prev;
struct list_head *pick = head->prev;
for (int r = rand() % len; r > 0; r--)
pick = pick->prev;
if (pick != pos)
swap(pos, pick);
}
}
```
因為不能變更 queue.h ,所以 commit 時將以下程式刪除,並註解掉相關部份:
```c
void q_shuffle(struct list_head *head);
```
### 統計方法驗證
用測試腳本進行測試,結果如下:
```
Expectation: 41666
Observation: {'1234': 41815, '1243': 41822, '1324': 41755, '1342': 41791, '1423': 41971, '1432': 41786, '2134': 41630, '2143': 41593, '2314': 41318, '2341': 41660, '2413': 41525, '2431': 41724, '3124': 41846, '3142': 41996, '3214': 42002, '3241': 41622, '3412': 41620, '3421': 41955, '4123': 41415, '4132': 41530, '4213': 41545, '4231': 41340, '4312': 41384, '4321': 41355}
chi square sum: 25.175106801708825
```

結果大致符合 uniform distribution。
## 亂數產生器
> [作業要求](https://hackmd.io/@sysprog/linux2025-lab0-d#%E4%BA%82%E6%95%B8)
嘗試計算 linux 內建的 PRNG /dev/random
```
$ head -c 10M /dev/random | ent
Entropy = 7.999980 bits per byte.
Optimum compression would reduce the size
of this 10485760 byte file by 0 percent.
Chi square distribution for 10485760 samples is 288.33, and randomly
would exceed this value 7.42 percent of the times.
Arithmetic mean value of data bytes is 127.4672 (127.5 = random).
Monte Carlo value for Pi is 3.142315347 (error 0.02 percent).
Serial correlation coefficient is -0.000074 (totally uncorrelated = 0.0).
```
## 研讀論文
### 〈[Dude, is my code constant time?](https://eprint.iacr.org/2016/1123.pdf)〉
> [作業要求](https://hackmd.io/@sysprog/linux2025-lab0-d#-%E8%AB%96%E6%96%87%E3%80%88Dude-is-my-code-constant-time%E3%80%89%E9%87%8D%E9%BB%9E%E6%8F%90%E7%A4%BA)
dudect 的檢測流程包含三個步驟:
1. 測量執行時間
針對不同的 輸入類別(input classes),測量其執行時間:
* 固定輸入(fixed class):輸入值固定不變。
* 隨機輸入(random class):每次測試時隨機選擇輸入值。
使用現代 CPU 提供的 cycle counter(如 x86 的 TSC 計數器或 ARM 的 systick)來測量執行時間。
2. 進行後處理
* 裁剪(cropping): 為了減少環境雜訊影響,過大的測量值可以被丟棄。
* 高階處理(higher-order preprocessing): 有時可以使用類似於 DPA 攻擊的方式進行數據處理,以提高檢測能力。
3. 進行統計測試
* Welch’s t-test(t 檢定):檢測兩組輸入的執行時間分布是否有統計學上的顯著差異。
* Kolmogorov-Smirnov(K-S 檢定):非參數檢定,適用於更廣泛的時間變異性分析。
### [Bottom-up Mergesort: A Detailed Analysis](https://link.springer.com/article/10.1007/BF01294131)
### [The cost distribution of queue-mergesort, optimal mergesorts, and power-of-two rules](https://doi.org/10.1006/jagm.1998.0986)
### [Queue-Mergesort](https://doi.org/10.1016/0020-0190(93)90088-q)
## Git 實作
### Commit & Push
```
clang-format -i *.[ch]
git commit -a
git push
```
### Config 設定
```
git config --global user.name "liangchingyun"
git config --global user.email "joanne10021@gmail.com"
```
### fetch
```
git remote add sysprog21 https://github.com/sysprog21/lab0-c.git
git fetch sysprog21
git remote remove sysprog21
```
### rebase
* `git branch` :查看本地分支
* `git branch -r` :查看遠端倉庫分支
* `git remote show origin` :查看本地與遠端的關係
* `git status` :檢查本地與遠端是否同步
* `git pull origin master` :更新本地分支(拉取遠端的最新變更)
* `git push origin master` :推送本地變更到遠端
* `git rebase -i HEAD~N`:查看最新的 N 個 Commit
* `git branch -d branch-name`:刪除本地分支
* `git branch -D branch-name`:強制刪除本地分支
* `git push origin --delete branch-name`:刪除遠端分支
* `git commit --amend --author="user-name <user-email>"`:修改最近一次 commit 的作者資訊
* `git rebase --abort`: 放棄剛才的 rebase
```
git checkout origin
git rebase sysprog21/master
// 處理未 commit 的檔案
git stash
git rebase jserv/main
git stash pop
git push branch_name --force
// 解決衝突
git add queue.c
git rebase --continue
// 更新到本地 branch
git reset --hard 7034022c
git pull origin master
git reset --hard 7034022c
這個指令 只影響本地端(local repository),不會對遠端造成任何
影響它重設你本地目前的分支(例如 master)到某個舊的 commit。
清除你本地的修改和暫存。
git pull origin master --force
```
### 建立 Pull Request
```
git remote add upstream https://github.com/sysprog21/vcam.git
git fetch upstream
git checkout upstream/main
-----------------------------
(might need to rebase to reviewer branch if you have two PR)
git checkout -b new-branch
git add .
git commit -m "Commit message"
git push origin new-branch
----------------------------
* open another new branch
git fetch jserv main
git branch -D Fix_verify.sh_typo
git checkout -b Fix_verify.sh_typo jserv/main
# (edit scripts/verify.sh)
git add scripts/verify.sh
git commit -m "Fix verify.sh typo and refine comments"
git push -u origin Fix_verify.sh_typo
git checkout dynamic_MCS
```
### squash:合併 commits
```
// 查commit歷史
git log --oneline
// 合併最新的n個commits
git rebase -i HEAD~n
將除了第一行外的 pick 改成 s
// 從某個commit
git rebase -i x3y4z5w // 想合併的最早那個commit的上一個
將要合併的的 pick 改成 s
// 修改前一次 commit
直接改檔案
git add
git commit --amend
git push origin branch_name --force
```
### edit:編輯 commit
```
// 查 commit 歷史
git log --oneline
// 修改倒數第 n 個 commit
git rebase -i HEAD~n
將要修改的 pick 改成 e
直接改檔案
git commit --amend
git rebase --continue
git push origin branch_name --force
```