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

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

萬字深入分析Linux系統(tǒng)調(diào)用與API

2023-02-04 14:54 作者:補給站Linux內(nèi)核  | 我要投稿

一、基本概念解析

我們在很多書籍上、博客上都學(xué)過或者聽說過系統(tǒng)調(diào)用與API這兩個概念,那么這兩個概念究竟是什么意思,它們之間是什么關(guān)系呢?如果我們閱讀過《操作系統(tǒng)導(dǎo)論》,就會明白操作系統(tǒng)的目的與作用,就會知道內(nèi)核是要向進(jìn)程提供服務(wù)的,那么內(nèi)核是如何向進(jìn)程提供服務(wù)的呢?下面我們就來一探究竟。

1.1 系統(tǒng)調(diào)用的來源與作用

我們先來看一下進(jìn)程的虛擬內(nèi)存空間布局,我們以32位為例,64位的邏輯也是一樣的。

可以看到一個進(jìn)程的內(nèi)存空間分為用戶空間和內(nèi)核空間兩部分。每個進(jìn)程都有自己獨立的用戶空間,但是所有進(jìn)程都共享同一個內(nèi)核空間,所以所有進(jìn)程都可以請求內(nèi)核的服務(wù)。不過內(nèi)核空間運行在特權(quán)級,用戶空間運行在非特權(quán)級,所以用戶空間是不能直接訪問內(nèi)核空間的。為此,內(nèi)核向用戶空間提供了有限制的訪問,系統(tǒng)調(diào)用。用戶空間可以通過系統(tǒng)調(diào)用來調(diào)用內(nèi)核里一些特定的函數(shù)。這樣的話,進(jìn)程就可以通過系統(tǒng)調(diào)用來請求內(nèi)核的服務(wù)了。系統(tǒng)調(diào)用是如何實現(xiàn)的呢?這是需要硬件的特殊支持的,第三章節(jié)會講。

1.2 API的來源與作用

既然有了系統(tǒng)調(diào)用,進(jìn)程可以通過系統(tǒng)調(diào)用來請求內(nèi)核的服務(wù),那么為什么還會有API呢?因為系統(tǒng)調(diào)用是偏底層的,有很多細(xì)節(jié)要處理,而且不同的平臺其系統(tǒng)調(diào)用并不相同;就算是同一個平臺,其提供的系統(tǒng)調(diào)用功能以及系統(tǒng)調(diào)用的實現(xiàn)方法都有可能會發(fā)生變化。因此為了屏蔽系統(tǒng)調(diào)用的各種細(xì)節(jié),增加通用性和跨平臺性,操作系統(tǒng)又向用戶進(jìn)程提供了API。API,Application Programming Interface,應(yīng)用程序編程接口,它的意思就是它的字面意思,就是指操作系統(tǒng)向應(yīng)用程序提供的編程接口?,F(xiàn)實中有很多人把API當(dāng)做I(Interface)接口的意思來用,本文所說的API都是指它的本意。有了API你就不用考慮系統(tǒng)調(diào)用了,無論在任何平臺、任何OS,你只管使用API,只要它們的API是相同的,你的源碼就是兼容的、跨平臺的。

1.3 API與系統(tǒng)調(diào)用的關(guān)系

API和系統(tǒng)調(diào)用具體是什么關(guān)系呢?系統(tǒng)調(diào)用是偏底層、偏實現(xiàn)的,API是偏上層、偏接口的。系統(tǒng)調(diào)用是實現(xiàn)在內(nèi)核里的,它的修改只要符合內(nèi)核的規(guī)范、只要內(nèi)核的主要管理者同意就可以。API它首先是行業(yè)標(biāo)準(zhǔn)或者業(yè)內(nèi)標(biāo)準(zhǔn),是不能隨意改變的,一般都有相應(yīng)的標(biāo)準(zhǔn)委員會來制定和發(fā)展API。API的實現(xiàn)是在用戶空間庫里面,一般都是在libc中實現(xiàn)。API的底層實現(xiàn)一般使用的是系統(tǒng)調(diào)用,很多API和系統(tǒng)調(diào)用是一對一關(guān)系。但也有特殊情況,比如有的API并不使用系統(tǒng)調(diào)用,有的系統(tǒng)調(diào)用沒有對應(yīng)的API,有的API可能調(diào)用了多個系統(tǒng)調(diào)用,有的系統(tǒng)調(diào)用可能被多個API使用。也就是說大部分情況下API和系統(tǒng)調(diào)用是1:1的關(guān)系,但有些情況下是1:0、0:1、1:n、n:1、m:n的關(guān)系。當(dāng)API和系統(tǒng)調(diào)用的關(guān)系是1:1,而且它們的名字也相同時,我們不能把它們看做是同一個事物,而應(yīng)當(dāng)把它們看做不同的事物,只不過是名字相同而已,是同名的API使用了同名的系統(tǒng)調(diào)用。就好比有兩種情況,第一種情況是,有兩個人都叫張偉,一個是副市長,一個是公安局局長,張偉副市長安排張偉局長去做某件事情。第二種情況是,有一個人叫張偉,他是副市長兼任公安局局長,張偉副市長兼局長去做某件事情。這兩種情況是不一樣的,同名的API與系統(tǒng)調(diào)用的關(guān)系類似于前者。

下面我們舉例來說明一下API與系統(tǒng)調(diào)用的關(guān)系。我們來寫一個最簡單的hello world程序,代碼如下。

編譯:gcc -o hello hello.c

運行:./hello

會在屏幕上輸出 hello, world。

這個程序非常簡單,我們調(diào)用了兩個API(strlen 和 write),在屏幕上輸出了一行文字。同樣是API,strlen沒有使用系統(tǒng)調(diào)用,自己直接在用戶空間就把功能實現(xiàn)了,而write API則使用了write系統(tǒng)調(diào)用。有些API的功能比較簡單,自己在用戶空間就能實現(xiàn),沒必要麻煩內(nèi)核。但是有些API的功能在用戶空間是不可能實現(xiàn)或者很難實現(xiàn)的,必須要求助于內(nèi)核。我們把write API與write系統(tǒng)調(diào)用畫成圖,如下所示:


API函數(shù)通過系統(tǒng)調(diào)用機(jī)制調(diào)用系統(tǒng)調(diào)用函數(shù)。那么系統(tǒng)調(diào)用機(jī)制要做的事情有哪些呢?有兩件事,一是實現(xiàn)CPU特權(quán)級的轉(zhuǎn)變,把CPU設(shè)置為特權(quán)模式之后才能執(zhí)行內(nèi)核的代碼。二是傳遞系統(tǒng)調(diào)用的編號和函數(shù)參數(shù),系統(tǒng)調(diào)用函數(shù)有很多,怎么知道你想調(diào)用的是哪個系統(tǒng)調(diào)用函數(shù)呢,通過編號來區(qū)分。系統(tǒng)調(diào)用函數(shù)大部分都是有參數(shù)的,所以還需要傳遞參數(shù),參數(shù)怎么傳遞是和具體硬件相關(guān)的,由相應(yīng)的ABI來規(guī)定。


【文章福利】小編推薦自己的Linux內(nèi)核技術(shù)交流群:【749907784】整理了一些個人覺得比較好的學(xué)習(xí)書籍、視頻資料共享在群文件里面,有需要的可以自行添加哦?。。。ê曨l教程、電子書、實戰(zhàn)項目及代碼)? ? ?


1.4 系統(tǒng)調(diào)用機(jī)制的基本原理

那么系統(tǒng)調(diào)用機(jī)制該怎么實現(xiàn)呢?答案是要靠CPU提供的特殊指令(系統(tǒng)調(diào)用指令)來實現(xiàn),雖然不同架構(gòu)的CPU實現(xiàn)不盡相同,但是大概模式都是一樣的,都是往某個寄存器寫入系統(tǒng)調(diào)用編號,在約定的寄存器或者棧上寫入?yún)?shù),然后調(diào)用特殊指令(系統(tǒng)調(diào)用指令),此時CPU就會切換到特權(quán)模式并進(jìn)入內(nèi)核執(zhí)行一段預(yù)先設(shè)定的代碼(系統(tǒng)調(diào)用入口函數(shù)),這段代碼會根據(jù)系統(tǒng)調(diào)用編號調(diào)用相應(yīng)的系統(tǒng)調(diào)用函數(shù)。畫成圖如下所示:

可以看出完成一個系統(tǒng)調(diào)用有兩個關(guān)鍵點,一是系統(tǒng)調(diào)用編號要能對應(yīng)上,二是系統(tǒng)調(diào)用入口函數(shù)要提前設(shè)置好。這樣系統(tǒng)調(diào)用入口函數(shù)才能根據(jù)系統(tǒng)調(diào)用編號找到正確的系統(tǒng)調(diào)用函數(shù)。

需要說明的是,一個平臺提供的系統(tǒng)調(diào)用指令不一定只有一個,不同系統(tǒng)調(diào)用指令對應(yīng)的系統(tǒng)調(diào)用入口函數(shù)也不相同,這個第三章會詳細(xì)講解。

二、API的制定和實現(xiàn)

最開始的時候是沒有操作系統(tǒng)的,后來逐漸產(chǎn)生了操作系統(tǒng)。操作系統(tǒng)對應(yīng)用程序提供的用戶空間接口就叫做API(應(yīng)用程序編程接口)。API剛開始是看著缺啥就添啥,沒有一定的標(biāo)準(zhǔn)。后來隨著操作系統(tǒng)的發(fā)展,再野蠻生長就不行了,于是就有了操作系統(tǒng)API標(biāo)準(zhǔn)規(guī)范。不同的操作系統(tǒng),它們的API并不相同,API的制定與維護(hù)方法也不相同。

2.1 POSIX API

UNIX操作系統(tǒng)家族的API叫做POSIX(Portable Operating System Interface)。POSIX是IEEE制定的規(guī)范,POSIX這個名字是GNU的倡導(dǎo)者Richard Stallman建議的,按照當(dāng)時的命名習(xí)慣在最后加了個X。UNIX還有另外一個規(guī)范叫做Single UNIX Specification,簡稱SUS,是由Open Group發(fā)布的。后來POSIX和SUS合并開發(fā),內(nèi)容一樣,但是對外還是用兩個名字。想要了解POSIX S最新的標(biāo)準(zhǔn),請查看網(wǎng)站https://unix.org/online.html。

Linux本身僅僅是個內(nèi)核,并不是個操作系統(tǒng)。GNU/Linux或者Linux發(fā)行版才是個完整的操作系統(tǒng)。Linux發(fā)行版都遵循POSIX API。網(wǎng)站https://man7.org 和書籍《The Linux Programming Interface》非常全面詳細(xì)地介紹了POSIX API的語義以及它在Linux上的一些實現(xiàn)情況,非常值得大家認(rèn)真學(xué)習(xí)或者經(jīng)常查閱。

2.2 Windows API

Windows的API在16位的時候叫做Windows API。后來到了32位的時候,重新設(shè)計了API,由于16的API和32位的API差別非常大,所以就重新命名為Win32 API。到了64位的時候,API基本沒啥變化,就是把有些參數(shù)從32位提升到了64位,所以64位的Windows的API也依然被人們叫做Win32 API。當(dāng)然64位的API也被Windows命名為Windows API,因為16位Windows早已成為歷史,這么命名也不會引起歧義。現(xiàn)在Windows API和Win32 API幾乎是同義詞,區(qū)別不大。

由于Windows操作系統(tǒng)是微軟一家的閉源產(chǎn)品,所以它的API規(guī)范是由公司制定的。這和POSIX是由標(biāo)準(zhǔn)委員會制定的是不一樣的。

2.3 API的實現(xiàn)

API本身僅僅是個規(guī)范,是個概念性的東西,它具體是怎么實現(xiàn)的呢?目前業(yè)界都是把API放在libc里面來實現(xiàn)的。所以libc里面不僅有C標(biāo)準(zhǔn)庫的實現(xiàn),還有操作系統(tǒng)API的實現(xiàn)。所以大家不能認(rèn)為libc就是一個普通的lib,它的作用是非常重要的,沒有l(wèi)ibc,幾乎所有的進(jìn)程都無法運行,libc是進(jìn)程通向內(nèi)核的必經(jīng)之路。幾乎所有的進(jìn)程都鏈接了libc,大部分都是動態(tài)鏈接的,通過ldd命令可以查到,通過/proc/$pid/maps也可以查到;少部分是靜態(tài)鏈接libc的,是查不到libc.so的,但是程序本身還是包含libc的代碼的。當(dāng)然你也可以自己調(diào)用系統(tǒng)調(diào)用就不用libc,一般只有演示程序會這么做。

Libc在不同操作系統(tǒng)上的實現(xiàn)是不同的,在同一個操作系統(tǒng)也可能有多個不同的實現(xiàn)。Linux發(fā)行版上最流行的libc實現(xiàn)是Glibc,Android上的libc實現(xiàn)是bionic。

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

系統(tǒng)調(diào)用機(jī)制的實現(xiàn)原理都是相同的,但是不同操作系統(tǒng)、不同硬件平臺上的實現(xiàn)細(xì)節(jié)又不盡相同。下面我們分別來講一下Linux在x86平臺和arm平臺上實現(xiàn)細(xì)節(jié)。

3.1 x86平臺的實現(xiàn)

X86平臺的系統(tǒng)調(diào)用的實現(xiàn)方法經(jīng)歷了三代的變遷,每次改變都提高了系統(tǒng)調(diào)用的執(zhí)行效率。第一代系統(tǒng)調(diào)用指令,借用了中斷機(jī)制的指令,int 0x80、iret。第二代系統(tǒng)調(diào)用指令sysenter、sysexit。第三代系統(tǒng)調(diào)用指令syscall、sysret。三代指令在內(nèi)核中的使用情況如下圖所示:

?下面我們分別講一下這三代指令的基本原理。

3.2 指令基本原理

第一代系統(tǒng)調(diào)用指令使用的是中斷指令,基本原理如下。中斷發(fā)生時,CPU會切換到特權(quán)模式并跳到內(nèi)核執(zhí)行預(yù)先指定的一段程序。執(zhí)行哪段程序呢,要根據(jù)中斷源來決定,不同的中斷源執(zhí)行不同的程序,每個中斷源都對應(yīng)一個整數(shù)來標(biāo)識自己,這個整數(shù)就叫做中斷向量。中斷源有三類,外設(shè)中斷、CPU異常、指令中斷,前兩種都有自己的方法來指定中斷向量,指令中斷是在指令的操作數(shù)里面指定中斷向量號的。我們的系統(tǒng)調(diào)用就是利用指令中斷,用向量號0x80,也就是十進(jìn)制的128當(dāng)做自己的中斷向量,來執(zhí)行系統(tǒng)調(diào)用的。我們在用戶空間,先把系統(tǒng)調(diào)用編號賦值給寄存器EAX,然后執(zhí)行int 0x80,CPU就會跳轉(zhuǎn)到內(nèi)核執(zhí)行內(nèi)核預(yù)先設(shè)定的中斷處理程序(也就是系統(tǒng)調(diào)用入口函數(shù))。系統(tǒng)調(diào)用入口函數(shù)根據(jù)EAX的值調(diào)用對應(yīng)的系統(tǒng)調(diào)用函數(shù)。系統(tǒng)調(diào)用函數(shù)執(zhí)行完成之后返回系統(tǒng)調(diào)用入口函數(shù),入口函數(shù)再執(zhí)行iret返回到用戶空間,一個系統(tǒng)調(diào)用就完成了。

第二代系統(tǒng)調(diào)用指令sysenter/sysexit,由于通過中斷流程進(jìn)行系統(tǒng)調(diào)用開銷太大了,很多操作對系統(tǒng)調(diào)用來說又是沒有意義的,因此Intel專門開發(fā)了只用于系統(tǒng)調(diào)用的指令。由于sysenter是專用指令,它可以把很多中斷相關(guān)的操作都省略掉,具體來說有以下幾點,1.不再自動把寄存器信息保存到內(nèi)核棧上,2.不再自動從內(nèi)核棧上加載esp的值,3.不再走中斷處理流程。

使用sysenter指令需要提前設(shè)置一些MSR寄存器,具體來說要做以下一些設(shè)置。把內(nèi)核代碼段的選擇符寫入MSR IA32_SYSENTER_CS,把系統(tǒng)調(diào)用入口函數(shù)寫入MSR IA32_SYSENTER_EIP,內(nèi)核棧段的選擇符要放在緊挨著內(nèi)核棧段的后面,把內(nèi)核棧的地址寫入MSR IA32_SYSENTER_ESP,這樣sysenter執(zhí)行時CPU就會切換到特權(quán)模式,然后執(zhí)行系統(tǒng)調(diào)用入口函數(shù)。在執(zhí)行sysexit之前把要返回到的用戶空間指令的地址寫入EDX,用戶空間棧的值寫入ECX。

sysenter/sysexit指令也可以用于64位模式,但是Linux選擇在64位上只使用syscall/sysret。

第三代系統(tǒng)調(diào)用指令syscall/sysret,是AMD開發(fā)的,它只能用于64位模式,比sysenter/sysexit還要快一些,因為1.它不再保存和恢復(fù)用戶空間RSP,2.它只能用于平坦內(nèi)存,因此省略了分段單元的開銷。

使用syscall/sysret前要提前設(shè)置一些MSR。要在MSR IA32_STAR中設(shè)置內(nèi)核空間和用戶空間的代碼段,其中內(nèi)核空間CS、SS在47:32位,用戶空間CS、SS在63:48位。系統(tǒng)調(diào)用入口函數(shù)的地址要寫人MSR IA32_LSTR。syscall執(zhí)行的時候會把MSR IA32_STAR的47:32位加載到CS和SS,把MSR IA32_LSTR的值加載到RIP。在執(zhí)行sysret之前把要返回到的用戶空間指令的地址寫入RCX,sysret執(zhí)行時會把MSR IA32_STAR的63:48位加載到CS和SS,把RCX加載到RIP。

3.3?系統(tǒng)調(diào)用編號

我們先來解決第一個問題,系統(tǒng)調(diào)用編號是怎么確定的。不同架構(gòu)不同位數(shù)的系統(tǒng),系統(tǒng)調(diào)用編號是不一樣的。如果用戶空間傳遞的系統(tǒng)調(diào)用編號和內(nèi)核里的系統(tǒng)調(diào)用編號對不上,那問題就嚴(yán)重了。Linux內(nèi)核在編譯時會生成一個文件,arch/x86/include/generated/uapi/asm/unistd_64.h,這個文件是生成的,不是本來就有的,這個文件里面有所有系統(tǒng)調(diào)用的編號。在安裝操作系統(tǒng)時或者單獨安裝內(nèi)核和內(nèi)核頭文件時,這個文件會被安裝在/usr/include/asm/unistd_64.h,libc會使用這個文件,這樣用戶空間傳遞的編號和內(nèi)核里面的系統(tǒng)調(diào)用編號就是一致的了。

3.4?系統(tǒng)調(diào)用入口函數(shù)

下面我們來說說系統(tǒng)調(diào)用入口函數(shù)是怎么設(shè)置的。X86_64對于64位的進(jìn)程來說只有一個系統(tǒng)調(diào)用指令,就是syscall,它的入口函數(shù)在linux-src/arch/x86/entry/entry_64.S, 函數(shù)名叫entry_SYSCALL_64。對于32位的進(jìn)程來說有三個系統(tǒng)調(diào)用指令 int 0x80、sysenter、syscall,它們的入口函數(shù)都在 linux-src/arch/x86/entry/entry_64_compat.S,函數(shù)名分別叫做entry_INT80_compat、entry_SYSENTER_compat、entry_SYSCALL_compat。設(shè)置它們的代碼在兩個地方,syscall(64)、syscall(32)、sysenter 這三個設(shè)置在一個地方,在文件linux-src/arch/x86/kernel/cpu/common.c中的函數(shù) syscall_init

從代碼中可以看出只有在64位的情況下才會設(shè)置syscall指令的入口函數(shù),只有在系統(tǒng)兼容32位進(jìn)程(CONFIG_IA32_EMULATION)的情況下才會設(shè)置syscall(32)、sysenter的兼容入口函數(shù)。大部分linux發(fā)行版都支持32位進(jìn)程兼容。


兼容int 0x80的代碼設(shè)置在另外一個地方,因為int 0x80是中斷指令,所以它是在設(shè)置中斷的地方設(shè)置的,具體位置是linux-src/arch/x86/kernel/idt.c中的函數(shù)idt_setup_traps。

從代碼中可以看出,只有系統(tǒng)支持32位進(jìn)程兼容(CONFIG_IA32_EMULATION)才會去設(shè)置entry_INT80_compat。

我們設(shè)置好了這些系統(tǒng)調(diào)用指令的入口函數(shù)之后,當(dāng)用戶空間調(diào)用這些指令的時候就會調(diào)用這些函數(shù)。那么這些函數(shù)又是怎樣去調(diào)用具體對應(yīng)的系統(tǒng)調(diào)用函數(shù)呢?我們以64位進(jìn)程的syscall指令為例來看一看。先看它的入口函數(shù),linux-src/arch/x86/entry/entry_64.S:entry_SYSCALL_64

我們對代碼做了精簡只留下最關(guān)鍵的??梢钥吹胶瘮?shù)先把__USER_DS和__USER_CS都push到了棧上,這是為了執(zhí)行最后面的那條sysretq時可以返回用戶空間把特權(quán)級也轉(zhuǎn)為用戶級。函數(shù)的主體就是調(diào)用函數(shù)do_syscall_64,我們再來看一個這個函數(shù),linux-src/arch/x86/entry/common.c

可以看到do_syscall_64就是調(diào)用do_syscall_x64,do_syscall_x64就是根據(jù)用戶空間傳來的系統(tǒng)調(diào)用編號在sys_call_table數(shù)組中調(diào)用相應(yīng)的函數(shù)。那么這個sys_call_table數(shù)組是怎么來的呢?它是在文件linux-5.15.28/arch/x86/entry/syscall_64.c中定義的,如下:

那么syscalls_64.h的內(nèi)容是什么,它是怎么來的呢?這個文件并不是手寫的,而是在編譯時由腳本生成的,它是根據(jù)文件linux-src/arch/x86/entry/syscalls/syscall_64.tbl 生成的。我們截取一段syscalls_64.h的內(nèi)容如下:

對syscall_64.c進(jìn)行預(yù)編譯之后我們可以發(fā)現(xiàn)sys_call_table數(shù)組的內(nèi)容如下:

也就是說這是由一堆函數(shù)名構(gòu)成的函數(shù)指針數(shù)組,那么這些函數(shù)名是怎么生成的呢?它是由一系列的SYSCALL_DEFINEx宏生成的,x代表函數(shù)的參數(shù)個數(shù)。我們以open系統(tǒng)調(diào)用來講解一下,open系統(tǒng)調(diào)用的實現(xiàn)是在文件linux-src/fs/open.c

我們把宏SYSCALL_DEFINE3展開之后大致可以得到如下的代碼:

可以看出這個宏會生成函數(shù)__x64_sys_open,這個函數(shù)正好是sys_call_table數(shù)組里面的函數(shù)名。__x64_sys_open接受的參數(shù)是一個寄存器集的指針,然后提取寄存器中的值再調(diào)用函數(shù)__se_sys_open,函數(shù)__se_sys_open對參數(shù)進(jìn)行強(qiáng)轉(zhuǎn)再調(diào)用__do_sys_open,這個函數(shù)是最終的函數(shù)。我們可以看到這里面還生成了函數(shù)__ia32_sys_open,這個函數(shù)是32位進(jìn)程兼容的系統(tǒng)調(diào)用所使用的數(shù)組ia32_sys_call_table的成員。


3.5?匯編程序演示

下面我們用匯編語言來試一試執(zhí)行系統(tǒng)調(diào)用,一般情況下我們都不會直接使用系統(tǒng)調(diào)用指令,下面的例子僅僅是為了演示,標(biāo)準(zhǔn)編程中請使用API。

執(zhí)行如下命令,先匯編后鏈接

gcc -c -o hello-syscall64.o hello-syscall64.S
ld -entry _start hello-syscall64.o -o hello-syscall64
然后運行程序
./hello-syscall64
可以看到運行成功,命令行輸出了 Hello from syscall !

下面我們再來演示一下32位進(jìn)程兼容模式的系統(tǒng)調(diào)用,匯編代碼如下:

執(zhí)行如下命令,先匯編后鏈接

gcc -m32 -c -o hello-syscall32.o hello-syscall32.S
ld -melf_i386 -entry _start hello-syscall32.o -o hello-syscall32
然后運行程序
./hello-syscall32
可以看到運行成功,命令行輸出了
Hello from int 0x80 !
Hello from sysenter !

從上面的匯編代碼示例中我們看到了用戶空間是如何調(diào)用系統(tǒng)調(diào)用的,這也正是libc中的做法。我們前面有個內(nèi)容沒有講,那就是執(zhí)行了系統(tǒng)調(diào)用指令,CPU是如何切換到特權(quán)模式的。其實前面的系統(tǒng)調(diào)用入口函數(shù)設(shè)置里面也在相應(yīng)的寄存器里面設(shè)置了__KERNEL_CS,這個會導(dǎo)致CPU切到特權(quán)模式來執(zhí)行。


上述代碼放到了github上:https://github.com/orangeboyye/hello-syscall


3.6?vsyscall與vdso


最剛開始的時候只有一種系統(tǒng)調(diào)用方式int 0x80,這時候libc都是直接使用這個指令。后來有個sysenter系統(tǒng)調(diào)用指令,libc就要考慮系統(tǒng)有沒有sysenter指令,有的話就用sysenter,沒有的話就用int 0x80。但是這對libc來說太難了,因此內(nèi)核想了一個辦法,把內(nèi)核的一個page設(shè)置為用戶空間可訪問的,叫做vsyscall,libc通過這個vsyscall來進(jìn)行系統(tǒng)調(diào)用,就不用有那么復(fù)雜的考慮了。對于內(nèi)核來說,如果CPU支持sysenter并且內(nèi)核自己也支持sysenter,就把vsyscall設(shè)置為sysenter,否則就設(shè)置為int 0x80。這對內(nèi)核來說是一件非常簡單的事。后來人們發(fā)現(xiàn)可以把一些系統(tǒng)調(diào)用的函數(shù)放到vsyscall里面,如果獲取系統(tǒng)時間,這是一個只讀的操作,而且對系統(tǒng)沒有啥影響,放到vsyscall之后,libc就可以直接調(diào)用了,沒有額外的開銷。后來人們又覺得vsyscall的地址在內(nèi)核空間,而且vsyscall沒有一定的格式,這不太好。于是又開發(fā)了vdso,它是so的格式,在進(jìn)程創(chuàng)建的時候映射到進(jìn)程的地址空間,這樣進(jìn)程就可以像使用so一樣使用vdso。再后來,64位的進(jìn)程下只有一個系統(tǒng)調(diào)用指令,vsyscall的最初的作用就沒有了意義,所以64位進(jìn)程下的vsyscall和vdso就沒有了系統(tǒng)調(diào)用指令兼容層的功能,就只剩下了可以直接調(diào)用一些系統(tǒng)調(diào)用函數(shù)的功能。

四、總結(jié)回顧

內(nèi)核為了向用戶空間提供服務(wù),設(shè)計出了系統(tǒng)調(diào)用機(jī)制,系統(tǒng)調(diào)用機(jī)制可以讓用戶空間調(diào)用內(nèi)核里的某些特定的函數(shù)。要實現(xiàn)系統(tǒng)調(diào)用機(jī)制需要有CPU提供的特殊指令才行。由于歷史原因,系統(tǒng)調(diào)用指令在x86平臺上不止有一個。系統(tǒng)調(diào)用指令的作用是把CPU模式切換到特權(quán)模式、讓CPU跳到指定的入口函數(shù)來執(zhí)行,并把用戶空間提供的系統(tǒng)調(diào)用編號和參數(shù)傳遞進(jìn)內(nèi)核。入口函數(shù)根據(jù)系統(tǒng)調(diào)用編號調(diào)用相應(yīng)的函數(shù)并傳遞參數(shù),執(zhí)行完畢后再返回用戶空間。

我們一般情況下并不會直接使用系統(tǒng)調(diào)用,操作系統(tǒng)為我們提供了非常豐富的API,用起來更方便。

原文作者:Linux閱碼場






萬字深入分析Linux系統(tǒng)調(diào)用與API的評論 (共 條)

分享到微博請遵守國家法律
苍溪县| 贡觉县| 广汉市| 肥乡县| 兰考县| 乐至县| 德阳市| 会宁县| 长子县| 富川| 永安市| 和林格尔县| 武穴市| 临澧县| 安溪县| 苏尼特左旗| 石景山区| 黄浦区| 华宁县| 新绛县| 洛川县| 高邑县| 颍上县| 舒兰市| 菏泽市| 新沂市| 新竹县| 得荣县| 永福县| 武穴市| 天祝| 河西区| 奉贤区| 明溪县| 额尔古纳市| 福建省| 南乐县| 茌平县| 博乐市| 菏泽市| 张掖市|