精品国产人成在线_亚洲高清无码在线观看_国产在线视频国产永久2021_国产AV综合第一页一个的一区免费影院黑人_最近中文字幕MV高清在线视频

0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

Linux內(nèi)核系統(tǒng)調(diào)用概述及實現(xiàn)原理

Linux內(nèi)核補給站 ? 來源:Linux內(nèi)核補給站 ? 作者:Linux內(nèi)核補給站 ? 2022-05-14 14:11 ? 次閱讀

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

區(qū)別

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

聯(lián)系

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

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

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

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

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

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

基本機制

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

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

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

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

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

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

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

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

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

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

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

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

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

pYYBAGJ_SCKAVc5DAABrZ6IsyGo641.jpg?source=d16d100b

?

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


poYBAGJ_SCOAFN6lAAB-HyEo5eY490.jpg?source=d16d100b

?


pYYBAGJ_SCOATLHdAAA3jPGOX9k645.jpg?source=d16d100b

?


poYBAGJ_SCOAS-gvAABZjV2F9eI212.jpg?source=d16d100b

?


pYYBAGJ_SCSAE7BSAABUH7NUi94029.jpg?source=d16d100b

?

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


poYBAGJ_SCSAcgpXAACIThfI9BY682.jpg?source=d16d100b

?


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

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

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

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

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

pYYBAGJ_SCSAO7R5AACHf2TXLvY762.jpg?source=d16d100b

?

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

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

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

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

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

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

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

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

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

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


ret_from_sys_call

ret_from_sys_call入口的匯編程序段在linux進程管理中起到了十分重要的作用。

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

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

參數(shù)傳遞

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

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

參數(shù)驗證

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

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

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

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

如果是讀,該內(nèi)存應(yīng)被標(biāo)記為可讀。如果是寫,該內(nèi)存應(yīng)被標(biāo)記為可寫。進程決不能繞過內(nèi)存訪問限制。

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

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

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

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

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

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

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

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

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

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

訪問系統(tǒng)調(diào)用

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

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

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

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

系統(tǒng)調(diào)用訪問示例

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

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

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

從用戶空間直接訪問系統(tǒng)調(diào)用

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

long open(const char *filename, int flags, int mode)
而不靠庫支持,直接調(diào)用此系統(tǒng)調(diào)用的宏的形式為:
#define NR_ open 5
syscall3(long, open, const char*,filename, int, flags, int, mode)
這樣,應(yīng)用程序就可以直接使用open()
對于每個宏來說,都有2+ n個參數(shù)。
第一個參數(shù)對應(yīng)著系統(tǒng)調(diào)用的返回值類型。
第二個參數(shù)是系統(tǒng)調(diào)用的名稱。再以后是按照系統(tǒng)調(diào)用參數(shù)的順序排列的每個參數(shù)的類型和名稱。
_NR_ open在中定義,是系統(tǒng)調(diào)用號。該宏會被擴展成為內(nèi)嵌匯編的C函數(shù)。由匯編語言執(zhí)行前一節(jié)所討論的步驟,將系統(tǒng)調(diào)用號和參數(shù)壓入寄存器并觸發(fā)軟中斷來陷入內(nèi)核。調(diào)用open()系統(tǒng)調(diào)用直接把上面的宏放置在應(yīng)用程序中就可以了。
讓我們寫一個宏來使用前面編寫的foo()系統(tǒng)調(diào)用,然后再寫出測試代碼炫耀一下我們所做的努力。
#define NR foo 283
_sysca110(long, foo)
int main()
{
long stack size;
stack_ size=foo();
printf("The kernel stack
size is 81d/n",stack_ size);
return;
}

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

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

linux-2.6.*

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

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

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

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

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

編寫一個系統(tǒng)調(diào)用意味著要給內(nèi)核增加1個函數(shù),將新函數(shù)放入文件kernel/sys.c中。新函數(shù)代碼如下:
asmlingkage sys_testsyscall()
{ 
    print("hello world\n");    
    return 0;
 }

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

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

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

#define_NR_testsyscall 191

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

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

.long SYMBOL_NAME(_sys_tsetsycall)

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

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

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

#inculde _syscall0(int,testsyscall) main() { tsetsyscall(); }

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

testsyscall()函數(shù)中, 預(yù)處理程序產(chǎn)生所有必要的機器指令代碼,包括用系統(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ù)如:

asmlinkage int sys_mycall(int number) { printk("這是我添加的第一個系統(tǒng)調(diào)用"); return number; }

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

223 i386 mycall sys_mycall

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

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

模塊是內(nèi)核的一部分,但是并沒有被編譯到內(nèi)核里面去。它們被分別編譯并連接成一組目標(biāo)文件, 這些文件能被插入到正在運行的內(nèi)核,或者從正在運行的內(nèi)核中移走。內(nèi)核模塊至少必須有2個函數(shù):

init_modulecleanup_module

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

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

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

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

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

#inculde(linux/kernel.h)

#inculde(linux/module.h)

#inculde(linux/modversions.h)

#inculde(linux/sched.h)

 #inculde(asm/uaccess.h)

#define_NR_testsyscall 191

extern viod *sys_call+table[];

asmlinkage int testsyscall()

{ 
    printf("hello world\n");

    return 0;

}

int init_module()

{ 
    sys_call_table[_NR_tsetsyscall]=testsyscall;
    printf("system call testsyscall() loaded success\n");

    return 0;
}

void cleanup_module()
{

}

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

#define_NR_testsyscall 191

_syscall0(int,testsyscall)

main()
{
    testsyscall();
}

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

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

進程控制

poYBAGJ_SCWARutdAAHfbOb0t68474.jpg?source=d16d100b

?

文件系統(tǒng)控制

文件讀寫操作

pYYBAGJ_SCWAa_sIAAA2eZp8KhM439.jpg?source=d16d100b

?

文件系統(tǒng)操作

poYBAGJ_SCWAESP_AADua0XZVRk228.jpg?source=d16d100b

?

系統(tǒng)控制

pYYBAGJ_SCWAQ37NAAF2NAcqKSE611.jpg?source=d16d100b

?

內(nèi)存管理

poYBAGJ_SCaADjNNAAAsWC8n5a8320.jpg?source=d16d100b

?

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

pYYBAGJ_SCaAfykMAAAWJ1sID0I703.jpg?source=d16d100b

?

socket控制

poYBAGJ_SCaAXF7PAAA9YiRsGKo006.jpg?source=d16d100b

?

用戶管理

pYYBAGJ_SCaAEiwdAAA3r8ttkeY236.jpg?source=d16d100b

?

進程間通信

poYBAGJ_SCaAN-rMAAALHjeoKJg730.jpg?source=d16d100b

?

信號

pYYBAGJ_SCeAU-aZAAA90fMeA0w782.jpg?source=d16d100b

?

消息

poYBAGJ_SCeAJsyHAAAOwScTWus783.jpg?source=d16d100b

?

管道

pYYBAGJ_SCeAB2sjAAAJIKCF_1w103.jpg?source=d16d100b

?

信號量

poYBAGJ_SCeAO6q4AAANQI9Dkzs383.jpg?source=d16d100b

共享內(nèi)存

poYBAGJ_SCeAAtvVAAAOcivyHs8284.jpg?source=d16d100b

?

審核編輯:湯梓紅

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴
  • 內(nèi)核
    +關(guān)注

    關(guān)注

    3

    文章

    1363

    瀏覽量

    40228
  • Linux
    +關(guān)注

    關(guān)注

    87

    文章

    11225

    瀏覽量

    208920
  • 系統(tǒng)調(diào)用

    關(guān)注

    0

    文章

    28

    瀏覽量

    8320
收藏 人收藏

    評論

    相關(guān)推薦

    Linux系統(tǒng)調(diào)用實現(xiàn)與應(yīng)用

    在計算機科學(xué)中,系統(tǒng)調(diào)用(System Call)是一種操作系統(tǒng)提供的服務(wù),它允許應(yīng)用程序通過軟件中斷的方式訪問操作系統(tǒng)內(nèi)核中的函數(shù)。這些函
    發(fā)表于 06-14 11:46 ?510次閱讀

    Linux內(nèi)核系統(tǒng)調(diào)用詳解

    Linux內(nèi)核中設(shè)置了一組用于實現(xiàn)各種系統(tǒng)功能的子程序,稱為系統(tǒng)調(diào)用。用戶可以通過
    發(fā)表于 08-23 10:37 ?763次閱讀
    <b class='flag-5'>Linux</b><b class='flag-5'>內(nèi)核</b>中<b class='flag-5'>系統(tǒng)</b><b class='flag-5'>調(diào)用</b>詳解

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

    Linux內(nèi)核系統(tǒng)調(diào)用1. 應(yīng)用程序通過API而不是直接調(diào)用系統(tǒng)
    發(fā)表于 02-21 10:49

    ARM linux系統(tǒng)調(diào)用實現(xiàn)原理

    大家都知道linux的應(yīng)用程序要想訪問內(nèi)核必須使用系統(tǒng)調(diào)用從而實現(xiàn)從usr模式轉(zhuǎn)到svc模式。下面咱們看看它的
    發(fā)表于 05-30 11:24 ?2232次閱讀

    Linux內(nèi)核系統(tǒng)調(diào)用擴展研究

    系統(tǒng)凋用是操作系統(tǒng)內(nèi)核提供給用戶使用內(nèi)核服務(wù)的接口。LinuX操作系統(tǒng)由于其自由開放性,用戶可在
    發(fā)表于 07-25 16:09 ?40次下載
    <b class='flag-5'>Linux</b><b class='flag-5'>內(nèi)核</b><b class='flag-5'>系統(tǒng)</b><b class='flag-5'>調(diào)用</b>擴展研究

    編譯Linux2.6內(nèi)核并添加一個系統(tǒng)調(diào)用

    本文以實例來詳細描述了從準(zhǔn)備一直到使用新內(nèi)核Linux2.6 內(nèi)核編譯過程,然后介紹了添加系統(tǒng)調(diào)用
    發(fā)表于 12-01 15:54 ?46次下載

    基于linux系統(tǒng)實現(xiàn)的vivado調(diào)用VCS仿真教程

    linux系統(tǒng)實現(xiàn)vivado調(diào)用VCS仿真教程 作用:vivado調(diào)用VCS仿真可以加快工程的仿真和調(diào)試,提高效率。 前期準(zhǔn)備:確認安
    的頭像 發(fā)表于 07-05 03:30 ?1.1w次閱讀
    基于<b class='flag-5'>linux</b><b class='flag-5'>系統(tǒng)</b><b class='flag-5'>實現(xiàn)</b>的vivado<b class='flag-5'>調(diào)用</b>VCS仿真教程

    透了解系統(tǒng)調(diào)用助你成為Linux下編程高手

    Linux內(nèi)核中設(shè)置了一組用于實現(xiàn)各種系統(tǒng)功能的子程序,稱為系統(tǒng)調(diào)用。用戶可以通過
    的頭像 發(fā)表于 05-11 11:27 ?3414次閱讀
    透了解<b class='flag-5'>系統(tǒng)</b><b class='flag-5'>調(diào)用</b>助你成為<b class='flag-5'>Linux</b>下編程高手

    Linux系統(tǒng)調(diào)用的技巧

    函數(shù)則在刪除該模塊時調(diào)用。由于內(nèi)核模塊是內(nèi)核的一部分,所以能訪問所有內(nèi)核資源。根據(jù)對linux系統(tǒng)
    發(fā)表于 04-02 14:36 ?383次閱讀

    以源代碼為例,講解ARM Linux系統(tǒng)調(diào)用實現(xiàn)原理

    大家都知道linux的應(yīng)用程序要想訪問內(nèi)核必須使用系統(tǒng)調(diào)用從而實現(xiàn)從usr模式轉(zhuǎn)到svc模式。下面咱們看看它的
    發(fā)表于 08-12 10:42 ?1053次閱讀

    系統(tǒng)調(diào)用是如何實現(xiàn)的?

    這張圖畫了挺久的,主要是想讓大家可以從全局角度,看下linux內(nèi)核系統(tǒng)調(diào)用實現(xiàn)。 在講具體的細節(jié)之前,我們先根據(jù)上圖,從整體上看一下
    的頭像 發(fā)表于 02-20 16:46 ?3951次閱讀
    <b class='flag-5'>系統(tǒng)</b><b class='flag-5'>調(diào)用</b>是如何<b class='flag-5'>實現(xiàn)</b>的?

    關(guān)于Linux內(nèi)核系統(tǒng)調(diào)用是如何實現(xiàn)的與結(jié)果

    在執(zhí)行 entry_SYSCALL_64 函數(shù)時,內(nèi)核代碼會根據(jù)約定,先從rax寄存器中獲取想要執(zhí)行的系統(tǒng)調(diào)用的編號,然后根據(jù)該編號從sys_call_table數(shù)組中找到對應(yīng)的系統(tǒng)
    的頭像 發(fā)表于 03-19 10:52 ?1545次閱讀

    如何區(qū)分xenomai、linux系統(tǒng)調(diào)用/服務(wù)

    對于同一個POSIX接口應(yīng)用程序,可能既需要xenomai內(nèi)核提供服務(wù)(xenomai 系統(tǒng)調(diào)用),又需要調(diào)用linux
    的頭像 發(fā)表于 05-10 10:28 ?2007次閱讀

    Linux系統(tǒng)調(diào)用的具體實現(xiàn)原理

    文我將基于 ARM 體系結(jié)構(gòu)角度,從 Linux 應(yīng)用層例子到內(nèi)核系統(tǒng)調(diào)用函數(shù)的整個過程來梳理一遍,講清楚linux
    的頭像 發(fā)表于 09-05 17:16 ?1064次閱讀
    <b class='flag-5'>Linux</b><b class='flag-5'>系統(tǒng)</b><b class='flag-5'>調(diào)用</b>的具體<b class='flag-5'>實現(xiàn)</b>原理

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

    控制。也就是說操作系統(tǒng)是使用這些資源的唯一入口,而這個入口就是操作系統(tǒng)提供的系統(tǒng)調(diào)用(System Call)。在linux
    的頭像 發(fā)表于 11-09 10:27 ?514次閱讀
    <b class='flag-5'>Linux</b><b class='flag-5'>系統(tǒng)</b><b class='flag-5'>調(diào)用</b><b class='flag-5'>概述</b>