最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會(huì)員登陸 & 注冊(cè)

全網(wǎng)最詳細(xì)、最值得收藏的,linux內(nèi)核剖析--Linux系統(tǒng)調(diào)用詳解(實(shí)現(xiàn)機(jī)制分析)

2022-02-19 15:03 作者:補(bǔ)給站Linux內(nèi)核  | 我要投稿

本文介紹了系統(tǒng)調(diào)用的一些實(shí)現(xiàn)細(xì)節(jié)。首先分析了系統(tǒng)調(diào)用的意義,它們與庫(kù)函數(shù)和應(yīng)用程序接口(API)有怎樣的關(guān)系。然后,我們考察了Linux內(nèi)核如何實(shí)現(xiàn)系統(tǒng)調(diào)用,以及執(zhí)行系統(tǒng)調(diào)用的連鎖反應(yīng):陷入內(nèi)核,傳遞系統(tǒng)調(diào)用號(hào)和參數(shù),執(zhí)行正確的系統(tǒng)調(diào)用函數(shù),并把返回值帶回用戶(hù)空間。最后討論了如何增加系統(tǒng)調(diào)用,并提供了從用戶(hù)空間訪(fǎng)問(wèn)系統(tǒng)調(diào)用的簡(jiǎn)單例子。

系統(tǒng)調(diào)用概述

  • 計(jì)算機(jī)系統(tǒng)的各種硬件資源是有限的,在現(xiàn)代多任務(wù)操作系統(tǒng)上同時(shí)運(yùn)行的多個(gè)進(jìn)程都需要訪(fǎng)問(wèn)這些資源,為了更好的管理這些資源進(jìn)程是不允許直接操作的,所有對(duì)這些資源的訪(fǎng)問(wèn)都必須有操作系統(tǒng)控制。也就是說(shuō)操作系統(tǒng)是使用這些資源的唯一入口,而這個(gè)入口就是操作系統(tǒng)提供的系統(tǒng)調(diào)用(System Call)。在linux中系統(tǒng)調(diào)用是用戶(hù)空間訪(fǎng)問(wèn)內(nèi)核的唯一手段,除異常和陷入外,他們是內(nèi)核唯一的合法入口。

  • 一般情況下應(yīng)用程序通過(guò)應(yīng)用編程接口API,而不是直接通過(guò)系統(tǒng)調(diào)用來(lái)編程。在Unix世界,最流行的API是基于POSIX標(biāo)準(zhǔn)的。

  • 操作系統(tǒng)一般是通過(guò)中斷從用戶(hù)態(tài)切換到內(nèi)核態(tài)。中斷就是一個(gè)硬件或軟件請(qǐng)求,要求CPU暫停當(dāng)前的工作,去處理更重要的事情。比如,在x86機(jī)器上可以通過(guò)int指令進(jìn)行軟件中斷,而在磁盤(pán)完成讀寫(xiě)操作后會(huì)向CPU發(fā)起硬件中斷。

  • 中斷有兩個(gè)重要的屬性,中斷號(hào)和中斷處理程序。中斷號(hào)用來(lái)標(biāo)識(shí)不同的中斷,不同的中斷具有不同的中斷處理程序。在操作系統(tǒng)內(nèi)核中維護(hù)著一個(gè)中斷向量表(Interrupt Vector Table),這個(gè)數(shù)組存儲(chǔ)了所有中斷處理程序的地址,而中斷號(hào)就是相應(yīng)中斷在中斷向量表中的偏移量。

  • 一般地,系統(tǒng)調(diào)用都是通過(guò)軟件中斷實(shí)現(xiàn)的,x86系統(tǒng)上的軟件中斷由int $0x80指令產(chǎn)生,而128號(hào)異常處理程序就是系統(tǒng)調(diào)用處理程序system_call(),它與硬件體系有關(guān),在entry.S中用匯編寫(xiě)。接下來(lái)就來(lái)看一下Linux下系統(tǒng)調(diào)用具體的實(shí)現(xiàn)過(guò)程。

為什么需要系統(tǒng)調(diào)用

  • linux內(nèi)核中設(shè)置了一組用于實(shí)現(xiàn)系統(tǒng)功能的子程序,稱(chēng)為系統(tǒng)調(diào)用。系統(tǒng)調(diào)用和普通庫(kù)函數(shù)調(diào)用非常相似,只是系統(tǒng)調(diào)用由操作系統(tǒng)核心提供,運(yùn)行于內(nèi)核態(tài),而普通的函數(shù)調(diào)用由函數(shù)庫(kù)或用戶(hù)自己提供,運(yùn)行于用戶(hù)態(tài)。

  • 一般的,進(jìn)程是不能訪(fǎng)問(wèn)內(nèi)核的。它不能訪(fǎng)問(wèn)內(nèi)核所占內(nèi)存空間也不能調(diào)用內(nèi)核函數(shù)。CPU硬件決定了這些(這就是為什么它被稱(chēng)作“保護(hù)模式”

  • 為了和用戶(hù)空間上運(yùn)行的進(jìn)程進(jìn)行交互,內(nèi)核提供了一組接口。透過(guò)該接口,應(yīng)用程序可以訪(fǎng)問(wèn)硬件設(shè)備和其他操作系統(tǒng)資源。這組接口在應(yīng)用程序和內(nèi)核之間扮演了使者的角色,應(yīng)用程序發(fā)送各種請(qǐng)求,而內(nèi)核負(fù)責(zé)滿(mǎn)足這些請(qǐng)求(或者讓?xiě)?yīng)用程序暫時(shí)擱置)。實(shí)際上提供這組接口主要是為了保證系統(tǒng)穩(wěn)定可靠,避免應(yīng)用程序肆意妄行,惹出大麻煩。

  • 系統(tǒng)調(diào)用在用戶(hù)空間進(jìn)程和硬件設(shè)備之間添加了一個(gè)中間層。該層主要作用有三個(gè):

  1. 它為用戶(hù)空間提供了一種統(tǒng)一的硬件的抽象接口。比如當(dāng)需要讀些文件的時(shí)候,應(yīng)用程序就可以不去管磁盤(pán)類(lèi)型和介質(zhì),甚至不用去管文件所在的文件系統(tǒng)到底是哪種類(lèi)型。

  2. 系統(tǒng)調(diào)用保證了系統(tǒng)的穩(wěn)定和安全。作為硬件設(shè)備和應(yīng)用程序之間的中間人,內(nèi)核可以基于權(quán)限和其他一些規(guī)則對(duì)需要進(jìn)行的訪(fǎng)問(wèn)進(jìn)行裁決。舉例來(lái)說(shuō),這樣可以避免應(yīng)用程序不正確地使用硬件設(shè)備,竊取其他進(jìn)程的資源,或做出其他什么危害系統(tǒng)的事情。

  3. 每個(gè)進(jìn)程都運(yùn)行在虛擬系統(tǒng)中,而在用戶(hù)空間和系統(tǒng)的其余部分提供這樣一層公共接口,也是出于這種考慮。如果應(yīng)用程序可以隨意訪(fǎng)問(wèn)硬件而內(nèi)核又對(duì)此一無(wú)所知的話(huà),幾乎就沒(méi)法實(shí)現(xiàn)多任務(wù)和虛擬內(nèi)存,當(dāng)然也不可能實(shí)現(xiàn)良好的穩(wěn)定性和安全性。在Linux中,系統(tǒng)調(diào)用是用戶(hù)空間訪(fǎng)問(wèn)內(nèi)核的惟一手段;除異常和中斷外,它們是內(nèi)核惟一的合法入口。

API/POSIX/C庫(kù)的區(qū)別與聯(lián)系

  • 一般情況下,應(yīng)用程序通過(guò)應(yīng)用編程接口(API)而不是直接通過(guò)系統(tǒng)調(diào)用來(lái)編程。這點(diǎn)很重要,因?yàn)閼?yīng)用程序使用的這種編程接口實(shí)際上并不需要和內(nèi)核提供的系統(tǒng)調(diào)用一一對(duì)應(yīng)。

一個(gè)API定義了一組應(yīng)用程序使用的編程接口。它們可以實(shí)現(xiàn)成一個(gè)系統(tǒng)調(diào)用,也可以通過(guò)調(diào)用多個(gè)系統(tǒng)調(diào)用來(lái)實(shí)現(xiàn),而完全不使用任何系統(tǒng)調(diào)用也不存在問(wèn)題。實(shí)際上,API可以在各種不同的操作系統(tǒng)上實(shí)現(xiàn),給應(yīng)用程序提供完全相同的接口,而它們本身在這些系統(tǒng)上的實(shí)現(xiàn)卻可能迥異。

  • 在Unix世界中,最流行的應(yīng)用編程接口是基于POSIX標(biāo)準(zhǔn)的,其目標(biāo)是提供一套大體上基于Unix的可移植操作系統(tǒng)標(biāo)準(zhǔn)。POSIX是說(shuō)明API和系統(tǒng)調(diào)用之間關(guān)系的一個(gè)極好例子。在大多數(shù)Unix系統(tǒng)上,根據(jù)POSIX而定義的API函數(shù)和系統(tǒng)調(diào)用之間有著直接關(guān)系。

  • Linux的系統(tǒng)調(diào)用像大多數(shù)Unix系統(tǒng)一樣,作為C庫(kù)的一部分提供如下圖所示。C庫(kù)實(shí)現(xiàn)了 Unix系統(tǒng)的主要API,包括標(biāo)準(zhǔn)C庫(kù)函數(shù)和系統(tǒng)調(diào)用。所有的C程序都可以使用C庫(kù),而由于C語(yǔ)言本身的特點(diǎn),其他語(yǔ)言也可以很方便地把它們封裝起來(lái)使用。

  • 從程序員的角度看,系統(tǒng)調(diào)用無(wú)關(guān)緊要,他們只需要跟API打交道就可以了。相反,內(nèi)核只跟系統(tǒng)調(diào)用打交道;庫(kù)函數(shù)及應(yīng)用程序是怎么使用系統(tǒng)調(diào)用不是內(nèi)核所關(guān)心的。

  • 關(guān)于Unix的界面設(shè)計(jì)有一句通用的格言“提供機(jī)制而不是策略”。換句話(huà)說(shuō),Unix的系統(tǒng)調(diào)用抽象出了用于完成某種確定目的的函數(shù)。至干這些函數(shù)怎么用完全不需要內(nèi)核去關(guān)心。區(qū)別對(duì)待機(jī)制(mechanism)和策略(policy)是Unix設(shè)計(jì)中的一大亮點(diǎn)。大部分的編程問(wèn)題都可以被切割成兩個(gè)部分:“需要提供什么功能”(機(jī)制)和“怎樣實(shí)現(xiàn)這些功能”(策略)。

區(qū)別

  • api是函數(shù)的定義,規(guī)定了這個(gè)函數(shù)的功能,跟內(nèi)核無(wú)直接關(guān)系。而系統(tǒng)調(diào)用是通過(guò)中斷向內(nèi)核發(fā)請(qǐng)求,實(shí)現(xiàn)內(nèi)核提供的某些服務(wù)。

聯(lián)系

  • 一個(gè)api可能會(huì)需要一個(gè)或多個(gè)系統(tǒng)調(diào)用來(lái)完成特定功能。通俗點(diǎn)說(shuō)就是如果這個(gè)api需要跟內(nèi)核打交道就需要系統(tǒng)調(diào)用,否則不需要。

  • 程序員調(diào)用的是API(API函數(shù)),然后通過(guò)與系統(tǒng)調(diào)用共同完成函數(shù)的功能。

  • 因此,API是一個(gè)提供給應(yīng)用程序的接口,一組函數(shù),是與程序員進(jìn)行直接交互的。

  • 系統(tǒng)調(diào)用則不與程序員進(jìn)行交互的,它根據(jù)API函數(shù),通過(guò)一個(gè)軟中斷機(jī)制向內(nèi)核提交請(qǐng)求,以獲取內(nèi)核服務(wù)的接口。

  • 并不是所有的API函數(shù)都一一對(duì)應(yīng)一個(gè)系統(tǒng)調(diào)用,有時(shí),一個(gè)API函數(shù)會(huì)需要幾個(gè)系統(tǒng)調(diào)用來(lái)共同完成函數(shù)的功能,甚至還有一些API函數(shù)不需要調(diào)用相應(yīng)的系統(tǒng)調(diào)用(因此它所完成的不是內(nèi)核提供的服務(wù))

系統(tǒng)調(diào)用的實(shí)現(xiàn)原理

基本機(jī)制

  • 前文已經(jīng)提到了Linux下的系統(tǒng)調(diào)用是通過(guò)0x80實(shí)現(xiàn)的,但是我們知道操作系統(tǒng)會(huì)有多個(gè)系統(tǒng)調(diào)用(Linux下有319個(gè)系統(tǒng)調(diào)用),而對(duì)于同一個(gè)中斷號(hào)是如何處理多個(gè)不同的系統(tǒng)調(diào)用的?最簡(jiǎn)單的方式是對(duì)于不同的系統(tǒng)調(diào)用采用不同的中斷號(hào),但是中斷號(hào)明顯是一種稀缺資源,Linux顯然不會(huì)這么做;還有一個(gè)問(wèn)題就是系統(tǒng)調(diào)用是需要提供參數(shù),并且具有返回值的,這些參數(shù)又是怎么傳遞的?也就是說(shuō),對(duì)于系統(tǒng)調(diào)用我們要搞清楚兩點(diǎn):

  1. 系統(tǒng)調(diào)用的函數(shù)名稱(chēng)轉(zhuǎn)換。

  2. 系統(tǒng)調(diào)用的參數(shù)傳遞。

  • 首先看第一個(gè)問(wèn)題。實(shí)際上,Linux中每個(gè)系統(tǒng)調(diào)用都有相應(yīng)的系統(tǒng)調(diào)用號(hào)作為唯一的標(biāo)識(shí),內(nèi)核維護(hù)一張系統(tǒng)調(diào)用表,sys_call_table,表中的元素是系統(tǒng)調(diào)用函數(shù)的起始地址,而系統(tǒng)調(diào)用號(hào)就是系統(tǒng)調(diào)用在調(diào)用表的偏移量。在x86上,系統(tǒng)調(diào)用號(hào)是通過(guò)eax寄存器傳遞給內(nèi)核的。比如fork()的實(shí)現(xiàn):

  • 用戶(hù)空間的程序無(wú)法直接執(zhí)行內(nèi)核代碼。它們不能直接調(diào)用內(nèi)核空間中的函數(shù),因?yàn)閮?nèi)核駐留在受保護(hù)的地址空間上。如果進(jìn)程可以直接在內(nèi)核的地址空間上讀寫(xiě)的話(huà),系統(tǒng)安全就會(huì)失去控制。所以,應(yīng)用程序應(yīng)該以某種方式通知系統(tǒng),告訴內(nèi)核自己需要執(zhí)行一個(gè)系統(tǒng)調(diào)用,希望系統(tǒng)切換到內(nèi)核態(tài),這樣內(nèi)核就可以代表應(yīng)用程序來(lái)執(zhí)行該系統(tǒng)調(diào)用了。

  • 通知內(nèi)核的機(jī)制是靠軟件中斷實(shí)現(xiàn)的。首先,用戶(hù)程序?yàn)橄到y(tǒng)調(diào)用設(shè)置參數(shù)。其中一個(gè)參數(shù)是系統(tǒng)調(diào)用編號(hào)。參數(shù)設(shè)置完成后,程序執(zhí)行“系統(tǒng)調(diào)用”指令。x86系統(tǒng)上的軟中斷由int產(chǎn)生。這個(gè)指令會(huì)導(dǎo)致一個(gè)異常:產(chǎn)生一個(gè)事件,這個(gè)事件會(huì)致使處理器切換到內(nèi)核態(tài)并跳轉(zhuǎn)到一個(gè)新的地址,并開(kāi)始執(zhí)行那里的異常處理程序。此時(shí)的異常處理程序?qū)嶋H上就是系統(tǒng)調(diào)用處理程序。它與硬件體系結(jié)構(gòu)緊密相關(guān)。

  • 新地址的指令會(huì)保存程序的狀態(tài),計(jì)算出應(yīng)該調(diào)用哪個(gè)系統(tǒng)調(diào)用,調(diào)用內(nèi)核中實(shí)現(xiàn)那個(gè)系統(tǒng)調(diào)用的函數(shù),恢復(fù)用戶(hù)程序狀態(tài),然后將控制權(quán)返還給用戶(hù)程序。系統(tǒng)調(diào)用是設(shè)備驅(qū)動(dòng)程序中定義的函數(shù)最終被調(diào)用的一種方式。

  • 從系統(tǒng)分析的角度,linux的系統(tǒng)調(diào)用涉及4個(gè)方面的問(wèn)題。

響應(yīng)函數(shù)sys_xxx

  • 響應(yīng)函數(shù)名以“sys_”開(kāi)頭,后跟該系統(tǒng)調(diào)用的名字。

例如 系統(tǒng)調(diào)用fork()的響應(yīng)函數(shù)是sys_fork()(見(jiàn)Kernel/fork.c),exit()的響應(yīng)函數(shù)是sys_exit()(見(jiàn)kernel/fork.)。

系統(tǒng)調(diào)用表與系統(tǒng)調(diào)用號(hào)-=>數(shù)組與下標(biāo)

  • 文件include/asm/unisted.h為每個(gè)系統(tǒng)調(diào)用規(guī)定了唯一的編號(hào)。


在我們系統(tǒng)中/usr/include/asm/unistd_32.h,可以通過(guò)find / -name unistd_32.h -print查找) 而內(nèi)核中的頭文件路徑不同的內(nèi)核版本以及不同的發(fā)行版,文件的存儲(chǔ)結(jié)構(gòu)可能有所區(qū)別



假設(shè)用name表示系統(tǒng)調(diào)用的名稱(chēng),那么系統(tǒng)調(diào)用號(hào)與系統(tǒng)調(diào)用響應(yīng)函數(shù)的關(guān)系是:以系統(tǒng)調(diào)用號(hào)_NR_name作為下標(biāo),可找出系統(tǒng)調(diào)用表sys_call_table(見(jiàn)arch/i386/kernel/entry.S)中對(duì)應(yīng)表項(xiàng)的內(nèi)容,它正好是該系統(tǒng)調(diào)用的響應(yīng)函數(shù)sys_name的入口地址。 系統(tǒng)調(diào)用表sys_call_table記錄了各sys_name函數(shù)在表中的位置,共190項(xiàng)。有了這張表,就很容易根據(jù)特定系統(tǒng)調(diào)用




  • 在表中的偏移量,找到對(duì)應(yīng)的系統(tǒng)調(diào)用響應(yīng)函數(shù)的入口地址。系統(tǒng)調(diào)用表共256項(xiàng),余下的項(xiàng)是可供用戶(hù)自己添加的系統(tǒng)調(diào)用空間。

  • 在Linux中,每個(gè)系統(tǒng)調(diào)用被賦予一個(gè)系統(tǒng)調(diào)用號(hào)。這樣,通過(guò)這個(gè)獨(dú)一無(wú)二的號(hào)就可以關(guān)聯(lián)系統(tǒng)調(diào)用。當(dāng)用戶(hù)空間的進(jìn)程執(zhí)行一個(gè)系統(tǒng)調(diào)用的時(shí)候,這個(gè)系統(tǒng)調(diào)用號(hào)就被用來(lái)指明到底是要執(zhí)行哪個(gè)系統(tǒng)調(diào)用。進(jìn)程不會(huì)提及系統(tǒng)調(diào)用的名稱(chēng)。

  • 系統(tǒng)調(diào)用號(hào)相當(dāng)關(guān)鍵,一旦分配就不能再有任何變更,否則編譯好的應(yīng)用程序就會(huì)崩潰。Linux有一個(gè)“未實(shí)現(xiàn)”系統(tǒng)調(diào)用sys_ni_syscall(),它除了返回一ENOSYS外不做任何其他工作,這個(gè)錯(cuò)誤號(hào)就是專(zhuān)門(mén)針對(duì)無(wú)效的系統(tǒng)調(diào)用而設(shè)的。

  • 因?yàn)樗械南到y(tǒng)調(diào)用陷入內(nèi)核的方式都一樣,所以?xún)H僅是陷入內(nèi)核空間是不夠的。因此必須把系統(tǒng)調(diào)用號(hào)一并傳給內(nèi)核。在x86上,系統(tǒng)調(diào)用號(hào)是通過(guò)eax寄存器傳遞給內(nèi)核的。在陷人內(nèi)核之前,用戶(hù)空間就把相應(yīng)系統(tǒng)調(diào)用所對(duì)應(yīng)的號(hào)放入eax中了。這樣系統(tǒng)調(diào)用處理程序一旦運(yùn)行,就可以從eax中得到數(shù)據(jù)。其他體系結(jié)構(gòu)上的實(shí)現(xiàn)也都類(lèi)似。

  • 內(nèi)核記錄了系統(tǒng)調(diào)用表中的所有已注冊(cè)過(guò)的系統(tǒng)調(diào)用的列表,存儲(chǔ)在sys_call_table中。它與體系結(jié)構(gòu)有關(guān),一般在entry.s中定義。這個(gè)表中為每一個(gè)有效的系統(tǒng)調(diào)用指定了惟一的系統(tǒng)調(diào)用號(hào)。sys_call_table是一張由指向?qū)崿F(xiàn)各種系統(tǒng)調(diào)用的內(nèi)核函數(shù)的函數(shù)指針組成的表:system_call()函數(shù)通過(guò)將給定的系統(tǒng)調(diào)用號(hào)與NR_syscalls做比較來(lái)檢查其有效性。如果它大于或者等于NR syscalls,該函數(shù)就返回一ENOSYS。否則,就執(zhí)行相應(yīng)的系統(tǒng)調(diào)用。




? ? ?call *sys_ call-table(,%eax, 4)

由于系統(tǒng)調(diào)用表中的表項(xiàng)是以32位(4字節(jié))類(lèi)型存放的,所以?xún)?nèi)核需要將給定的系統(tǒng)調(diào)用號(hào)乘以4,然后用所得的結(jié)果在該表中查詢(xún)其位置

進(jìn)程的系統(tǒng)調(diào)用命令轉(zhuǎn)換為INT 0x80中斷的過(guò)程

  • 宏定義_syscallN()見(jiàn)include/asm/unisted.h)用于系統(tǒng)調(diào)用的格式轉(zhuǎn)換和參數(shù)的傳遞。N取0~5之間的整數(shù)。

  • 參數(shù)個(gè)數(shù)為N的系統(tǒng)調(diào)用由_syscallN()負(fù)責(zé)格式轉(zhuǎn)換和參數(shù)傳遞。系統(tǒng)調(diào)用號(hào)放入EAX寄存器,啟動(dòng)INT 0x80后,規(guī)定返回值送EAX寄存器。

系統(tǒng)調(diào)用功能模塊的初始化

  • 對(duì)系統(tǒng)調(diào)用的初始化也就是對(duì)INT 0x80的初始化。

  • 系統(tǒng)啟動(dòng)時(shí),匯編子程序setup_idt(見(jiàn)arch/i386/kernel/head.S)準(zhǔn)備了1張256項(xiàng)的idt表,由start_kernel()(見(jiàn)init/main.c),trap_init()(見(jiàn)arch/i386/kernel/traps.c)調(diào)用的C語(yǔ)言宏定義set_system_gate(0x80,&system_call)(見(jiàn)include/asm/system.h)設(shè)置0x80號(hào)軟中斷的服務(wù)程序?yàn)?system_call(見(jiàn)arch/i386/kernel/entry.S), system.call就是所有系統(tǒng)調(diào)用的總?cè)肟凇?/p>

內(nèi)核如何為各種系統(tǒng)調(diào)用服務(wù)

  • 當(dāng)進(jìn)程需要進(jìn)行系統(tǒng)調(diào)用時(shí),必須以C語(yǔ)言函數(shù)的形式寫(xiě)一句系統(tǒng)調(diào)用命令。該命令如果已在某個(gè)頭文件中由相應(yīng)的_syscallN()展開(kāi),則用戶(hù)程序必須包含該文件。當(dāng)進(jìn)程執(zhí)行到用戶(hù)程序的系統(tǒng)調(diào)用命令時(shí),實(shí)際上執(zhí)行了由宏命令_syscallN()展開(kāi)的函數(shù)。系統(tǒng)調(diào)用的參數(shù) 由各通用寄存器傳遞,然后執(zhí)行INT 0x80,以?xún)?nèi)核態(tài)進(jìn)入入口地址system_call。


  • ret_from_sys_call

  • 以ret_from_sys_call入口的匯編程序段在linux進(jìn)程管理中起到了十分重要的作用。

  • 所有系統(tǒng)調(diào)用結(jié)束前以及大部分中斷服務(wù)返回前,都會(huì)跳轉(zhuǎn)至此處入口地址。 該段程序不僅僅為系統(tǒng)調(diào)用服務(wù),它還處理中斷嵌套、CPU調(diào)度、信號(hào)等事務(wù)。

內(nèi)核如何為系統(tǒng)調(diào)用的參數(shù)傳遞參數(shù)

參數(shù)傳遞

  • 除了系統(tǒng)調(diào)用號(hào)以外,大部分系統(tǒng)調(diào)用都還需要一些外部的參數(shù)輸人。所以,在發(fā)生異常的時(shí)候,應(yīng)該把這些參數(shù)從用戶(hù)空間傳給內(nèi)核。最簡(jiǎn)單的辦法就是像傳遞系統(tǒng)調(diào)用號(hào)一樣把這些參數(shù)也存放在寄存器里。在x86系統(tǒng)上,ebx, ecx, edx, esi和edi按照順序存放前五個(gè)參數(shù)。需要六個(gè)或六個(gè)以上參數(shù)的情況不多見(jiàn),此時(shí),應(yīng)該用一個(gè)單獨(dú)的寄存器存放指向所有這些參數(shù)在用戶(hù)空間地址的指針。

  • 給用戶(hù)空間的返回值也通過(guò)寄存器傳遞。在x86系統(tǒng)上,它存放在eax寄存器中。接下來(lái)許多關(guān)于系統(tǒng)調(diào)用處理程序的描述都是針對(duì)x86版本的。但不用擔(dān)心,所有體系結(jié)構(gòu)的實(shí)現(xiàn)都很類(lèi)似。

參數(shù)驗(yàn)證

  • 系統(tǒng)調(diào)用必須仔細(xì)檢查它們所有的參數(shù)是否合法有效。舉例來(lái)說(shuō),與文件I/O相關(guān)的系統(tǒng)調(diào)用必須檢查文件描述符是否有效。與進(jìn)程相關(guān)的函數(shù)必須檢查提供的PID是否有效。必須檢查每個(gè)參數(shù),保證它們不但合法有效,而且正確。

  • 最重要的一種檢查就是檢查用戶(hù)提供的指針是否有效。試想,如果一個(gè)進(jìn)程可以給內(nèi)核傳遞指針而又無(wú)須被檢查,那么它就可以給出一個(gè)它根本就沒(méi)有訪(fǎng)問(wèn)權(quán)限的指針,哄騙內(nèi)核去為它拷貝本不允許它訪(fǎng)問(wèn)的數(shù)據(jù),如原本屬于其他進(jìn)程的數(shù)據(jù)。在接收一個(gè)用戶(hù)空間的指針之前,內(nèi)核必須保證:

  1. 指針指向的內(nèi)存區(qū)域?qū)儆谟脩?hù)空間。進(jìn)程決不能哄騙內(nèi)核去讀內(nèi)核空間的數(shù)據(jù)。

  2. 指針指向的內(nèi)存區(qū)域在進(jìn)程的地址空間里。進(jìn)程決不能哄騙內(nèi)核去讀其他進(jìn)程的數(shù)據(jù)。

  3. 如果是讀,該內(nèi)存應(yīng)被標(biāo)記為可讀。如果是寫(xiě),該內(nèi)存應(yīng)被標(biāo)記為可寫(xiě)。進(jìn)程決不能繞過(guò)內(nèi)存訪(fǎng)問(wèn)限制。

  • 內(nèi)核提供了兩個(gè)方法來(lái)完成必須的檢查和內(nèi)核空間與用戶(hù)空間之間數(shù)據(jù)的來(lái)回拷貝。注意,內(nèi)核無(wú)論何時(shí)都不能輕率地接受來(lái)自用戶(hù)空間的指針!這兩個(gè)方法中必須有一個(gè)被調(diào)用。為了向用戶(hù)空間寫(xiě)入數(shù)據(jù),內(nèi)核提供了copy_to_user(),它需要三個(gè)參數(shù)。第一個(gè)參數(shù)是進(jìn)程空間中的目的內(nèi)存地址。第二個(gè)是內(nèi)核空間內(nèi)的源地址。最后一個(gè)參數(shù)是需要拷貝的數(shù)據(jù)長(zhǎng)度(字節(jié)數(shù))。

  • 為了從用戶(hù)空間讀取數(shù)據(jù),內(nèi)核提供了copy_from_ user(),它和copy-to-User()相似。該函數(shù)把第二個(gè)參數(shù)指定的位置上的數(shù)據(jù)拷貝到第一個(gè)參數(shù)指定的位置上,拷貝的數(shù)據(jù)長(zhǎng)度由第三個(gè)參數(shù)決定。

  • 如果執(zhí)行失敗,這兩個(gè)函數(shù)返回的都是沒(méi)能完成拷貝的數(shù)據(jù)的字節(jié)數(shù)。如果成功,返回0。當(dāng)出現(xiàn)上述錯(cuò)誤時(shí),系統(tǒng)調(diào)用返回標(biāo)準(zhǔn)-EFAULT。

  • 注意copy_to_user()和copy_from_user()都有可能引起阻塞。當(dāng)包含用戶(hù)數(shù)據(jù)的頁(yè)被換出到硬盤(pán)上而不是在物理內(nèi)存上的時(shí)候,這種情況就會(huì)發(fā)生。此時(shí),進(jìn)程就會(huì)休眠,直到缺頁(yè)處理程序?qū)⒃擁?yè)從硬盤(pán)重新?lián)Q回物理內(nèi)存。

系統(tǒng)調(diào)用的返回值

  • 系統(tǒng)調(diào)用(在Linux中常稱(chēng)作syscalls)通常通過(guò)函數(shù)進(jìn)行調(diào)用。它們通常都需要定義一個(gè)或幾個(gè)參數(shù)(輸入)而且可能產(chǎn)生一些副作用,例如寫(xiě)某個(gè)文件或向給定的指針拷貝數(shù)據(jù)等等。為防止和正常的返回值混淆,系統(tǒng)調(diào)用并不直接返回錯(cuò)誤碼,而是將錯(cuò)誤碼放入一個(gè)名為errno的全局變量中。通常用一個(gè)負(fù)的返回值來(lái)表明錯(cuò)誤。返回一個(gè)0值通常表明成功。如果一個(gè)系統(tǒng)調(diào)用失敗,你可以讀出errno的值來(lái)確定問(wèn)題所在。通過(guò)調(diào)用perror()庫(kù)函數(shù),可以把該變量翻譯成用戶(hù)可以理解的錯(cuò)誤字符串。

  • errno不同數(shù)值所代表的錯(cuò)誤消息定義在errno.h中,你也可以通過(guò)命令”man 3 errno”來(lái)察看它們。需要注意的是,errno的值只在函數(shù)發(fā)生錯(cuò)誤時(shí)設(shè)置,如果函數(shù)不發(fā)生錯(cuò)誤,errno的值就無(wú)定義,并不會(huì)被置為0。另外,在處理errno前最好先把它的值存入另一個(gè)變量,因?yàn)樵阱e(cuò)誤處理過(guò)程中,即使像printf()這樣的函數(shù)出錯(cuò)時(shí)也會(huì)改變errno的值。

  • 當(dāng)然,系統(tǒng)調(diào)用最終具有一種明確的操作。舉例來(lái)說(shuō),如getpid()系統(tǒng)調(diào)用,根據(jù)定義它會(huì)返回當(dāng)前進(jìn)程的PID。內(nèi)核中它的實(shí)現(xiàn)非常簡(jiǎn)單:


asmlinkage long sys_ getpid(void) { ? ?return current-> tgid; }

  • 上述的系統(tǒng)調(diào)用盡管非常簡(jiǎn)單,但我們還是可以從中發(fā)現(xiàn)兩個(gè)特別之處。首先,注意函數(shù)聲明中的asmlinkage限定詞,這是一個(gè)小戲法,用于通知編譯器僅從棧中提取該函數(shù)的參數(shù)。所有的系統(tǒng)調(diào)用都需要這個(gè)限定詞。其次,注意系統(tǒng)調(diào)用get_pid()在內(nèi)核中被定義成sys_ getpid。這是Linux中所有系統(tǒng)調(diào)用都應(yīng)該遵守的命名規(guī)則。

訪(fǎng)問(wèn)系統(tǒng)調(diào)用

系統(tǒng)調(diào)用上下文

  • 內(nèi)核在執(zhí)行系統(tǒng)調(diào)用的時(shí)候處于進(jìn)程上下文。current指針指向當(dāng)前任務(wù),即引發(fā)系統(tǒng)調(diào)用的那個(gè)進(jìn)程。

  • 在進(jìn)程上下文中,內(nèi)核可以休眠并且可以被搶占。這兩點(diǎn)都很重要。首先,能夠休眠說(shuō)明系統(tǒng)調(diào)用可以使用內(nèi)核提供的絕大部分功能。休眠的能力會(huì)給內(nèi)核編程帶來(lái)極大便利。在進(jìn)程上下文中能夠被搶占,其實(shí)表明,像用戶(hù)空間內(nèi)的進(jìn)程一樣,當(dāng)前的進(jìn)程同樣可以被其他進(jìn)程搶占。因?yàn)樾碌倪M(jìn)程可以使用相同的系統(tǒng)調(diào)用,所以必須小心,保證該系統(tǒng)調(diào)用是可重人的。當(dāng)然,這也是在對(duì)稱(chēng)多處理中必須同樣關(guān)心的問(wèn)題。

  • 當(dāng)系統(tǒng)調(diào)用返回的時(shí)候,控制權(quán)仍然在system_call()中,它最終會(huì)負(fù)責(zé)切換到用戶(hù)空間并讓用戶(hù)進(jìn)程繼續(xù)執(zhí)行下去。

系統(tǒng)調(diào)用訪(fǎng)問(wèn)示例

  • 操作系統(tǒng)使用系統(tǒng)調(diào)用表將系統(tǒng)調(diào)用編號(hào)翻譯為特定的系統(tǒng)調(diào)用。系統(tǒng)調(diào)用表包含有實(shí)現(xiàn)每個(gè)系統(tǒng)調(diào)用的函數(shù)的地址。例如,read() 系統(tǒng)調(diào)用函數(shù)名為sys_read。read()系統(tǒng)調(diào)用編號(hào)是 3,所以sys_read() 位于系統(tǒng)調(diào)用表的第四個(gè)條目中(因?yàn)橄到y(tǒng)調(diào)用起始編號(hào)為0)。從地址 sys_call_table + (3 * word_size) 讀取數(shù)據(jù),得到sys_read()的地址。

  • 找到正確的系統(tǒng)調(diào)用地址后,它將控制權(quán)轉(zhuǎn)交給那個(gè)系統(tǒng)調(diào)用。我們來(lái)看定義sys_read()的位置,即fs/read_write.c文件。這個(gè)函數(shù)會(huì)找到關(guān)聯(lián)到 fd 編號(hào)(傳遞給 read() 函數(shù)的)的文件結(jié)構(gòu)體。那個(gè)結(jié)構(gòu)體包含指向用來(lái)讀取特定類(lèi)型文件數(shù)據(jù)的函數(shù)的指針。進(jìn)行一些檢查后,它調(diào)用與文件相關(guān)的 read() 函數(shù),來(lái)真正從文件中讀取數(shù)據(jù)并返回。與文件相關(guān)的函數(shù)是在其他地方定義的 —— 比如套接字代碼、文件系統(tǒng)代碼,或者設(shè)備驅(qū)動(dòng)程序代碼。這是特定內(nèi)核子系統(tǒng)最終與內(nèi)核其他部分協(xié)作的一個(gè)方面。

  • 讀取函數(shù)結(jié)束后,從sys_read()返回,它將控制權(quán)切換給 ret_from_sys。它會(huì)去檢查那些在切換回用戶(hù)空間之前需要完成的任務(wù)。如果沒(méi)有需要做的事情,那么就恢復(fù)用戶(hù)進(jìn)程的狀態(tài),并將控制權(quán)交還給用戶(hù)程序。

從用戶(hù)空間直接訪(fǎng)問(wèn)系統(tǒng)調(diào)用

  • 通常,系統(tǒng)調(diào)用靠C庫(kù)支持。用戶(hù)程序通過(guò)包含標(biāo)準(zhǔn)頭文件并和C庫(kù)鏈接,就可以使用系統(tǒng)調(diào)用(或者調(diào)用庫(kù)函數(shù),再由庫(kù)函數(shù)實(shí)際調(diào)用)。但如果你僅僅寫(xiě)出系統(tǒng)調(diào)用,glibc庫(kù)恐怕并不提供支持。值得慶幸的是,Linux本身提供了一組宏,用于直接對(duì)系統(tǒng)調(diào)用進(jìn)行訪(fǎng)問(wèn)。它會(huì)設(shè)置好寄存器并調(diào)用陷人指令。這些宏是_syscalln(),其中n的范圍從0到6。代表需要傳遞給系統(tǒng)調(diào)用的參數(shù)個(gè)數(shù),這是由于該宏必須了解到底有多少參數(shù)按照什么次序壓入寄存器。舉個(gè)例子,open()系統(tǒng)調(diào)用的定義是:


long open(const char *filename, int flags, int mode)

  • 而不靠庫(kù)支持,直接調(diào)用此系統(tǒng)調(diào)用的宏的形式為:


#define NR_ open 5 syscall3(long, open, const char*,filename, int, flags, int, mode)

  • 這樣,應(yīng)用程序就可以直接使用open()

  • 對(duì)于每個(gè)宏來(lái)說(shuō),都有2+ n個(gè)參數(shù)。

  • 第一個(gè)參數(shù)對(duì)應(yīng)著系統(tǒng)調(diào)用的返回值類(lèi)型。

  • 第二個(gè)參數(shù)是系統(tǒng)調(diào)用的名稱(chēng)。再以后是按照系統(tǒng)調(diào)用參數(shù)的順序排列的每個(gè)參數(shù)的類(lèi)型和名稱(chēng)。

  • _NR_ open在<asm/unistd.h>中定義,是系統(tǒng)調(diào)用號(hào)。該宏會(huì)被擴(kuò)展成為內(nèi)嵌匯編的C函數(shù)。由匯編語(yǔ)言執(zhí)行前一節(jié)所討論的步驟,將系統(tǒng)調(diào)用號(hào)和參數(shù)壓入寄存器并觸發(fā)軟中斷來(lái)陷入內(nèi)核。調(diào)用open()系統(tǒng)調(diào)用直接把上面的宏放置在應(yīng)用程序中就可以了。

  • 讓我們寫(xiě)一個(gè)宏來(lái)使用前面編寫(xiě)的foo()系統(tǒng)調(diào)用,然后再寫(xiě)出測(cè)試代碼炫耀一下我們所做的努力。

添加系統(tǒng)調(diào)用

通過(guò)修改內(nèi)核源代碼添加系統(tǒng)調(diào)用

linux-2.6.*

  • 通過(guò)以上分析linux系統(tǒng)調(diào)用的過(guò)程,

  • 將自己的系統(tǒng)調(diào)用加到內(nèi)核中就是一件容易的事情。下面介紹一個(gè)實(shí)際的系統(tǒng)調(diào)用,

  • 并把它加到內(nèi)核中去。要增加的系統(tǒng)調(diào)用是:inttestsyscall(),其功能是在控制終端屏幕上顯示hello world,

  • 執(zhí)行成功后返回0。

編寫(xiě)int testsyscall()系統(tǒng)調(diào)用–響應(yīng)函數(shù)

  • 編寫(xiě)一個(gè)系統(tǒng)調(diào)用意味著要給內(nèi)核增加1個(gè)函數(shù),將新函數(shù)放入文件kernel/sys.c中。新函數(shù)代碼如下:

添加系統(tǒng)調(diào)用號(hào)

  • 編寫(xiě)了新的系統(tǒng)調(diào)用過(guò)程后,下一項(xiàng)任務(wù)是使內(nèi)核的其余部分知道這一程序的存在,然后重建包含新的系統(tǒng)調(diào)用的內(nèi)核。為了把新的函數(shù)連接到已有的內(nèi)核中去, 需要編輯2個(gè)文件:

1).inculde/asm/unistd.h在這個(gè)文件中加入

系統(tǒng)調(diào)用表中添加對(duì)應(yīng)項(xiàng)

2).are/i386/kernel/entry.s這個(gè)文件用來(lái)對(duì)指針數(shù)組初始化,在這個(gè)文件中增加一行:


  • 將.rept NR_syscalls-190改為NR_SYSCALLS-191,然后重新編譯和運(yùn)行新內(nèi)核。

使用新的系統(tǒng)調(diào)用

  • 在保證的C語(yǔ)言庫(kù)中沒(méi)有新的系統(tǒng)調(diào)用的程序段,必須自己建立其代碼如下

  • 在這里使用了_syscall0宏指令,宏指令本身在程序中將擴(kuò)展成名為syscall()的函數(shù),它在main()函數(shù)內(nèi)部加以調(diào)用。

  • 在testsyscall()函數(shù)中, 預(yù)處理程序產(chǎn)生所有必要的機(jī)器指令代碼,包括用系統(tǒng)調(diào)用參數(shù)值加載相應(yīng)的cpu寄存器, 然后執(zhí)行int 0x80中斷指令。

linux-3.*

  • 在linux-3.8.4/kernel/sys.c 文件末尾添加新的系統(tǒng)調(diào)用函數(shù)如:


在arch/x86/syscall_32.tbl下找到unused 223號(hào)調(diào)用然后替換如:

如果是64位系統(tǒng),在arch/x86/syscalls/syscall_64.tbl下找到313號(hào)系統(tǒng)調(diào)用,然后在其下面加上314號(hào)自己的中斷如: `314 common mycall sys_mycall

利用內(nèi)核模塊添加系統(tǒng)調(diào)用

  • init_module和cleanup_module。

  • 第一個(gè)函數(shù)是在把模塊插入內(nèi)核時(shí)調(diào)用的;

  • 第二個(gè)函數(shù)則在刪除該模塊時(shí)調(diào)用。由于內(nèi)核模塊是內(nèi)核的一部分,所以能訪(fǎng)問(wèn)所有內(nèi)核資源。根據(jù)對(duì)linux系統(tǒng)調(diào)用機(jī)制的分析,

  • 如果要增加系統(tǒng)調(diào)用,可以編寫(xiě)自己的函數(shù)來(lái)實(shí)現(xiàn),然后在sys_call_table表中增加一項(xiàng),使該項(xiàng)中的指針指向自己編寫(xiě)的函數(shù),

  • 就可以實(shí)現(xiàn)系統(tǒng)調(diào)用。下面用該方法實(shí)現(xiàn)在控制終端上打印“hello world” 的系統(tǒng)調(diào)用testsyscall()。

編寫(xiě)系統(tǒng)調(diào)用內(nèi)核模塊

使用新的系統(tǒng)調(diào)用

內(nèi)核Linux系統(tǒng)調(diào)用的列表

  • 以下是Linux系統(tǒng)調(diào)用的一個(gè)列表,包含了大部分常用系統(tǒng)調(diào)用和由系統(tǒng)調(diào)用派生出的的函數(shù)。

進(jìn)程控制


文件系統(tǒng)控制

文件讀寫(xiě)操作


文件系統(tǒng)操作


系統(tǒng)控制


內(nèi)存管理



網(wǎng)絡(luò)管理



socket控制


用戶(hù)管理


進(jìn)程間通信

信號(hào)


消息


管道


信號(hào)量


共享內(nèi)存




全網(wǎng)最詳細(xì)、最值得收藏的,linux內(nèi)核剖析--Linux系統(tǒng)調(diào)用詳解(實(shí)現(xiàn)機(jī)制分析)的評(píng)論 (共 條)

分享到微博請(qǐng)遵守國(guó)家法律
嘉黎县| 孟村| 九寨沟县| 芒康县| 平远县| 肇源县| 三门县| 峡江县| 新竹市| 营口市| 西畴县| 茌平县| 崇州市| 呼图壁县| 泰和县| 鄄城县| 聊城市| 天津市| 乐陵市| 石家庄市| 香河县| 赤壁市| 新邵县| 湟源县| 高邮市| 邹城市| 称多县| 金沙县| 大邑县| 贵阳市| 洱源县| 冀州市| 特克斯县| 东光县| 鲁甸县| 鄢陵县| 龙江县| 恭城| 罗定市| 绥江县| 安岳县|