contributed by < liangchingyun
>
Urbaner3
根據作業 review,檢查事項 1, 5, 8 嘗試聚焦並整理程式碼,並加強改進。
6.48 Alternate Keywords 此段落,注意表達的因果關係,我不是很容易直接解釋或是想到他與什麼有關聯,請舉例,並以此為例,再檢查作業一二文章表達通順。
鼓勵根據作業要求、能力,挑戰論文閱讀,設計實驗。為了專題做預備。如果排程器、中斷、線程等名詞有不理解,隨時提出問題,向講師、同學、討論區發問,養成習慣作筆記,保持進步,一定會成功的。
list_for_each
: 走訪雙向鏈結串列中的每個節點
一開始並沒有初始化list_head
,這導致分配的記憶體中list_head
結構的成員未被設置為預期的初始值,顯示錯誤:
Segmentation fault occurred. You dereferenced a NULL or invalid pointerAborted (core dumped)
修改後正確初始化list_head
q_insert_head
: 將一個新元素插入到佇列的首部。
q_insert_tail
: 將一個新元素插入到佇列的尾部。
q_insert_tail
即是將 list_add
改為 list_add_tail
strdup
: 分配記憶體並將字符串複製到該記憶體中
一開始使用 list_for_each_entry_safe
巨集指令,在commit時遇到問題:
因此我去查標頭檔中對list_for_each_entry_safe
的定義:
根據 3.4 Options Controlling C Dialect,使用 -ansi
會關閉某些 GCC 特性,因此會禁用 typeof
關鍵字。在 6.48 Alternate Keywords 中提到使用替代關鍵字(如 __asm__
、__extension__
、__inline__
和 __typeof__
)即可在啟用 -ansi
時使用。然而,若修改標頭檔會出現錯誤訊息,因此改成不使用巨集指令的寫法:
此函式完成後 q_new
, q_insert_head
和 q_insert_tail
均得以通過 make test
struct list_head
: 鏈結串列的節點
element_t
: 是包含鏈結節點的結構體
list_entry
: 一個巨集,用來從鏈結節點指標(pos)回推「擁有這個節點的結構體」
q_romove_head
: 將佇列的首部的元素移除。
q_romove_tail
: 將佇列的尾部的元素移除。
strncpy
函數已經將 first_entry->value
中的內容複製到 sp
中,因此不需要將 sp
重新賦值。
first_entry->list
: first_entry 節點中用來鏈接到其他節點的鏈結串列指針。
然而,此作法無法通過 trace-17-complexity。因為這個函數沒有處理 first_entry->value
可能為 NULL
的情況。如果 value
為 NULL
,在使用 strncpy
時可能會導致未定義行為。
修改後即可通過 trace-17-complexity。
q_remove_tail
即是將 first_entry
改為 last_entry
。
原本的程式碼:
因為 q_swap
是 q_reverseK
在 K=2
時的特例,因此改為以下程式碼:
將節點從鏈結串列中刪除後,需要手動釋放這些節點所佔用的記憶體,因此做了以下修正:
參考 2095. Delete the Middle Node of a Linked List 的其中一個解法
使用
strcmp
比較兩個節點的value
是否相同
參考同學寫法,使用 MergeTwoLists
descend ^ (strcmp(e1->value, e2->value) < 0)
定義成 bool condition
,精簡了 node
的指派邏輯。fast
和 slow
指標的初始化方式,並將 for
循環改為 while
進行快慢指標的移動。e1
及 e2
的值不會遭變更,因此加上 const
簡化了走訪邏輯,直接用 pos
來控制位置,讓程式看起來更直觀。
q_descend
即是將 strcmp(right->value, left->value) > 0
改成 strcmp(right->value, left->value) < 0
原本 queue_to_merge
是在迴圈外部宣告,但這個變數只在迴圈中使用,把它移進迴圈內。
執行 $ make valgrind
後,得到以下訊息:
結果顯示程式通過所有測試,也沒有發生 Memory Leak 、 Invalid Memory Access 等問題。
這段程式碼涉及到三個核心函數:
merge()
- 合併兩個已排序的單向鏈結串列,並返回排序後的鏈結串列merge_final()
- 進行最終合併並恢復雙向鏈結串列list_sort()
- 主排序函數,負責將鏈結串列拆分並合併排序它使用空結尾的單向鏈結串列來合併子串列,最後再還原為雙向鏈結串列,提高效能。時間複雜度為 O(n log n),適合排序大型鏈結串列。
在 list_sort()
中,排序過程透過 Bottom-Up 的方式進行合併:
[4] [2] [5] [3] [1]
[4]
+ [2]
→ [2,4]
[5]
+ [3]
→ [3,5]
[1]
(單獨保留,因為目前沒有另一個可合併的區塊)[2,4]
+ [3,5]
→ [2,3,4,5]
[1]
保留[2,3,4,5]
+ [1]
→ [1,2,3,4,5]
list_sort.c
加入 lab0-c
專案Commit: 0c1203d
將 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
。
我執行 ./qtest
並使用以下的命令來分析排序效能:
執行結果如下:
q_sort | q_ksort |
---|---|
0.111 | 0.089 |
因為不能變更 queue.h ,所以 commit 時將以下程式刪除,並註解掉相關部份:
參考資料:Linux 核心專題: 改進 lib/list_sort.c、Timsort 研究與對 Linux 核心貢獻嘗試、最貼近現實的排序演算法- Timsort、linux23q1-timsort
正確處理空指針的情況:
縮小變數作用範圍:
將沒有被修改的變數宣告為 const
指標
將 head
初始化為 NULL
刪掉沒被使用的變數
指標 tp
指向 stk - 1
導致未定義行為的修正
移除冗餘的運算
(*tp).len
–> tp->len
&(*(tp->next))
–> ->next
&(*(tp))
–> tp
測試結果:
Implementation | Elapsed time | Comparisons |
---|---|---|
Linux | 184876 | 19646345 |
shiverssort | 137262 | 14339471 |
timsort | 152536 | 15254864 |
從鏈結串列的尾端開始反向走訪,pos
表示目前尚未被抽取的最後一個節點,而 pick
則是從 pos
往前數的第 r
個節點,最後將 pos
和 pick
進行位置交換。
因為不能變更 queue.h ,所以 commit 時將以下程式刪除,並註解掉相關部份:
用測試腳本進行測試,結果如下:
結果大致符合 uniform distribution。
嘗試計算 linux 內建的 PRNG /dev/random
dudect 的檢測流程包含三個步驟:
git branch
:查看本地分支git branch -r
:查看遠端倉庫分支git remote show origin
:查看本地與遠端的關係git status
:檢查本地與遠端是否同步git pull origin master
:更新本地分支(拉取遠端的最新變更)git push origin master
:推送本地變更到遠端git commit --amend
:修改最後一次 Commit 的訊息git rebase -i HEAD~N
:查看最新的 N 個 Commitgit 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