Try   HackMD

2023q1 Homework1 (lab0)

contributed by < ShamrockLee >

queue.c 函式實作

實作細節

Linux 核心的 list API 使用帶有起始節點( list head )的環狀雙向鍊結串列。 節點以結構 struct list_head 表示,該結構有兩個成員 prevnext ,分別存放指向串列中前一個與後一個節點的指標。

每個節點所攜帶的資料,以巨集 list_entry 存取。 list_entrycontainer_of ,可用來存取帶有該節點變數(非指標)的結構的位址,藉以存取該結構中帶有的資料。 前述結構在 queue.h 當中有 element_tqueue_contex_t

list.h 當中大部分函式輸入參數被設計來傳入起始節點指標(能看到以 head 命名的輸入參數多次出現)。 然而,那些函式也能用在串列中其他節點上,以插入、讀取或移除該節點兩側的節點。 善用這種操作能在更多地方重用 list.h 所定義的函式,減少手動賦值 prevnext 帶來的的維護負擔。

以下是個別函式實作值得注意的部份:

  • q_free

    • 在釋放記憶體空間時,由於節點是 element_t 的成員,會跟著 element_t 一起釋放;若再以 free 釋放節點,就會出錯。
  • q_delete_mid

    • 由於 q_sort 的合併排序實作也會需要知道中間節點的位址,故將計算中間節點位址的部份另外獨立為函式 q_get_mid
    • q_get_mid 以起始節點的後方節點為起點,透過快慢指標求得中間節點的位置。
  • q_delete_dup

    • 這個函數仍保留較多的手動賦值操作,且程式碼較為冗長,或許還能簡化。
  • q_reverse

    • 這個函數以交換 prevnext 實作。
    • 會被 q_reverseKq_swap 呼叫。
  • q_reverseK

    • 函式中,宣告了兩個 struct list_head 變數 temp_head_reversertemp_head_result 作為兩個「暫時串列」的起始節點;前者用以傳入 q_reverse ,後者儲存反轉後的節點。
  • q_swap

    • k = 2 的 q_reverseK 實現
    • 不確定會不會比直接賦值 prev 、 next 效能差。
  • q_merge

    • 排序部份獨立為函數 q_merge2 ,以便 q_sort 合併排序時重用。
    • 直接操作 q_contex_tsize 以減少時間複雜度。
  • q_sort

    • 實作為合併排序。
    • 宣告struct list_head temp_head 使用 q_get_mid 尋找中間節點,並以 list_cut_position 把前半部切給 &temp_head
    • 兩個鍊結串列分別傳入 q_sort 排序,再使用 q_merge2 合併。

過程概要

實作初期,只看到了 list API 所使用的串列結構,並從註解發現大部分函式都是對起始節點的操作,未能理解 list_movelist_splicelist_cut_position 適用的情境,因此實作 q_swapq_reverseKq_sort 時幾乎都是手動指定 prevnext 這造成程式碼愈來愈冗長、難以除錯,以至於花費許多時間而缺乏成效,最後卡在合併串列(merge)的部份。又因為怕被發現進度落後,遲遲未上傳程式碼並接受檢核。

在一對一對談後,以「儘可能重用 List API 提供的函式」與「儘可能重用已定義的函式」為目標最佳化。 仔細閱讀 list.h 的函式,花了三小時內實作、重構了所有函式,五小時除錯,達到記憶體使用以外的行為符合期待的結果。 這顯示及早接受檢核的重要性與重用 API 提供的程式碼帶來的效果。