1. 程式人生 > 其它 >作業系統 Lab2 kmt

作業系統 Lab2 kmt

Lab2-kmt

花了整個五一三天假期,最後是聽了答疑才知道怎麼解決棧的資料競爭的....

痛苦的部分主要是多核 logging 和怎麼用 qemu debug 的問題。搞定了這些技術上的難題,剩下就是老老實實寫程式碼了。

設計

spinlock

去觀摩了 xv6 的程式碼,發現不僅要自旋,還得關中斷。並且關中斷這事還得是巢狀的。

semaphore

每個訊號量有一個等待佇列 wait_tid,一個 value,還有一個 wakeup 表示需要喚醒多少個睡眠中的程序。

我的實現需要在 sem_signalsem_wait 中修改全域性的任務連結串列。由於 os_trap() 中途也可以 sem_signal

,所以需要保證對連結串列讀寫的互斥。同時由於 os_trap() 的第一個操作必須是儲存上下文、最後必須是切換,因此需要保證這兩個操作對程序狀態的修改是符合 save_context 後和 switch_task 前的語義的。

在除錯過程中遇到過一個印象深刻的Bug

一開始我認為只能排程 RUNNABLE 的任務,但實際上可以排程所有非 RUNNING 的任務。注意到等待訊號量進入睡眠後需要一次切換讓出 CPU,這就是一種從 SLEEP 排程的情況。

棧的資料競爭

一開始的上下文切換是通過記錄棧上的指標完成的,即每個任務記錄一個上下文指標,指向棧上由 AM 的 cte 儲存的上下文。

於是就可以觀察到,某些時候 os_trap()

會返回到空的 %rip

後面每個任務的結構體裡都單獨拷貝一份上下文,這樣就會在多核時出現經典的 triple fault。然後STFW發現可以用 -d exec 來列印 trace,用 -d cpu-reset 來列印暫存器的值。然後就可以發現每次都是一個執行緒的 %rip 跑飛了,triple fault 就恰好是三次越界指令訪問。並且可以發現每次都是在 cpu_current() 呼叫後返回到了錯誤地址,意味著棧被改寫了。

然後我去翻了聊天記錄,發現有同學問了一樣的問題,但是沒有看懂他的解決方案。於是中午去聽了答疑,知道了怎麼延遲任務T的排程來確保T的棧不會被兩個 CPU 同時操作。感覺這個想法還是很厲害的。

但是這樣做會出現新的問題:如果用smp=2跑3個任務,那麼就會出現問題。CPU[0]從 idle[0]->print,而 CPU[1] 此時無法從 idle[1] 跳到任何任務(一個正在執行,另一個由於棧切換必須等到 CPU[0] 下一次 os_trap() 才能排程,但是 yield() 的語義是讓出 CPU[1],因此會被我的 assert 抓到)

解決方案也很簡單。我開了2倍smp的 idle 任務,用於保證每個 CPU 至少可以切換到另一個 idle 上。這樣雖然不太優雅,但也還能跑起來。

tty的神祕Bug

一開始我開了 128 個 task_struct,然後在跑 dev 的時候滾鍵盤就會出現某個任務的結構體被修改了的情況。通過 assert 和斷點找到了是 tty_rendermemset 一段記憶體,然後這段記憶體恰好處在某兩個結構體中間,結果就是改寫了我的結構體資訊。

這個 Bug 比較難抓到,每7、8次才能復現一次,並且每次導致出錯的 memset 地址都是一樣的(非常整齊,恰好是頁面的整數倍)。一開始我以為是 pmm.c 的問題,分配的記憶體和裝置地址重疊了。但我列印之後發現並不是這樣。而且更神奇的是,我把 task_struct 的數量減少到 64 之後,這個 Bug 就再也沒法觸發了。。。