我們來(lái)聊聊系統(tǒng)調(diào)用與普通的函數(shù)調(diào)用之間的區(qū)別。
作為程序員你肯定寫過(guò)無(wú)數(shù)的函數(shù),假設(shè)有這樣兩個(gè)函數(shù):
void funcB() {
}
void funcA() {
funcB();
}
函數(shù)之間是可以相互調(diào)用的,這很簡(jiǎn)單很happy有沒(méi)有。
要知道是代碼、是函數(shù)就可以相互調(diào)用,不管你用什么語(yǔ)言寫的。
假設(shè)funcB是內(nèi)核中的函數(shù),funcA是你自己寫的函數(shù),就像這樣:
// Linux內(nèi)核中的函數(shù)
void funcB() {
}
// 你的函數(shù)
void funcA() {
funcB();
}
那么funcA應(yīng)該也能調(diào)用funcB(如果funcB可以供外界調(diào)用的話)。
有的同學(xué)可能會(huì)驚呼,我們可以自己編寫代碼調(diào)用操作系統(tǒng)的函數(shù),那豈不是可以直接控制操作系統(tǒng)了?
too yong too simple!
如果我們編寫的代碼可以直接調(diào)用所有的操作系統(tǒng)函數(shù)那么從某種程度上講的確可以說(shuō)是能控制操作系統(tǒng),但如果操作系統(tǒng)只允許你調(diào)用內(nèi)核中的有限的幾個(gè)函數(shù)呢?
怎么樣,你(應(yīng)用程序)是不是就被限制住了。
你又會(huì)問(wèn),操作系統(tǒng)是怎樣限制應(yīng)用程序能調(diào)用哪些內(nèi)核中的函數(shù)呢?
實(shí)際上單靠操作系統(tǒng)這種軟件是沒(méi)有辦法限制應(yīng)用程序能調(diào)用哪些以及多少個(gè)內(nèi)核函數(shù)的,因此為施加這種限制必須依靠——硬件。
這里的硬件指的就是CPU。
那么CPU又是怎么施加這種限制的呢?
我們先來(lái)看看普通的函數(shù)調(diào)用,函數(shù)調(diào)用對(duì)應(yīng)的機(jī)器指令是call指令,就像這樣:
call 0x400410
call指令后的這個(gè)地址0x400410就是被調(diào)函數(shù)的第一條機(jī)器指令所在的內(nèi)存地址。
當(dāng)CPU執(zhí)行到這條機(jī)器指令時(shí)直接跳轉(zhuǎn)到對(duì)應(yīng)的地址繼續(xù)執(zhí)行指令,從程序員的角度看就是函數(shù)調(diào)用。
而如果是我們程序的函數(shù)調(diào)用操作系統(tǒng)的函數(shù)就不允許使用call指令了,而是syscall機(jī)器指令(x86_64)。
使用syscall指令調(diào)用操作系統(tǒng)函數(shù)時(shí)也是把相應(yīng)函數(shù)的第一條指令的地址放到syscall之后嗎?
顯然不是的,因?yàn)椴僮飨到y(tǒng)系統(tǒng)代碼和你的代碼都是單獨(dú)編譯以及運(yùn)行的,你根本就不知道操作系統(tǒng)的某個(gè)函數(shù)存放在內(nèi)存的什么位置上,也不應(yīng)該讓你知道,因此使用syscall調(diào)用操作系統(tǒng)的函數(shù)時(shí)我們只能附加一個(gè)序號(hào),比如序號(hào)0對(duì)應(yīng)操作系統(tǒng)中的A函數(shù)、序號(hào)1對(duì)應(yīng)操作系統(tǒng)中的B函數(shù)等等,這樣使用syscall指令時(shí)只需要將該序號(hào)寫入rax寄存器即可,CPU在執(zhí)行syscall指令時(shí)通過(guò)讀取rax寄存器的值就能知道到底該調(diào)用操作系統(tǒng)中的哪個(gè)函數(shù)了。
可以看到,利用這種機(jī)制操作系統(tǒng)限制了應(yīng)用程序可以調(diào)用哪些內(nèi)核中的函數(shù)。
有的同學(xué)可能會(huì)有疑問(wèn),如果一個(gè)call指令因?yàn)榉N種原因后面跟上的地址”無(wú)意“中指向了一個(gè)內(nèi)核函數(shù)的地址,那么CPU執(zhí)行call指令時(shí)會(huì)怎樣呢?就像這樣:
call 0x400410
這里假設(shè)0x400410這個(gè)地址指向了一個(gè)內(nèi)核函數(shù)地址。
很簡(jiǎn)單,CPU在執(zhí)行這條指令時(shí)會(huì)判斷出當(dāng)前進(jìn)程沒(méi)有權(quán)限訪問(wèn)0x400410這個(gè)地址,因此CPU在執(zhí)行這條指令時(shí)會(huì)產(chǎn)生異常,該進(jìn)程會(huì)被直接kill掉。
這里列舉了Linux在各種處理器上怎樣進(jìn)行系統(tǒng)調(diào)用。
看到了吧,syscall和call在使用方法上還是有很大不同的,可以看到call是直接調(diào)用的,也就是說(shuō)應(yīng)用程序這一層中的函數(shù)調(diào)用是直接調(diào)用的,而syscall其實(shí)是間接調(diào)用的,即我們調(diào)用操作系統(tǒng)中的函數(shù)時(shí)其實(shí)是間接調(diào)用的。
除此之外,CPU在執(zhí)行call指令以及syscall指令時(shí)另外一個(gè)不同點(diǎn)在于模式的切換。
當(dāng)CPU執(zhí)行普通函數(shù)時(shí)其實(shí)是運(yùn)行在用戶態(tài),user mode,在這種模式下CPU不能執(zhí)行某些特權(quán)指令,這也就意味著我們的程序其實(shí)是受限的;而當(dāng)CPU執(zhí)行syscall開始執(zhí)行操作系統(tǒng)的代碼時(shí)會(huì)切換到內(nèi)核態(tài),kernel mode,在這種模式下CPU可以執(zhí)行任何特權(quán)指令,不受任何限制,操作系統(tǒng)才是真正的管理計(jì)算機(jī)的大boss。
可以看到,當(dāng)在普通程序中進(jìn)行函數(shù)調(diào)用時(shí)就是函數(shù)調(diào)用,而普通函數(shù)調(diào)用操作系統(tǒng)中的函數(shù)時(shí)才叫系統(tǒng)調(diào)用。
最后再說(shuō)一點(diǎn),普通的函數(shù)調(diào)用所使用的棧全部位于進(jìn)程的棧區(qū),假設(shè)main函數(shù)調(diào)用funcA函數(shù),funcA調(diào)用funcB函數(shù),那么此時(shí)的進(jìn)程內(nèi)存布局就像這樣:
而進(jìn)行系統(tǒng)調(diào)用時(shí)當(dāng)CPU開始執(zhí)行操作系統(tǒng)的代碼時(shí)不再基于進(jìn)程棧區(qū)而是會(huì)跳轉(zhuǎn)到操作系統(tǒng)某個(gè)特定內(nèi)存區(qū)域,該區(qū)域作為進(jìn)程在內(nèi)核中的棧區(qū),因此也叫做內(nèi)核棧,每個(gè)進(jìn)程在內(nèi)核中都有自己的內(nèi)核棧,因此我們可以看到一個(gè)進(jìn)程其實(shí)有兩個(gè)棧區(qū),一個(gè)在用戶態(tài)一個(gè)在內(nèi)核態(tài)。
假設(shè)main函數(shù)調(diào)用funcA,funcA進(jìn)行系統(tǒng)調(diào)用,調(diào)用內(nèi)核中的funcB函數(shù),funcB函數(shù)調(diào)用內(nèi)核中的funcC函數(shù),那么此時(shí)的內(nèi)存布局就像這樣:
好啦,這個(gè)話題就到這里,希望對(duì)大家理解操作系統(tǒng)有所幫助。
-
函數(shù)
+關(guān)注
關(guān)注
3文章
4277瀏覽量
62323 -
代碼
+關(guān)注
關(guān)注
30文章
4722瀏覽量
68231 -
系統(tǒng)調(diào)用
+關(guān)注
關(guān)注
0文章
28瀏覽量
8317
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論