日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 运维知识 > linux >内容正文

linux

linux系统调用理解之摘录(2)

發(fā)布時間:2025/3/21 linux 39 豆豆
生活随笔 收集整理的這篇文章主要介紹了 linux系统调用理解之摘录(2) 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

原文博客?http://blog.csdn.net/gatieme/article/details/50779184


Linux系統(tǒng)調(diào)用的實現(xiàn)機制分析

本文介紹了系統(tǒng)調(diào)用的一些細(xì)節(jié)。

首先,分析了系統(tǒng)調(diào)用的意義,他們與庫函數(shù)和應(yīng)用程序接口的關(guān)系。

然后,我們分析內(nèi)核如何實現(xiàn)系統(tǒng)調(diào)用,以及執(zhí)行系統(tǒng)調(diào)用的連鎖反應(yīng):

陷入內(nèi)核——>傳遞系統(tǒng)調(diào)用號和外部輸入?yún)?shù)——>執(zhí)行對應(yīng)的系統(tǒng)調(diào)用函數(shù)——>把返回值帶回用戶空間。

最后,分析如何增加系統(tǒng)調(diào)用,并提供從用戶空間訪問系統(tǒng)調(diào)用的例子;


1.系統(tǒng)調(diào)用過的意義

Linux內(nèi)核中設(shè)置了一組用于實現(xiàn)系統(tǒng)服務(wù)的子程序,這些程序成為系統(tǒng)調(diào)用(程序)。注意:請自行根據(jù)上下文理解“系統(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)核空間,也不能調(diào)用內(nèi)核函數(shù)。這是由CPU硬件決定的(這就是為什么它被稱為“保護模式”)。為了和用戶空間上的進程進行交互,內(nèi)核提供了一組接口,即系統(tǒng)調(diào)用。通過接口,應(yīng)用程序可以訪問硬件設(shè)備和其他操作系統(tǒng)資源。

系統(tǒng)調(diào)用相當(dāng)于在用戶空間和硬件設(shè)備之間添加了一個中間層。它的主要作用有三個:

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

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

(3)每個進程都運行在虛擬系統(tǒng)中,而在用戶空間和系統(tǒng)的其他部分之間增加一層公共接口,也是出于這種考慮。如果應(yīng)用程序可以隨意訪問硬件而內(nèi)核又對此一無所知的話,那就沒法實現(xiàn)多任務(wù)和虛擬內(nèi)存。

(迷糊??)

在Linux中,系統(tǒng)調(diào)用時用戶空間訪問內(nèi)核的唯一手段;除異常和中斷外,系統(tǒng)調(diào)用時內(nèi)核唯一的合法入口。


2.API/POSIX/C庫的關(guān)系

一般情況下,應(yīng)用程序通過應(yīng)用程序接口(API)而不是使用syscall來實現(xiàn)系統(tǒng)調(diào)用。

這點很重要,因為應(yīng)用程序使用API接口實際上并不需要和內(nèi)核提供的系統(tǒng)調(diào)用一一對應(yīng)。一個API可以通過一個系統(tǒng)調(diào)用實現(xiàn),也可以通過使用多個系統(tǒng)調(diào)用來實現(xiàn),甚至不適用任何系統(tǒng)調(diào)用也是可以的。實際上,API可以在各種不同的操作系統(tǒng)上實現(xiàn),給應(yīng)用程序提供完全一樣的接口,但是在不同系統(tǒng)上,他們的內(nèi)部實現(xiàn)可能是不同的(比如通過 ifdef 來區(qū)分)。

在UNIX中,最流行的API是基于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標(biāo)準(zhǔn)定義的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)應(yīng)用層的庫函數(shù)和系統(tǒng)調(diào)用封裝函數(shù)。所有的C程序員都可以使用C庫。

從程序員的角度看,系統(tǒng)調(diào)用無關(guān)緊要,他們只需要和API打交道。相反,內(nèi)核只跟系統(tǒng)調(diào)用打交道;

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

(不明覺厲。。。)

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

您或許疑惑:“當(dāng)輸入cat proc/CPUinfo時,cupinfo()函數(shù)怎么如何被調(diào)用的?”

實際上,內(nèi)核在完成引導(dǎo)后,控制流就從相對之間的“接下來調(diào)用哪個函數(shù)?”改變成為“等待模式”:等待系統(tǒng)調(diào)用、異常和中斷。

用戶空間的程序無法直接執(zhí)行內(nèi)核代碼,而是以某種方式通知系統(tǒng),告訴內(nèi)核自己需要執(zhí)行一個系統(tǒng)調(diào)用,希望系統(tǒng)切換到內(nèi)核態(tài),并執(zhí)行那里的異常處理程序。

通知內(nèi)核的機制是靠軟中斷實現(xiàn)的。過程如下:

首先,用戶程序設(shè)置系統(tǒng)調(diào)用號和外部輸入?yún)?shù);

然后,應(yīng)用程序執(zhí)行“系統(tǒng)調(diào)用”指令(特殊的機器指令,在x86上是:“INT $0x80”,)。

在x86上,這個指令:產(chǎn)生一個編號為0x80的編程異常,這個編程異常對應(yīng)的是中斷描述符表IDT中的第128項——也就是對應(yīng)的系統(tǒng)門描述符。門描述符中含有一個預(yù)設(shè)的內(nèi)核空間地址,它指向了系統(tǒng)調(diào)用處理程序:system_call()(別和系統(tǒng)調(diào)用服務(wù)程序混淆,這個程序在entry.S文件中用匯編語言編寫)。

system_call()的主要作用:

a、保存程序的現(xiàn)有狀態(tài),即進程在用戶態(tài)下的CPU主要寄存器的值(所以叫軟中斷)(???有問題)

b、根據(jù)系統(tǒng)調(diào)用號計算出應(yīng)該使用哪一種系統(tǒng)調(diào)用,內(nèi)核進程查看系統(tǒng)調(diào)用表sys_call_table找到對應(yīng)的系統(tǒng)調(diào)用服務(wù)例程的入口地址;

c、轉(zhuǎn)到對應(yīng)的系統(tǒng)調(diào)用服務(wù)例程,并進一步調(diào)用執(zhí)行內(nèi)核中的相關(guān)功能函數(shù);

d、上述系統(tǒng)服務(wù)例程執(zhí)行完成后,返回系統(tǒng)調(diào)用返回值。

e、恢復(fù)用戶程序狀態(tài),將控制權(quán)交給應(yīng)用程序。

(注意:bcd沒有問題,ae的表述有問題。。)


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

在linux中,每一個系統(tǒng)調(diào)用都會被賦予一個系統(tǒng)調(diào)用號。

同時,Linux有一個“未實現(xiàn)”系統(tǒng)調(diào)用sysy_ni_syscall(),它除了返回ENOSYS外,不做任何工作,這個錯誤號就是專門為無效的系統(tǒng)調(diào)用設(shè)定的。

內(nèi)核中所有已經(jīng)注冊過的系統(tǒng)調(diào)用都會保存在sys_call_table表中。一般在entry.s中定義。

sys_call_table是一張由指向?qū)崿F(xiàn)各種系統(tǒng)調(diào)用的系統(tǒng)服務(wù)例程的函數(shù)指針組成的表。

ENTRY(sys_call_table)

.long SYMBOL_NAME(sys_ni_syscall) /* 0 - old "setup()" system call*/

.long SYMBOL_NAME(sys_exit)

.long SYMBOL_NAME(sys_fork)

.long SYMBOL_NAME(sys_read)

.long SYMBOL_NAME(sys_write)

.long SYMBOL_NAME(sys_open) /* 5 */

.long SYMBOL_NAME(sys_close)

.long SYMBOL_NAME(sys_waitpid)

。。。。。

.long SYMBOL_NAME(sys_capget)

.long SYMBOL_NAME(sys_capset)     ?/* 185 */

.long SYMBOL_NAME(sys_sigaltstack)

.long SYMBOL_NAME(sys_sendfile)

.long SYMBOL_NAME(sys_ni_syscall) /* streams1 */

.long SYMBOL_NAME(sys_ni_syscall) /* streams2 */

.long SYMBOL_NAME(sys_vfork)      /* 190 */

(還是不明白,這里面SYMBOL_NAME作用是?sys_vfork的宏定義是??)

system_call()函數(shù)通過將給定的系統(tǒng)調(diào)用好與NR-syscall作比較來檢查器有效性。如果它大于或者等于NR syscalls,該函數(shù)就返回一ENOSYS。否則,就執(zhí)行相應(yīng)的系統(tǒng)調(diào)用。

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

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


3.3????參數(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, esi和edi按照順序存放前五個參數(shù)。需要六個或六個以上參數(shù)的情況不多見,此時,應(yīng)該用一個單獨的寄存器存放指向所有這些參數(shù)在用戶空間地址的指針。

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

?


3.4????參數(shù)驗證

系統(tǒng)調(diào)用必須仔細(xì)檢查它們所有的參數(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)存訪問限制。


3.5 內(nèi)核空間與用戶空間之間數(shù)據(jù)的傳遞

內(nèi)核提供了2種方法來實現(xiàn)用戶空間和內(nèi)核空間之間數(shù)據(jù)的來回拷貝。

(1)向用戶空間寫入數(shù)據(jù):copy_to_user()函數(shù)

(2)從用戶空間讀數(shù)據(jù):copy_from_user()函數(shù)

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



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

系統(tǒng)調(diào)用(在Linux中常稱作syscalls)通常通過函數(shù)進行調(diào)用。它們通常都需要定義一個或幾個參數(shù)(輸入)而且可能產(chǎn)生一些副作用,例如寫某個文件或向給定的指針拷貝數(shù)據(jù)等等。為防止和正常的返回值混淆,系統(tǒng)調(diào)用并不直接返回錯誤碼,而是將錯誤碼放入一個名為errno的全局變量中。通常用一個負(fù)的返回值來表明錯誤。返回一個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ī)則


4.添加新的系統(tǒng)調(diào)用

給Linux添加一個新的系統(tǒng)調(diào)用是相對容易的工作。怎么設(shè)計和實現(xiàn)一個系統(tǒng)調(diào)用是難題所在,而把它添加進內(nèi)核的過程比較簡單。

在添加一個系統(tǒng)調(diào)用是我們需要考慮幾個問題:

(1)明確系統(tǒng)調(diào)用的用途。

注意:Linux不提倡采用多用途的系統(tǒng)調(diào)用(一個系統(tǒng)調(diào)用通過傳遞不同的參數(shù)值來選擇不同類別的功能),不要讓一個系統(tǒng)調(diào)用太復(fù)雜!

但是,這里有一個反例,ioctl()系統(tǒng)調(diào)用(可以查看詳細(xì)教程https://blog.csdn.net/zifehng/article/details/59576539)

(2)確定系統(tǒng)調(diào)用的參數(shù),返回值和錯誤碼。

系統(tǒng)調(diào)用的接口應(yīng)該盡量簡潔,設(shè)計越通用約好。這個系統(tǒng)調(diào)用可移植嗎?別對機器的字節(jié)長度和字節(jié)序做假設(shè)。當(dāng)你寫一個系統(tǒng)調(diào)用的時候,要時刻注意可移植性和健壯性,不但要考慮當(dāng)前,還要為將來做打算。


當(dāng)編譯完一個系統(tǒng)調(diào)用后,把它注冊成一個正式的系統(tǒng)調(diào)用是一件瑣碎的工作,有如下:

(1)在系統(tǒng)調(diào)用表的最后添加一項。每種支持該系統(tǒng)調(diào)用的硬件體系都必須做這樣的工作。從0開始算起,系統(tǒng)調(diào)用在該表中的位置就是它的系統(tǒng)調(diào)用號。(這一點非常重要,在表中并不會出現(xiàn)具體的數(shù)值號)

(2)對于各種體系結(jié)構(gòu),系統(tǒng)調(diào)用號必須定義在<asm/unistd.h>中

(3)系統(tǒng)調(diào)用必須編譯進內(nèi)核映像中(不能編譯成模塊)可以通過把它放進kernel/下的一個相關(guān)文件中就可以。或是自己定義一個文件,并被包含編譯(這樣比較麻煩)。

以下:

我們通過虛構(gòu)一個系統(tǒng)調(diào)用f00()來觀察一下這些步驟。

(1)首先,將sys_f00加入系統(tǒng)調(diào)用表中,對于大多數(shù)體系結(jié)構(gòu)來說,sys_call_table表位于entry.s文件中,形式如下:

ENTRY(sys_call_table)

.long SYMBOL_NAME(sys_ni_syscall) /* 0 - old "setup()" system call*/

.long SYMBOL_NAME(sys_exit)

.long SYMBOL_NAME(sys_fork)

.long SYMBOL_NAME(sys_read)

.long SYMBOL_NAME(sys_write)

.long SYMBOL_NAME(sys_open) /* 5 */

......

我們將新的系統(tǒng)調(diào)用添加在表的尾行:

.long SYMBOL_NAME(sys_f00)

雖然,這里沒有明確指明系統(tǒng)調(diào)用號,但我們加入的這個系統(tǒng)調(diào)用被按照次序分配給了283這個系統(tǒng)調(diào)用號!

對于每種需要支持的體系結(jié)構(gòu),我們必須將自己的系統(tǒng)調(diào)用添加到其sys_call_table中。(說明表不止一個,每種體系都有一個)

(2)將自己的系統(tǒng)調(diào)用號加入<asm/unistd.h>中。

它的格式如下:

/*本文件包含系統(tǒng)調(diào)用號*/

#define __NR_read ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 0
__SYSCALL(__NR_read, sys_read)
#define __NR_write ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?1
__SYSCALL(__NR_write, sys_write)
#define __NR_open ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 2
__SYSCALL(__NR_open, sys_open)
#define __NR_close ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?3
__SYSCALL(__NR_close, sys_close)

..................

然后,我們再該列表的加入自己的系統(tǒng)調(diào)用號

#define? __NR_f00? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 283

(3)f00系統(tǒng)調(diào)用的函數(shù)實現(xiàn)。

因為f00系統(tǒng)調(diào)用要被編譯進內(nèi)核映像,因此我們將它寫進 kernel/sys.c 文件中。

asmlinkage long sys_f00(void)

{

return 1;

}


這樣嚴(yán)格來說,現(xiàn)在就可以在用戶空間調(diào)用f00()系統(tǒng)調(diào)用了。

?

?

總結(jié)

以上是生活随笔為你收集整理的linux系统调用理解之摘录(2)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。