寫在前面的話
如果您對該系列感興趣的話,推薦您先看一下南京大學的計算機組成原理實驗(也就是PA)的講義,然後再來看這篇文章可能有更多地收穫。如果您是要完成該作業的學生,我推薦你先看講義,或者好好聽老師的講課,然後自己獨立完成這個作業,但是如果你沒有聽懂,或者你無論如何也無法理解講義上面的字,又或者說對講義上面的某點知識某個問題不瞭解而又覺得太簡單不好意思問老師,那麼您可能會從這篇文章裏面獲得一些你需要的信息。本篇文章將會包括筆者自己做PA的所有經過,希望你並不將該文章當成抄襲的根源,而是成爲你思考的源泉。
現在已經到了PA4的階段,經過了PA0的搭建環境、PA1簡單的功能函數實現、PA2的機器指令的模擬實現,PA3運行仙劍奇俠傳的運行,我們需要把NEMU改造地更加像一個真實的操作系統。
我們已經實現了所有的PA3任務,而PA4要做的事情更加底層,有很多內核、操作系統的知識,需要很好地瞭解之後再進行實現。
PA系列傳送門
PA0:https://blog.csdn.net/qq_41983842/article/details/88921427
PA1.1:https://blog.csdn.net/qq_41983842/article/details/88934779
PA1.2:https://blog.csdn.net/qq_41983842/article/details/89714479
PA1.3:https://blog.csdn.net/qq_41983842/article/details/89714689
PA2.1:https://blog.csdn.net/qq_41983842/article/details/95232055
PA2.2&2.3:https://blog.csdn.net/qq_41983842/article/details/101164495
PA3.1:https://blog.csdn.net/qq_41983842/article/details/103094859
PA3.2:https://blog.csdn.net/qq_41983842/article/details/103843093
PA4:https://blog.csdn.net/qq_41983842/article/details/104667951
目錄
思考題
-
如果發生了中斷嵌套, 將會發生什麼樣的災難性後果? 這一災難性的後果將會以什麼樣的形式表現出來?
如果選擇把現場信息保存在一個固定的地方, 發生中斷嵌套的時候, 第一次中斷保存的現場信息將會被優先級高的中斷處理過程所覆蓋,那麼如果現場信息被保存在
0x1000
這個地址處,trap frame的信息就會被覆蓋,那麼等到嵌套的中斷結束的時候,中斷現場信息已經沒有了,所以程序就一直執行這個中斷處理過程,會造成死機,由於是硬件中斷,導致沒有任何除了拔電源的方式來阻止這個中斷處理過程結束。 -
解釋分頁機制和硬件中斷是如何支撐仙劍奇俠傳和 hello 程序在我們的計算機系統(Nanos-lite, AM, NEMU)中分時運行的
首先分頁機制保證程序在虛擬空間上面運行,hello和仙劍的虛擬空間地址不一樣,所以他們不會相互衝突,這是能一起運行他們兩個的基礎,不然如果他們兩個地址有重合的地方運行其中一個肯定會把另外一個覆蓋的。有了這個保證,我們就可以通過硬件中斷來實現兩個程序分時運行。一開始現在仙劍的虛擬空間上面跑,然後跑着跑着,遇到了這個時鐘中斷指令,這個時候把現場保存下來,同時初始化好陷阱幀,程序跳轉到hello的虛擬地址空間來執行,之後又遇到中斷,繼續保存現場,然後構造仙劍的陷阱幀,CPU響應到了這個中斷信號,就通過
_EVENT_IRQ_TIME
這個事件來跳轉到schedule
函數中切換進程。總的來數就是一個保存現場然後不斷進程切換的過程。
實驗內容
實現內核自陷
首先要實現_umake()
函數,他的任務就是在 ustack
的底部初始化一個以 entry
爲返回地址的陷阱幀,也就是在棧上初始化如下內容, 然後返回陷阱幀的指針,並且要在陷阱幀之前設置好 _start()
函數的棧幀
| |
+---------------+ <---- ustack.end
| stack frame |
| of _start() |
+---------------+
| |
| trap frame |
| |
+---------------+ <--+
| | |
| | |
| | |
| | |
+---------------+ |
| tf | ---+
+---------------+ <---- ustack.start
| |
有了上面這個圖,思路也變得清晰了,首先要把stack frame of _start()
中參數全部設爲0或者NULL
,然後就來到了trap frame
的位置,開始初始化陷阱幀,就跟PA3.1的順序一樣來初始化他們就可以了。陷阱幀裏面每個元素都是int
大小,也就是4個字節,所以4個字節4個字節減少指針或者把他們放在數組裏通過下標的變化來分別對應每個元素給他們賦初值都行。這裏我採用指針的方式。
然後要修改Nanos-lite
的代碼,加入_trap();
,並且在proc.c
裏面做如下更改:
然後修改asye.c
文件中的irq_handle
函數加入0x81
也就是自陷的case:
接着在_asye_init
函數中根據system call來添加system trap
中斷門的情況,查看x86.h
中type參數有三種TG
,IG32
,TG32
,顯然這裏是IG32
,而entry就是我們下面要寫的函數,我聲明爲void systrap();
之後就要來到trap.s
中來添加systrap
這種entry
情況。模仿前面的vecnull
和vecsys
,可以很輕鬆寫出來這個操作。
在_trap
中,模仿框架中寫的,這裏要顯示中斷指令
這樣我們的操作系統就可以接收到一個 _EVENT_TRAP
事件,現在需要在Nanos-lite
接收到 _EVENT_TRAP
之後可以輸出一句話, 然後直接返回即可。需要在irq.c
中添加相關case
:
這時候我們再運行一下dummy
,就會發現這樣的情況:
實現上下文切換
這個任務主要是完成schedule()
函數來返回將要調度的進程的上下文。講義上面已經給出了基本的框架,通過 current
來決定接下來要調度哪一個進程,並且把當前進程的上下文信息的位置保存在 PCB 當中。新地址空間放在PCB的as中,而新的上下文放在tf指向的地方。
之後要修改剛纔改過的irp.c
中的case,不讓他輸出話了,而是返回這個函數的返回值。爲了等會運行可以看出來是走的這個case,還是暫時輸出一下把。
要在common.h
中聲明schedule
函數
_RegSet* schedule(_RegSet *prev);
最後需要在trap.s
裏面對asm_trap
進行相應的修改,irq_handle()
返回後,先將棧頂指針切換到新進程的陷阱幀, 然後才根據陷阱幀的內容恢復現場。把 addl $4, %esp
去除掉,把esp
換到eax
上面,就完成了切換
成功跑起來仙劍
分時運行仙劍奇俠傳和hello程序
添加第二個用戶程序
修改調度的代碼, 讓 schedule()
輪流返回仙劍奇俠傳和 hello 的現場
然後修改 do_event()
的代碼, 在處理完系統調用之後, 調用 schedule()
函數並返回其現場,也就是嵌套調用,先調用do_syscall
然後用do_syscall
的返回值來賦給schedule
進行進程調換。要注意do_syscall
的返回值不能再是NULL了,不然怎麼對NULL進行進程切換呢?
這分時運行可是把我的仙劍給卡炸了,幾秒鐘刷新一個畫面
優先級調度
爲了解決剛纔哪個問題,就要在schedule
函數裏面下功夫了,之前是每次調用這個函數,都會在hello和仙劍之間切換一次,而這次想做到每調用200次仙劍,切換一次hello輸出一下,這樣我們的仙劍就跑的快了。解決方法就是加一個count變量來記錄進程切換的次數,每調用一次schedule
計數器加一,當加滿200的時候,跳到hello執行一下,然後把計數器清零,重新開始計時。
這下子我的仙劍跑起來相比剛纔就流暢太多了。
添加時鐘中斷
這裏要實現一個類似於硬件中斷的機制。首先在 CPU 結構體中添加一個 bool
成員 INTR
在 dev_raise_intr()
中將INTR
引腳設置爲高電平.
在 exec_wrapper()
的末尾添加輪詢 INTR
引腳的代碼, 每次執行完一條指令就查看是否有硬件中斷到來
修改 raise_intr()
中的代碼, 在保存 EFLAGS
寄存器後, 將其 IF
位置爲 0
, 讓處理器進入關中斷狀態.
在 asye.c
中添加時鐘中斷的支持, 將時鐘中斷打包成 _EVENT_IRQ_TIME
事件。和之前的trap事件一樣。經過百度查找到x86
的時鐘中斷處理程序是int32 -- (int 0x20)
中斷頻率被設置爲100Hz(include/linux/sched.h,5)
,這樣就可以在irq_handle()
函數裏面添加時鐘中斷的event:
然後在下面的_asye_init
中添加時鐘中斷idt
索引,在這裏entry聲明爲systime
,其他的跟之前的idt
沒有任何區別。
接下來要做的就是在trap.s
中來寫這個systime
函數,跟前面的三個函數基本上一模一樣,區別的地方就在於irq
的id
是0x20
這樣就能打包成爲_EVENT_IRQ_TIME
事件。接下來要在do_event
函數中識別出來這個事件,添加一個case,直接調用 schedule()
進行進程調度,輸出一句話證明是時鐘中斷,並且去掉系統調用之後調用的 schedule()
代碼
修改 _umake()
的代碼, 在構造現場的時候, 設置正確的EFLAGS
。這裏所說的正確的eflags
其實指的就是IF
位,之前我們在保存eflags
寄存器的時候設置了IF
位爲0,就是關中斷,此時即使 INTR
引腳爲高電平, CPU 也不會響應中斷.所以我們在構造現場的時候要把IF
位設爲1,開中斷狀態,這樣CPU就會處理我們的時鐘中斷信號了。
跑一下仙劍,可以看出觸發了時鐘中斷事件
走過的一些彎路
-
在做4.1實現上下文切換的時候,找不到
asm_trap
在哪個地方認識不夠全面,始終以爲
asm_trap
是一個現成的c語言函數,在asye.c
文件裏面怎麼也找不到,後來仔細思考了一下,看到了自己在trap.s
中定義systrap
的代碼,然後往下一看就發現了asm_trap
這個關鍵詞,PA3的時候根本不知道trap.s
是幹啥的,也看不懂,現在要修改這個文件,總算知道一點東西了。比對講義,也明白了asm_trap
的功能,對之前的pusha
和popa
也有了更進一步的理解。 -
在實現上下文切換之後,更改了
schedule
函數之後,在運行仙劍的時候仍然無法切換到hello程序,看不到輸出沒有在
do_event
中更改syscall
的這種case,導致一直都在系統調用,但是沒有根據每次的系統調用來傳遞到進程切換這個函數,所以最終得等到_EVENT_TRAP
這種case的時候纔會調用schedule
函數,這就導致了他只會進行一次進程切換,更改之後要在每次系統調用的時候都要傳遞參數r
到schedule
函數中。
PA4到此就基本上結束了,而整個PA系列也要告一段落了,非常感謝大家的閱讀,願不久之後再相見!