Contributed by < dange0
>
sysprog2018
兩個 commit message 都沒有具體說明做了哪些修改,請修正
課程助教已修正
Dange0
:=
會立即展開的特性,GIT_HOOKS
會被馬上賦予值 .git/hooks/applied
.git/hooks/applied
,因此會去檢查 .git/hooks/applied
是否存在在 install-git-hook 裡,透過第14行的 touch
產生 applied,因此使用者在 make 時,$(GIT_HOOKS):
只會被執行一次。
為了加速 q_insert_tail
與 q_size
的速度,在 struct 中額外新增了 *tail
與 size
,在每次的新增或刪減就立即的更新相關數值。
在實做 queue.c 時,主要遇到了 3 跟問題:
q_insert_head
中複製字串不能只是用 strcpy 將字串複製到目標的位置,必須要先分配一塊記憶體空間給他,因此 strdup
正是符合這樣的條件,他會先 malloc 一塊空間並放上欲複製的字串,再將其記憶體位置回傳。其看似美好的程式碼如下:
測試結果如下:
發現系統在嘗試 free
時產生錯誤。初步認為是 strdup
出問題,因此決定使用 strcpy
與 malloc
取代之。
測試結果如下:
推論成立!不過 strdup
到底哪裡錯了呢?在觀看完 glibc 中的 strdup 實做方式後,發現與自己手刻基本上沒有差異。其 glibc/strdup.c
相關程式碼如下:
經過一番搜索,發現於 harness.h
中有定義 malloc
與 free
harness.h
harness.c 中的 test_malloc
這邊明確的展示了 test_malloc
的行為,於第 23 行:
使用者要求 malloc 的空間為 size
,然而他會 malloc 一塊更大的空間,包含了 block_ele_t
與 size_t
,並且回傳指標型態為 block_ele_t
block_ele_t 是一個 double linked-list 的型態,將 malloc 出去的記憶體做集中的管理
使用者實際要求的空間,會被存在 block_ele_t
中的 payload,並於第 23 行將其地址傳給 *p
最後在 function return 時,將 p 回傳。
因為這樣的實做方式,導致回傳的 p 並非指到 heap 的開始位置,在直接呼叫 free 時就會出錯,必須使用 test_free
才能正確的還原 heap 開始的位置,所以不行直接使用 strdup
。
在實做 q_remove_head 時,有要求需當 *sp 是 non-NULL 時,需要回傳 buffer size - 1 的值。
執行結果:
判斷為忘記加 null terminator,因此修改第 18 行如下:
執行結果:
在這個版本中,使用了兩個指標一個指向頭 ptr_head
,一個指向尾 ptr_tail
,將這兩個指標所指到的 struct 內部的 value 互換,然而這是一個 single linked-list 的架構,ptr_tail
必須透過 q_size
與 ptr_head
去做推算。這樣的架構就必須要兩個 for 迴圈,一個 for 迴圈是作為 ptr_head
走到 q_size/2 的位置,另一個迴圈做的是將 ptr_tail 指到對應的位置。
這樣的執行結果雖然正確,但是數量大的時候,效能會大大降低,因此這邊就開始做架構整體上的修改。
複雜度:O()
為了解決上一個版本複雜度過高的問題,這個版本將會把複雜度降為O(n),使用的方法為只走訪一次 linked-list,並且將每個 list 的 next 指向前一個 list。為了避免更動 next 值導致 linked-list 斷掉,因此使用了 3 個 pointer: ptr_pre, ptr_now, ptr_next,去做紀錄。
每輪都會將 ptr_now->next 指向 ptr_ptr,並且再將 ptr_pre, ptr_now, ptr_next 依序往下輪替。
實驗結果:
在評分系統中,主要需要注意的有兩個部分
首先可以先了解評分機制的執行流程與檔案間的關係。
從 makefile 中可以看到,在執行$ make test
時,scripts/driver.py
會被執行,在 driver.py
中,他引入了 trace-01-ops, trace-02-ops 等等 script 作為測試之輸入
trace-01-ops.cmd
在 trace-01-ops.cmd 中看到的指令就是在這項測驗中,系統會嘗試輸入這些指令,並觀察程式行為是否符合預期。因此在讀入這些指令之後,script.py 會將這些指令當作是 qtest 的參數傳入並執行 qtest 已開始測試。
在評分機制中, exception handling 非常的重要,因為使用者所撰寫的程式並不能保證不會有利外發生,如果每次例外發生時,程式都會崩潰,這樣這個評分機制就沒辦法為這些例外狀況做評分了。因此評分機制必須要確定例外發生時,程式仍然能正常運行,並且給予例外狀況扣分。
首先,最早看到 exception handling 相關程式碼是在 qtest.c 中。qtest 在初始時,會先設定好其 signal handler。
qtest.c 中的 signal handler
接著再來看看實際 signal handler 裡頭在做什麼,觀察 exception_setup
與 trigger_exception
harness.c
sigsetjmp
將 stack context 處存於變數 env 中。alarm
來決定 timeout 的時間。siglongjmp
將 stack 的狀態還原到 exception 發生前,以避免程式崩潰。由此可知,sigsetjmp
與 siglongjmp
是用來避免例外處理造成程式崩潰的函式,因此會使用 exception_setup
與 trigger_exception
的部分就是在一些有可能會出現例外的地方,也就是我們所撰寫的函式。以下以 do_free 為例:
qtest.c
qtest.c 中的 do_free 會在第 14 行呼叫我們所撰寫的 q_free,而系統要避免使用者 free 時造成例外發生,因此透過 exception_setup
與 exception_cancel
處理例外發生。當例外情況發生時,就會觸發上面所說的 sigalXXhandler ,再透過 trigger_exception
還原例外情況。
heap 狀態檢查機制也是評分系統中重要的一部分,因為評分系統必須要知道 heap 的狀態,才能知道使用者所撰寫的程式碼操作 heap 的行為是否正確。
其相關機制於 strdup 無法使用 章節討論過了。
評分系統在 malloc 與 free 這兩個函式上面動了手腳,將他們替換為 test_malloc
與 test_free
,他們利用一個 double linked-list 將 malloc 出去的 chunk 串起來,並且只將 chunk 中部分的空間作為原本使用者請求的空間返回給使用者。這樣的作法可以馬上掌握 heap 的狀態。
實際操作情況以上面的 do_free 為例,在第 20 行的地方,他執行了 allocation_check 去檢查 heap 是否有確實清空。
harness.c
allocation_check 其實就只是回傳 allocated_count 的值作為檢查的依據,而 allocated_count 在每次的 test_malloc 與 test_free 就會對該值做修改,以此實現 heap 狀態檢查機制。