操作系統機制之受限直接執行

受限直接執行(limited direct execution)是操作系統的關鍵底層機制之一,其目的就是讓用戶想運行的程序在CPU上運行之前,首先確保設置好硬件,以便在沒有操作系統幫助的情況下限制進程可以執行的操作。

 

直接執行

操作系統會以時間片輪轉的方式讓多個進程共享CPU,來實現虛擬化。但是,在執行這個機制時存在一些問題。第一個是性能:如何在不增加系統開銷的情況下實現進程間的切換?第二個是如何在運行其他進程的同時保留自身對CPU的控制權?控制權對於操作系統尤爲重要,因爲操作系統要負責資源的管理,如果沒有控制權,那麼也就沒辦法按照預想的策略控制多個進程,只需要一個進程可以簡單地無限制運行並接管CPU。因此,在保持控制權的同時獲得高性能,這是構建操作系統的主要挑戰之一。

在這個挑戰的前提下,受限直接執行(limited direct execution)就產生了。這個概念中的“直接執行”部分很簡單:就是字面上的進程直接執行的意思。也就是當操作系統希望啓動程序時,其會在進程列表中創建一個進程條目,爲進程分配一些資源,將程序代碼從磁盤加載到內存中,找到入口點(main()函數或類似的),跳轉到那裏,並開始運行用戶的代碼。

 

但是,單純的直接執行程序會產生一些問題。一:如果只運行一個程序,該如何能確保程序不做任何操作系統不希望它做的事,例如不要訪問到分配給它的以外的內存,並且同時仍然高效地運行它?二:當操作系統分配給一個進程的時間片耗光時,操作系統該如何將其中斷並切換到另一個進程?

這兩個問題的解決方案就是讓進程直接運行時受限。

 

受限制的操作

操作系統要允許一個進程能夠執行I/O和其他一些應該受限制的操作,但又不能讓這個進程跳過操作系統可以完全的控制CPU。因此,開發者們爲操作系統引入了一種新的處理器模式,稱爲用戶模式(user mode)。在用戶模式下運行的代碼會受到限制。例如,在用戶模式下運行時,進程不能執行某些受限制的指令,否則會導致處理器引發異常,操作系統可能會終止該進程。與用戶模式相對應的是內核模式(kernel mode),在此模式下,運行的代碼可以做它喜歡的事,包括一些特權操作,操作系統(或內核)就以這種模式運行。
這其中的問題是如果某一時刻進程希望執行某種被限制的操作(如從磁盤讀取),該如何實現?爲了實現這一點,幾乎所有的現代硬件都提供了用戶程序執行系統調用的能力。要執行系統調用,程序必須執行特殊的指令,可以成爲陷阱(trap)指令。該指令將跳入內核並將特權級別提升到內核模式,之後系統就可以執行任何需要的特權操作(如果允許),從而爲調用進程執行所需的工作。完成後,操作系統調用一個特殊的從陷阱返回(return-from-trap)指令,該指令返回到發起調用的用戶程序中,同時將特權級別降低,回到用戶模式。

硬件通過提供不同的執行模式來協助操作系統。在用戶模式下,應用程序不能完全訪問硬件資源。在內核模式下,操作系統可以訪問機器的全部資源。還提供了陷入內核和從陷阱返回到用戶模式程序的特別說明,以及一些指令,讓操作系統告訴硬件陷阱表(trap table)在內存中的位置。執行陷阱時,硬件需要小心,因爲它必須確保存儲足夠的調用者寄存器,以便在操作系統發出從陷阱返回指令時能夠正確返回。例如,在x86上,處理器會將程序計數器、標誌和其他一些寄存器推送到每個進程的內核棧(kernel stack)上。從返回陷阱將從棧彈出這些值,並恢復執行用戶模式程序。

內核通過在啓動時設置陷阱表(trap table)來實現。當機器啓動時,它在特權(內核)模式下執行,因此可以根據需要自由配置機器硬件。操作系統做的第一件事,就是告訴硬件在發生某些異常事件時要運行哪些代碼。例如,當發生硬盤中斷,發生鍵盤中斷或程序進行系統調用時,應該運行哪些代碼?操作系統通常通過某種特殊的指令,通知硬件這些陷阱處理程序的位置。一旦硬件被通知,它就會記住這些處理程序的位置,直到下一次重新啓動機器,並且硬件知道在發生系統調用和其他異常事件時要跳轉到哪段代碼。

LDE協議有兩個階段。第一個階段(在系統引導時),內核初始化陷阱表,並且CPU記住它的位置以供隨後使用。內核通過特權指令來執行此操作(所有特權指令均以粗體突出顯示)。第二個階段(運行進程時),在使用從陷阱返回指令開始執行進程之前,內核設置了一些內容(例如,在進程列表中分配一個節點,分配內存)。這會將CPU切換到用戶模式並開始運行該進程。當進程希望發出系統調用時,它會重新陷入操作系統,然後再次通過從陷阱返回,將控制權還給進程。該進程然後完成它的工作,並從main()返回。這通常會返回到一些存根代碼,它將正確退出該程序(例如,通過調用exit()系統調用,這將陷入OS中)。此時,OS清理乾淨,任務完成了。

 

進程間切換

下一個問題是,操作系統如何重新獲得CPU的控制權,來達到切換進程的目的?

協作方式:等待系統調用

過去某些系統採用協作方式。在這種風格下,操作系統相信系統的進程會合理運行。運行時間過長的進程被假定會定期放棄CPU,以便操作系統可以決定運行其他任務。但是實際上,大多進程通過進行系統調用將CPU的控制權轉交給操作系統;他們會在進行的某些操作的後續中,調價一個顯式的調用,來講操作權交還給系統。

還有進程執行了某些非法操作時,也會將控制權交還給系統。例如進程以0爲除數,或者嘗試訪問系統分配給它的以外的內存。此時操作系統會重新獲取CPU控制權,並有可能終止進程。

因此,協作調度系統中,操作系統通過等待程序調用或程序非法行爲發生,來得到重新獲取控制權的機會。然而,如果當進程陷入無限循環不會再進行系統調用的話,那麼此時唯一的解決辦法就是——重啓計算機。

非協作方式:操作系統進行控制

對於協作方式可以看到,當進程不協作也就是不主動進行系統調用也不出錯的情況下,操作系統是無法重新獲得CPU的控制權的。那麼這時操作系統該如何保證自己的地位?答案就是通過硬件的幫助。在許多年前構建計算機系統的人們發現了時鐘中斷(timer interrupt)。時鐘設備可以編程爲每隔幾毫秒產生一次中斷。產生中斷時,當前正在運行的進程停止,操作系統中預先配置的中斷處理程序(interrupt handler)運行。此時,操作系統重新獲得CPU的控制權,就可以做它該做的事,例如停止當前進程,並啓動另一個進程。

那麼又得到了一個問題,操作系統在啓動時必須通知硬件哪些代碼在發生時鐘中斷時運行。在啓動過程中,操作系統也必須啓動時鐘,這是一項特權操作。一旦時鐘開始運行,操作系統就感到安全了,因爲控制權最終會歸還給它,因此操作系統可以自由運行用戶程序。時鐘也可以關閉(也是特權操作)。硬件在發生中斷時有一定的責任,尤其是在中斷髮生時,要爲正在運行的程序保存足夠的狀態,以便隨後從陷阱返回指令能夠正確恢復正在運行的程序。這一組操作與硬件在顯式系統調用陷入內核時的行爲非常相似,其中各種寄存器因此被保存(進入內核棧),因此從陷阱返回指令可以容易地恢復。

保存和恢復上下文

操作系統已經重新獲得CPU控制權後,必須決定是繼續運行當前正在運行的進程,還是切換到另一個進程。這個決定是由調度程序(scheduler)做出的,它是操作系統的一部分。如果決定進行切換,OS就會執行一些底層代碼,即所謂的上下文切換(context switch)。上下文切換在概念上很簡單:操作系統要做的就是爲當前正在執行的進程保存一些寄存器的值,從而確保下一次切換到當前的進程時可以繼續正確運行;當切換到下一個進程時,根據上一次切換時保存的進程寄存器有關值,來回復進程的執行指令。

爲了保存當前正在運行的進程的上下文,操作系統會執行一些底層彙編代碼,來保存通用寄存器、程序計數器,以及當前正在運行的進程的內核棧指針,然後恢復寄存器、程序計數器,並切換內核棧,供即將運行的進程使用。通過切換棧,內核在進入切換代碼調用時,是一個進程(被中斷的進程)的上下文,在返回時,是另一進程(即將執行的進程)的上下文。當操作系統最終執行從陷阱返回指令時,即將執行的進程變成了當前運行的進程。在這個過程中,有兩種類型的寄存器保存/恢復。第一種是發生時鐘中斷的時候,此時運行進程的用戶寄存器由硬件隱式保存,使用該進程的內核棧。第二種是當操作系統決定從A切換到B,內核寄存器將被操作系統明確地保存,但這次被存儲在該進程的進程結構的內存中。後一個操作讓系統從好像剛剛由A陷入內核,變成好像剛剛由B陷入內核。

當然,時鐘中斷期間是禁止中斷的(disable interrupt),這樣做可以確保在處理一箇中斷時,不會將其他中斷交給CPU,當然,操作系統必須及其小心的這樣做,因爲禁用中斷時間過長或過短都可能造成問題。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章