1. 程式人生 > >Linux系統的中斷、系統呼叫和排程概述

Linux系統的中斷、系統呼叫和排程概述

最近學習Linux作業系統,關於中斷系統呼叫和程序的級別總是感覺有些模糊的地方,特在此做個小結,整理下思路。

所謂的中斷就是在計算機執行程式的過程中,由於出現了某些特殊事情,使得CPU暫停對程式的執行,轉而去執行處理這一事件的程式。等這些特殊事情處理完之後再回去執行之前的程式。中斷一般分為三類:1、由計算機硬體異常或故障引起的中斷,稱為內部異常中斷;2、由程式中執行了引起中斷的指令而造成的中斷,稱為軟中斷(這也是和我們將要說明的系統呼叫相關的中斷);3、由外部裝置請求引起的中斷,稱為外部中斷。簡單來說,對中斷的理解就是對一些特殊事情的處理。

與中斷緊密相連的一個概念就是中斷處理程式了。當中斷髮生的時候,系統需要去對中斷進行處理,對這些中斷的處理是由作業系統核心中的特定函式進行的,這些處理中斷的特定的函式就是我們所說的中斷處理程式了。

另一個與中斷緊密相連的概念就是中斷的優先順序。中斷的優先順序說明的是當一箇中斷正在被處理的時候,處理器能接受的中斷的級別。中斷的優先順序也表明了中斷需要被處理的緊急程度。每個中斷都有一個對應的優先順序,當處理器在處理某一中斷的時候,只有比這個中斷優先順序高的中斷可以被處理器接受並且被處理。優先順序比這個當前正在被處理的中斷優先順序要低的中斷將會被忽略。

典型的中斷級如下所示

發生軟體中斷時,其他所有的中斷都可能發生並被處理;但當發生磁碟中斷時,就只有時鐘中斷和機器錯誤中斷能被處理了。

在講系統呼叫之前,先說下程序的執行在系統上的兩個級別:使用者級和核心級,也稱為使用者態和系統態(user mode  and kernel mode)。程式的執行一般是在使用者態下執行的,但當程式需要使用作業系統提供的服務時,比如說開啟某一裝置、建立檔案、讀寫檔案等,就需要向作業系統發出呼叫服務的請求,這就是系統呼叫。Linux系統有專門的函式庫來提供這些請求作業系統服務的入口,這個函式庫中包含了作業系統說提供的對外服務的介面。當程序發出系統呼叫之後,它所處的執行狀態就會由使用者態變成核心態。但這個時候,程序本身其實並沒有做什麼事情,這個時候是由核心在做相應的操作,去完成程序所提出的這些請求。

系統呼叫和中斷的關係就在於,當程序發出系統呼叫申請的時候,會產生一個軟體中斷。產生這個軟體中斷以後,系統會去對這個軟中斷進行處理,這個時候程序就處於核心態了。

那麼使用者態和核心態之間的區別是什麼呢?(以下區別摘至《UNIX作業系統設計》)

1、使用者態的程序能存取它們自己的指令和資料,但不能存取核心指令和資料(或其他程序的指令和資料)。然而,核心態下的程序能夠存取核心和使用者地址

2、某些機器指令是特權指令,在使用者態下執行特權指令會引起錯誤

對此要理解的一個是,在系統中核心並不是作為一個與使用者程序平行的估計的程序的集合,核心是為使用者程序執行的。

如何去理解上面所說的區別,特別是區別1呢?真正理解這點,需要對程序在記憶體中的表示有個大致的理解。

每個程式需要被裝入(全部或部分)裝入記憶體後才能開始執行,在32位機器中,每個程序都有4G的虛擬地址空間。但是對這4G的空間並不是全部都可以被程序使用的,在Linux中,對這4G的空間的大致劃分如下


因此,從原則上來說,在程序中使用者能使用的虛擬地址空間是隻有3G的。正常情況下,使用者程式在使用者態執行的時候,只能訪問在這3G範圍內的地址(當然也不是所有地址都能訪問)。當程序由於系統呼叫而進入核心態時,這時系統核心會執行相應的操作,它能訪問自己1G的地址空間,同時也能訪問3G的使用者地址空間。而使用者程序的指令的資料是存在與那3G的地址空間中的。當然還有程序的棧等部分。

細分來說的話,一個程序在記憶體中一般包含以下部分,資料段、指令段、執行時堆、棧等。示意圖如下

核心自己的棧這在最上層的1G的地址空間內,這是隻能由核心訪問的部分。

在說程序排程之前,首先整理下程序可能的各種狀態。首先我們知道程序的執行狀態是分為核心態和使用者態。其他的程序狀態包括如下

程序未被執行,但出於就緒狀態,只要核心排程它,即可執行

程序正在睡眠中並且存在主存中

程序處於就緒狀態,但處在二級儲存器中

程序在睡眠中,且在二級儲存器中

程序執行完核心態的操作,正要返回使用者態時,核心做了程序排程,切換了上下文

程序剛被建立

程序呼叫了exit,處於僵死狀態

程序之間的狀態轉換示意圖如下,箭頭指明瞭轉換的方向


程序的各種狀態的轉換比較複雜,這裡主要關注程序的兩個執行狀態、睡眠以及從就緒到執行的轉換

從示意圖中可以看出,當發生中斷或者系統呼叫時,程序由使用者態轉成核心態。但程序在核心態執行時,是不能被搶佔的,因此只有當程序由核心態進入睡眠狀態時,或者程序處於使用者態時,核心才允許做程序排程,切換上下文。而處於就緒狀態的程序當被核心排程開始執行的時候,首先是進入核心態的。

那麼為什麼,程序在核心態執行的時候,不允許做程序排程呢?因為在核心態執行的時候,需要訪問一些全域性核心資料結構中的資訊。如果執行在核心態時的程序排程,那麼會改變核心執行時的程序上下文,而這些不同的程序上下文對同一個核心資料可能會有不同的操作要求,這樣將有可能會破壞全域性核心資料結構中的資訊。為了保證核心核心資料的一致性,當程序處於核心態時,是不允許進行上下文切換的。

這就涉及到另一個問題,中斷是在任何時刻都能發生的,並且對中斷的處理也可能影響核心資料的一致性。那麼,但程序在核心態執行時,對於發生中斷的情況又如何保證核心資料的一致性呢?解決的方法大致分為兩類,一種是簡單地在核心態時,禁止所有的中斷,但這樣會降低對中斷的響應速度;另一種方法是設立臨界區,找出操作語句中可能會導致核心資料不一致的地方,然後將這部分設為臨界區。但CPU執行臨界區程式碼時,把CPU的執行級別提高,這樣就可以遮蔽掉很多的中斷,從而保證資料的一致性了。一般,為了保證系統的效能,臨界區都很小,並且不會經常出現。