《程序員的自我修養》讀書筆記——系統調用、API

這一篇介紹一下系統調用,熟悉一下流程。很多做客戶端的同學根本不知道這些內容。建議花時間看看相關的知識。最好的方式還是去看源碼,反匯編,才能深刻的理解。

系統調用

程序運行的時候,本身是沒有權限訪問多少系統資源的。系統資源有限,如果操作系統不進行控制,那么各個程序難免會產生沖突。線程操作系統都將可能產生沖突的系統資源保護起來,阻止程序直接訪問。比如文件、網絡、IO、各種設備等。

比如無論在Windows還是Linux中,程序員都不能直接去訪問硬盤的某扇區上的數據,必須通過文件系統,也不能擅自修改任意文件。所有這些操作必須經過操作系統規定的方式進行。比如用fopen打開沒有權限的文件就會失敗。

比如:想要程序延遲執行一段時間,不借助操作系統就是使用循環,這樣會白白消耗CPU,造成資源浪費。如果使用操作系統提供的定時器就可以方便有效。

系統調用涵蓋的功能很廣,有程序運行鎖必須的支持,如創建和退出進程、線程,進程內存管理,對系統資源的訪問等。

Linux系統調用

在x86下,系統調用是通過0x80中斷完成,各個通用寄存器用于傳遞參數。EAX寄存器用于表示系統調用的接口號。

比如:EAX=1表示退出進程,EAX=2表示創建進程,EAX=3表示讀文件或IO,EAX=4表示寫文件或IO。每個系統調用對應到內核源碼中的一個函數,他們都是以sys_開頭的,比如exit調用對應內核中的sys_exit函數

Linux內核提供了幾百個系統調用,下面列舉部分


所以完全可以不使用glibc封裝的fopen、fread、fclose操作文件,而直接使用系統函數open,read,close實現。

技巧:Linux中可以使用man查看系統調用詳情,使用參數2表示系統調用手冊(比如 man 2 read)

如果直接使系統調用會有非常多的問題:

  • 使用不方便,操作系統提供的系統調用接口往往過于原始。程序員需要了解很多跟操作系統相關的細節
  • 各個操作系統之間系統調用不兼容

于是增加一個層來解決,系統調用與程序之間增加一個抽象層。這個層就是前面所說的glibc,或者API.

系統調用原理

這里單單以Linux為例,至于Windows調用原理暫時省略。

用戶態、內核態及中斷

現代操作系統中有兩種特權級別,分為用戶模式和內核模式。

多個模式存在,那么操作系統就可以讓不同代碼運行在不同模式下,進而限制代碼的權限,提高穩定性、安全性。一般普通程序在用戶態,操作會受到限制。系統調用運行在內核態,應用程序基本都是運行在用戶態被限制。

用戶態的程序通過中斷來運行內核態的代碼,進而從用戶態切換到內核態。

中斷

中斷是操作系統的一個概念。中斷是一個硬件或者軟件發出的請求,要求CPU暫停當前的工作轉手去執行更加重要的事情。

比如在編輯文本的時候,敲擊鍵盤那CPU如何得知道呢?一種是輪詢,CPU每隔一小段時間就去詢問鍵盤是否有鍵按下,但是除非一直打字,否則大部分輪訓都是得到沒有鍵按下的結果。這樣就白白浪費掉了很多資源。

另一種方式就是當鍵盤按下的時候,鍵盤芯片發一個信號給CPU,然后CPU接收到信號之后就只到鍵盤按下了。然后再去詢鍵盤具體是哪個鍵被按下,這樣的信號就是一種中斷。——硬件中斷

中斷一般有兩個屬性,一個是中斷號(從0開始),一個稱為中斷處理程序(ISR),不同中斷具有不同的中斷號,一個中斷號對應一個中斷處理程序。在內核中保存了一個數組,叫做中斷向量表,這個數組第n項包含了指向第n個中斷號的中斷處理程序的指針。當中斷來的時候,CPU就會暫停當前執行的代碼,根據中斷的中斷號,在中斷向量表中找到對應的中斷處理程序,并且調用他,處理完之后,CPU繼續執行之前的代碼。

中斷有硬件中斷,這種來至于硬件的異常或者其他時間的發生比如斷電,鍵盤按下。另一種是軟件中斷,軟件中斷一般是一條指令(i386下是int),帶有一個參數記錄中斷號,這個指令用戶可以手動觸發某個中斷并執行中斷處理程序。比如int 0x80這條指令就會調用第0x80號的中斷處理程序。

中斷號是有限的,不可能每一個系統調用都對應一個中斷號,更加合理的是用一個或少數幾個中斷號來對應所有的系統滴啊用。比如Linux中int 0x80來觸發所有的系統調用。

系統調用會有一個系統調用號,表明是哪一個系統調用,這個系統調用通常就是系統調用在系統調用表中的位置。比如Linux中的fork函數調用號是2,這個系統調用號在執行int指令前就會被放到某個固定的寄存器中,對應的中斷代碼會取得這個系統調用號,并且調用正確的函數。

比如Linux中int 0x80為例,系統調用號使用eax來傳入,用戶將系統調用號保存在eax,然后使用int 0x80調用中斷,中斷服務傳給信號就可以從eax取得系統調用號,進行調用相應的函數。

基于int的Linux經典系統調用

下面以fork為例

觸發中斷

首先當程序在代碼里面調用一個系統調用時,是以一個函數的形式調用的,比如fork:

int main() {
 fork();
}

fork函數是對系統調用fork的封裝,可以用下面的宏定義:

_syscall0(pid_t, fork)

_syscall0是一個宏,用于定義一個沒有參數的系統調用封裝。他的第一個參數為這個系統調用的返回值類型,pid_t代表進程的id。第二個參數是系統調用的名字。_syscall0展開之后 會形成一個與系統調用名稱同名的函數。下面是i386的syscall0

匯編解釋:

  • __asm__是一個gcc關鍵字,表示接下來要嵌入匯編代碼,volatile關鍵字告訴GCC對這段代碼不進行任何優化
  • __asm__第一個參數是一個字符串,代表匯編代碼的文本,這里匯編代煤制油int $0x80,表示要調用0x80號中斷
  • =a(__res)表示用eax(a表示eax)輸出返回數據并存儲到_res中
  • 0(_NR ##name)表示用_NR ##name為輸入,0指示由編譯器選擇和輸出相同的寄存器(eax)來傳遞參數。

__syscal_return是另外一個宏

最終fork函數匯編之后


當系統調用如果有參數的話會用syscall1


相比syscall0多了個b,它表示把arg1強制轉換為long,然后保存在ebx最為輸入。

所以如果系統內調用如果有1個參數,則參數通過ebx來傳遞。x86下的linux系統調用參數最多有6個。分別使用6個寄存器來傳遞。分別是ebx,ecx,ed想,esi,edi和ebp。

當進行系統調用的時候,CPU執行到int $0x80時,會保存現場以便恢復。接著切換到內核態,然后CPU查找中斷向量表低0x80元素。

切換堆棧

在實際執行中斷向量表中的第0x80好中斷之前,CPU還要進行堆棧的奇幻,在Linx中,用戶態和內核態使用的是不同的棧,兩者各自負責各自的函數調用,互不干擾。

在執行0x80中斷的時候,程序從用戶態切換到內核態。這時相應的棧也必須切換到內核棧,從中斷處理函數中返回時,程序當前棧需要從內核棧切換到用戶棧。

當前棧是指ESP值所在的棧空間,如果ESP的值位于用戶棧范圍能,那么程序的當前棧就是用戶棧。內核棧同理。并且SS的值需要指向當前棧所在的頁。所以用戶棧切換到內核棧就是:

  • 保存當前ESP、SS的值
  • 將ESP、SS的值設置為內核棧的值

(反過來同理)

  • 恢復原理ESP、SS的值
  • 用戶態的ESP和SS的值保存在內核棧上,

當發生中斷的時候,CPU除了進入內核態之外還會做如下事情:

  • 找到當前進程的內核棧(每一個進程都有一個內核棧)
  • 在內核棧中一猜壓入用戶態的寄存器SS、ESP、EFLAGS、CS、EIP

當內核從系統調用中返回,則調動iret指令回到用戶態,iret指令會從內核棧里面彈出SS、ESP、EFLAGS、CS、EIP的值。使得棧恢復到用戶態的狀態。


中斷處理程序

在int指令切換了棧之后,程序就切換到中斷向量表轉給你記錄0x80號中斷處理陳旭。下面是linux i386中斷服務流程

內核的系統調用函數往往以sys_加上系統調用函數名了,比如sys_fork,sys_open等。

關于sys開頭的系統內調用函數如何從用戶那里獲得參數的。是通過寄存實現的。我們知道用戶調用系統調用時,根據系統電泳參數數量不同,一次將參數放入EBX,EXC,EDX,ESI,EBP這6個寄存器。如果一個參數的系統調用就是EBX,兩個參數的系統調用就是EBX和ECX

小結

通過閱讀,歸根結底還是要懂匯編并且去看源碼才能把整個過程分析正確。平時所使用的把所有的細節都已經屏蔽了。地下還深藏著許多玄機。——而這一點確實大都數開發人員的短板

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,786評論 6 534
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,656評論 3 419
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,697評論 0 379
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,098評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,855評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,254評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,322評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,473評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,014評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,833評論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,016評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,568評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,273評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,680評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,946評論 1 288
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,730評論 3 393
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,006評論 2 374

推薦閱讀更多精彩內容