SYSCALL系統(tǒng)調(diào)用就是調(diào)用操作系統(tǒng)提供的一系列內(nèi)核功能函數(shù),因?yàn)閮?nèi)核總是對用戶程序持不信任的態(tài)度,一些核心功能不能直接交由用戶程序來實(shí)現(xiàn)執(zhí)行。用戶程序只能發(fā)出請求,然后內(nèi)核調(diào)用相應(yīng)的內(nèi)核函數(shù)來幫著處理,將結(jié)果返回給應(yīng)用程序。如此才能保證系統(tǒng)的穩(wěn)定和安全。本文采用 的實(shí)例來講解系統(tǒng)調(diào)用具體是如何實(shí)現(xiàn)的。 系統(tǒng)調(diào)用是給用戶態(tài)下的程序使用的,但是用戶程序并不直接使用系統(tǒng)調(diào)用,而是系統(tǒng)調(diào)用在用戶態(tài)下的接口。這個(gè)用戶接口就是操作系統(tǒng)提供的系統(tǒng)調(diào)用 ,一般遵循 標(biāo)準(zhǔn)。 的系統(tǒng)調(diào)用是用 上述說的用戶接口就會執(zhí)行 執(zhí)行 可是系統(tǒng)調(diào)用是有很多的,雖然 中實(shí)現(xiàn)的系統(tǒng)調(diào)用沒多少,沒多少也還是有那么一些的,怎么區(qū)別它們呢?這就涉及了系統(tǒng)調(diào)用號概念,每一個(gè)系統(tǒng)調(diào)用都唯一分配了一個(gè)整數(shù)來標(biāo)識,比如說 里面 系統(tǒng)調(diào)用的調(diào)用號就為 1。 系統(tǒng)調(diào)用通俗的講就是是用戶態(tài)下的程序托內(nèi)核辦事,既然是托人辦事那得告訴人家你要辦什么事對吧。這個(gè)告訴人家具體要辦什么事就是要給內(nèi)核傳遞系統(tǒng)調(diào)用號,問題是怎么傳呢?通常的做法就是將這個(gè)系統(tǒng)調(diào)用號放進(jìn) 寄存器,當(dāng)執(zhí)行到系統(tǒng)調(diào)用入口程序的時(shí)候就會根據(jù) eax 的值去調(diào)用具體的系統(tǒng)調(diào)用程序,比如說 中存放的是 1 那么就會去調(diào)用 這個(gè)系統(tǒng)調(diào)用的相關(guān)函數(shù)。這個(gè)系統(tǒng)調(diào)用的入口程序可以理解為在第 個(gè)門描述符中記錄的程序,因?yàn)榭隙ㄊ且雀鶕?jù)向量號拿到總的中斷服務(wù)程序(在這兒就是總的系統(tǒng)調(diào)用程序),然后再根據(jù) 的值去調(diào)用的具體的內(nèi)核功能函數(shù)。 上面只是說的一般的大致情況,如果看過前文多處理器下的中斷機(jī)制應(yīng)該知道, 對所有中斷(包括系統(tǒng)調(diào)用)的處理是先執(zhí)行共同的中斷入口程序,主要就是保護(hù)現(xiàn)場壓棧寄存器,然后根據(jù)向量號的不同執(zhí)行不同的中斷處理程序。在這里就是執(zhí)行系統(tǒng)調(diào)用入口程序,然后再根據(jù) 的值調(diào)用具體的內(nèi)核功能函數(shù)。 這個(gè)具體的內(nèi)核功能函數(shù)咱們就不討論了,內(nèi)核中的表現(xiàn)形式就是一個(gè)個(gè)不同的函數(shù),咱們這兒只討論兩件事: 一是參數(shù),有些系統(tǒng)調(diào)用的是需要參數(shù)的,用戶接口不真正干活,真正干活的是內(nèi)核功能函數(shù),但是需要的參數(shù)在用戶態(tài)下,所以需要在用戶接口部分向內(nèi)核傳遞參數(shù)。傳參有兩種方法:
二是返回值,函數(shù)的調(diào)用約定中規(guī)定了返回值應(yīng)該放在 寄存器里面。而在系統(tǒng)調(diào)用的一開始我們將系統(tǒng)調(diào)用號傳進(jìn)了 寄存器,然后中斷時(shí)保存上下文,將 壓入內(nèi)核棧,系統(tǒng)調(diào)用處理程序?qū)⒆詈蠼Y(jié)果放到 寄存器中。下面注意了,如果不對上下文中的 作修改的話,中斷退出的時(shí)候恢復(fù)上下文彈出 ,彈出的值是啥?是系統(tǒng)調(diào)用號,也就是說將結(jié)果放到 寄存器中放了個(gè)寂寞,所以肯定會有一個(gè)步驟修改上下文中 為結(jié)果這么一個(gè)步驟,這樣回到用戶態(tài)的時(shí)候這個(gè)結(jié)果才會在 寄存器中。 上述差不多將系統(tǒng)調(diào)用的一些理論知識說完了,下面用 的實(shí)例來看看系統(tǒng)調(diào)用具體如何實(shí)現(xiàn)的。 xv6 實(shí)例先來看張總圖把握一下整體流程: 首先便是用戶接口部分,用戶接口是操作系統(tǒng)提供的系統(tǒng)調(diào)用 函數(shù),一般是 標(biāo)準(zhǔn), 關(guān)于這用戶接口定義在 中,來隨便看兩個(gè): int fork(void);這只是對函數(shù)原型的聲明。具體做了什么事呢?這個(gè)定義在 中: 這是用匯編來寫的,而且使用了宏定義,我們來仔細(xì)閱讀一下這段代碼
所以這個(gè)函數(shù)做了什么事?應(yīng)該一目了然啊,就三條指令:
這里還使用了一些宏定義,首先是系統(tǒng)調(diào)用號,定義在 當(dāng)中,隨便看幾個(gè)意思一下: #define SYS_fork 1這個(gè)號就是自定義的,能夠?qū)⒚總€(gè)系統(tǒng)調(diào)用唯一區(qū)分開就好。 上面的宏定義中還涉及了 一個(gè) 兩個(gè) 所以上述 代表的系統(tǒng)調(diào)用的向量號, 版本不同,這個(gè)數(shù)可能不同,我這兒是 ,所以 接著就應(yīng)該是中斷的處理過程,這一塊在前文多處理器下的中斷機(jī)制已經(jīng)講述的很詳細(xì)了,而且還有過程圖,本文就不再贅述。本文重點(diǎn)講述執(zhí)行了通用的中斷入口程序之后如何執(zhí)行系統(tǒng)調(diào)用分支的,如何獲取用戶棧的參數(shù),如何修改上下文中的 使其返回正確的結(jié)果。 問題很多,咱們一個(gè)一個(gè)來解決,首先從 保存了上下文之后跳到 這個(gè)總的中斷處理程序,這個(gè)程序中會根據(jù)向量號不同去執(zhí)行不同的中斷處理程序,如果向量號表示的是系統(tǒng)調(diào)用的話,就會進(jìn)行如下操作: void trap(struct trapframe *tf)可以看到,如果中斷棧幀中的向量號表示的是系統(tǒng)調(diào)用號的話,就會去執(zhí)行系統(tǒng)調(diào)用入口程序。 這個(gè)系統(tǒng)調(diào)用入口程序定義在 里面: 這個(gè)系統(tǒng)調(diào)用的入口函數(shù)的作用就是根據(jù)中斷棧幀中的系統(tǒng)調(diào)用號去調(diào)用相應(yīng)的內(nèi)核功能函數(shù),然后將返回值再填寫到棧幀中的 處。 這個(gè)流程整個(gè)邏輯應(yīng)該是很清晰的,主要注意一點(diǎn),調(diào)用內(nèi)核功能函數(shù)的方式: extern int sys_fork(void);
接下來是定義了一個(gè)函數(shù)指針數(shù)組,就是將上述函數(shù)地址填到數(shù)組相應(yīng)的位置上。 關(guān)于系統(tǒng)調(diào)用還剩下最后一個(gè)問題,根據(jù)上述內(nèi)核中具體的系統(tǒng)調(diào)用函數(shù)原型可以看出,它們的返回類型都是 型且沒有參數(shù),但是有些系統(tǒng)調(diào)用是需要參數(shù)的,所以那些需要參數(shù)的系統(tǒng)調(diào)用就要去獲取參數(shù),去哪獲取呢?是的,去用戶棧獲取參數(shù),因?yàn)? 沒有使用寄存器來傳參,而是將參數(shù)直接壓入用戶棧里面的。 回到系統(tǒng)調(diào)用的開頭,何時(shí)將參數(shù)壓棧的,參數(shù)是為被調(diào)用函數(shù)準(zhǔn)備的,所以調(diào)用函數(shù)之前一定會將參數(shù)壓棧。這個(gè)被調(diào)用函數(shù)就是用戶接口,舉個(gè)例子如果調(diào)用 ,則在這之前一定會將參數(shù) 按照這個(gè)順序壓棧,再 調(diào)用函數(shù),只是在 語言中這個(gè)過程可能看起來不是那么真切,如果是用匯編來寫,或者查看編譯之后的程序,會有下面的大致過程: 在 捋清楚這個(gè)關(guān)系之后就知道怎么去拿參數(shù)了,直接去中斷棧幀中獲取用戶棧棧頂值 ,再根據(jù)參數(shù)返回地址的位置關(guān)系獲取一個(gè)個(gè)參數(shù),來看 中有關(guān)獲取參數(shù)的幾個(gè)函數(shù): int argint(int n, int *ip) //獲取系統(tǒng)調(diào)用的第n個(gè)int型的參數(shù),存到ip這個(gè)位置這是獲取一個(gè) t 型的參數(shù), 這個(gè)函數(shù)用來獲取一個(gè)指針,指針就是地址,地址就是一個(gè) 位無符號數(shù),所以調(diào)用前面的 來獲取這個(gè)數(shù)存到 中,這個(gè) 本身其實(shí)是個(gè)地址值,所以將其轉(zhuǎn)化 類型,然后賦值給 。 注意這里使用的是二級指針,為什么要使用二級指針,我們來看看如果使用一級指針會發(fā)生什么,如果這個(gè)函數(shù)是這樣: int argptr(int n, char *pp, int size) //pp類型變?yōu)閏har*如果這個(gè)函數(shù)變成這樣還對嗎,答案是不對的。舉個(gè)例子來說明,在 這個(gè)內(nèi)核功能函數(shù)中會調(diào)用 : 調(diào)用 的本意是獲取第一個(gè)參數(shù),也就是用戶接口 的 地址值,并將其賦給 。 假如 等于某個(gè)地址 ,如果使用一級指針:調(diào)用 如果使用的是二級指針,調(diào)用 還有個(gè)獲取字符串的函數(shù),跟獲取指針差不了太多,只是多了一個(gè)算字符串長度的步驟,這里就不贅述了。 本文關(guān)于系統(tǒng)調(diào)用就這么多,最后再看張圖來捋一捋: 這是以 write 系統(tǒng)調(diào)用為例的系統(tǒng)調(diào)用過程圖,圖是丑了點(diǎn),不過這條線應(yīng)該捋得還是挺清晰的,好啦,本文就到這里,有什么錯(cuò)誤還請批評指正,也歡迎大家來同我討論交流學(xué)習(xí)進(jìn)步。 |
|
|