現代作業系統中程式本身沒有多少權利訪問系統資源,為了保護系統資源,os會組織程式直接訪問系統資源,比如檔案、網路、io、各種裝置等。
但是有些場景不借助os沒法很好地辦到,比如讓程式等待一段時間,如果借助os,我們可以用sleep()
,但是要是自己寫的話,可能就是如下這樣:
for
(int i=
0;i<
1000000
;i++
);
可以是可以,但是這樣會浪費cpu時間。特別是這段程式在不同頻率的cpu上耗費時間不一樣:
因此這樣絕不是乙個好辦法。為了讓程式更好地借助os實現一些操作,os會提供一套介面,讓程式使用。這些介面往往通過中斷來實現,比如linux使用0x80
號中斷作為系統呼叫入口。
使用不便。os提供的介面比較原始,沒有很好地包裝
各個os系統呼叫不相容。linux、windows、unix都不同
為了解決這個問題,我們可以在系統呼叫和程式之間做一層抽象層來統一管理,這就是執行庫
。比如對於讀取檔案,我們可以用c語言中的fread
函式,其底層在windows下函式實現可能是readfile,linux下可能是read系統呼叫,但是沒關係,不管在哪個平台,都可以用fread來讀檔案。
現代cpu可以在多種截然不同特權級別下執行指令,現代os有使用者模式(user mode)
和核心模式(kernel mode)
,也就是使用者態
和核心態
。普通程式執行在使用者態,對操作有很多限制,比如沒法直接訪問硬體裝置、開關中斷、改變特權模式等。
使用者態程式想要執行核心態**一般要通過中斷(interrupt)
來從使用者態
切換到核心態
。**中斷就是乙個硬體或者軟體發出的請求,要求cpu暫停當前工作轉手去處理更加重要的事情。**舉個例子,我們在編輯檔案的時候,鍵盤上的鍵按下,cpu如何獲知這一點呢?
第一種是輪詢
(poll),cpu每隔一小段時間(幾十到幾百毫秒)詢問鍵盤是否被按下,大部分情況都是沒有鍵被按下的回應,這樣就很浪費cpu時間
第二種是發訊號
,cpu不理睬鍵盤,鍵盤被按下之後,鍵盤晶元給cpu傳送乙個訊號,cpu接受到訊號去詢問哪個鍵被按下。這個訊號就是乙個中斷
中斷有兩個屬性,提及中斷要有中斷號(從0開始)和中斷處理程式(interrupt service routine,isr),類似於訊號,有訊號id和訊號處理程式。核心裡有乙個中斷向量表(interrupt vector table),這個陣列的第n項表示第n號中斷的中斷處理程式。中斷來了之後,cpu暫停當前**執行並儲存當前上下文,根據中斷號查詢對應的處理程式呼叫。(這裡涉及程序上下文切換)
中斷有兩種:
硬體中斷
:硬體異常或者電源斷電、鍵盤被按下等
軟體中斷
:軟體中斷通常是指令,帶有乙個引數記錄中斷號,使用這個指令使用者可以手動觸發某個中斷並執行其中斷處理程式。比如在i386下,int 0x80
這個指令會去執行0x80
號中斷處理程式
對於中斷和系統呼叫又如何繫結的呢?
由於中斷號很有限,os不會讓乙個中斷號對應乙個系統呼叫。linux用int 0x80
來觸發所有系統呼叫。那麼對於同乙個中斷號,os怎麼知道哪乙個系統呼叫要執行呢?首先系統呼叫號會被放入乙個固定的暫存器eax
,使用者把系統呼叫號傳入eax
,然後使用int 0x80
呼叫中斷,中斷服務程式就可以eax
取得系統呼叫號,進而呼叫對應的函式。
下面以fork
(系統呼叫號為2)為例來看系統呼叫如何執行的:
一步一步看細節:
觸發中斷
程式在**中呼叫乙個系統呼叫時,用函式來實現的:
int
main()
fork
是對系統呼叫fork
的封裝,可以用下面這個巨集來定位它:
_syscall0
(pid_t,fork)
;
_syscall0
是乙個巨集函式,用來定義乙個沒有引數的系統呼叫的封裝。引數意義:
pid_t:表示系統呼叫返回值型別,代表程序id
fork:表示這個系統呼叫名稱是fork
這個巨集定義展開之後如下帶有at&t
格式的彙編**的程式:
pid_t fork
(void
)
翻譯一下就是:
pid_t fork
(void
)
__nr__fork是fork的系統呼叫號2,__syscall_return是另乙個巨集,用於檢查系統呼叫返回值。
當使用者呼叫某個系統呼叫的時候,實際是執行了以上一段彙編**。cpu
執行到int $0x80
時,會儲存現場以便恢復,接著會將特權狀態切換到核心態
。然後cpu便會查詢中斷向量表中的第0x80
號元素。
切換堆疊
在實際執行中斷向量表中的第0x80
號元素所對應的函式之前,cpu
首先還要進行棧的切換。在linux中,使用者態和核心態使用的是不同的棧,兩者各自負責各自的函式呼叫,互不干擾。但在應用程式呼叫0x80
號中斷時,程式的執行流程從使用者態切換到核心態,這時程式的當前棧必須也相應地從使用者棧切換到核心棧
。從中斷處理函式中返回時,程式的當前棧還要從核心棧切換回使用者棧。所謂的「當前棧」,指的是esp的值所在的棧空間。如果esp
的值位於使用者棧的範圍內,那麼程式的當前棧就是使用者棧,反之亦然。此外,暫存器ss
的值還應該指向當前棧所在的頁。
所以,將當前棧由使用者棧切換為核心棧的實際行為就是:
儲存當前的暫存器esp
、ss
的值。
將esp
、ss
的值設定為核心棧的相應值。
反過賴將當前棧從核心態轉為使用者態就是:
當0x80
號中斷發生的時候,cpu
除了切入核心態之外,還會自動完成下列幾件事:
找到當前程序的核心棧(每。 個程序都有自己的核心棧)。
在核心棧中依次壓入使用者態的暫存器ss
、esp
、eflags
、cs
、eip
.
而當核心從系統呼叫中返回的時候,須要呼叫iret
指令來回到使用者態,iret
指令則會從核心棧裡彈出暫存器ss
、esp
、eflags
、cs
、eip
的值,使得棧恢復到使用者態的狀態。這個過程可以用下圖來表示。
中斷處理程式
棧切換完成之後,程式流程就切換到了中斷向量表中記錄的0x80
號中斷處理程式。
具體見《程式設計師的自我修養》,下面直接上圖呼叫流程:
linux核心基礎(系統呼叫,簡明)
核心基礎 系統呼叫 在說系統呼叫之前,先來說說核心是怎麼和我們互動的,或者說是怎麼和我們產生交集的。首先,核心是用來控制硬體的只有核心才能直接控制硬體,所以說核心很重要,如果核心被控制那麼電腦的一切都被控制了,所以我們需要把核心保護起來,所以shell 就誕生了,我們絕大多數情況下是在和shell ...
linux核心基礎(系統呼叫,簡明)
核心基礎 系統呼叫 在說系統呼叫之前。先來說說核心是怎麼和我們互動的。或者說是怎麼和我們產生交集的。首先,核心是用來控制硬體的僅僅有核心才幹直接控制硬體,所以說核心非常重要,假設核心被控制那麼電腦的一切都被控制了,所以我們須要把核心保護起來。所以shell 就誕生了,我們絕大多數情況下是在和shel...
linux 系統呼叫
使用者應用可以通過兩種方式使用系統呼叫。第一種方式是通過c庫函式,包括系統呼叫在c庫中的封裝函式和其他普通函式。圖5.2 使用系統呼叫的兩種方式 第二種方式是使用 syscall巨集。2.6.18版本之前的核心,在include asm i386 unistd.h檔案中定義有7個 syscall巨集...