和系統調用打交道的方式:經過庫函數,把系統調用給封裝起來ubuntu
用戶態vs內核態:安全
通常現代CPU都有幾種不一樣的指令執行級別數據結構
在高級別的狀態下,代碼能夠執行特權指令,訪問任意的物理地址,這種CPU執行級別對應着內核態函數
在相應的低級別執行狀態下,代碼的掌控範圍會受到限制,只能在對應級別容許的範圍內活動ui
爲何有權限級別的劃分:爲了防止系統崩潰以及惡意代碼的入侵,經過劃分權限級別來讓系統更穩定spa
舉例:Intel x86 CPU有四種不一樣的執行級別0-3,Linux只使用了其中的0級和3級分別來表示內核態和用戶態操作系統
區分:在Linux中,地址空間是一個顯著的標誌,0xc0000000以上的地址空間只能在內核態下訪問,0x00000000-0xbfffffff的地址空間在兩種狀態下均可以訪問3d
(地址空間指的是邏輯地址而不是物理地址,邏輯地址:進程的地址空間裏邊的)指針
中斷處理是從用戶態進入內核態主要的方式code
系統調用只是一種特殊的中斷
當用戶態切換到內核態時,必須保存用戶態的寄存器上下文
中斷/int指令會在堆棧上保存一些寄存器的值,如:用戶態棧頂地址,當時的狀態字,當時的cs:eip的值
中斷髮生後第一件事:保存現場(進入中斷程序,保存須要用到的寄存器的數據)
SAVE_ALL:把其餘的寄存器的值給push到內核堆棧裏邊去
中斷處理結束前最後一件事:恢復現場(退出中斷程序,恢復保存寄存器的數據)
RESTOTRE_ALL:把用戶態保存的寄存器再popl出來
Iret指令與中斷信號(包括int指令)發生時的CPU作的動做相反
系統調用概述:
系統調用的意義
API和系統調用
應用程序,封裝程序,系統調用處理程序及系統調用服務例程之間的關係
系統調用的三層皮: xyz(API), system_call(中斷向量對應的中斷服務程序), sys_xyz (不一樣種類的服務程序)
系統調用的服務歷程:
系統調用的參數傳遞方法:
系統調用也須要輸入輸出參數,例如
實際的值
用戶態進程地址空間的變量的地址
包含指向用戶態函數的指針的數據結構的地址
使用庫函數API獲取當前系統時間
代碼:
time.c #include <stdio.h> #include <time.h> int main() { time_t tt;//int型數值 struct tm *t; tt = time(NULL); t = localtime(&tt);//強制類型轉換,便於輸出 printf("time:%d:%d:%d:%d:%d:%d:\n",t->tm_year+1960,t->tm_mon,t->tm_mda,t->tm_hour,t->tm_min,t->tm_sec); return 0; }
經過gcc time.c -o time -m32編譯後打印出當前系統的時間
用匯編方式處罰系統調用獲取當前系統時間
代碼:
time_asm.c #include <stdio.h> #include <time.h> int main() { time_t tt;//int型數值 struct tm *t; asm volatile( "mov $0,%%ebx\n\t"//系統調用傳遞第一個參數使用ebx,這裏是null "mov $0xd,%%eax\n\t"//傳遞系統調用號13(16進制即0xd) "int $0x80\n\t" "mov %%eax,$0\n\t"//經過eax這個寄存器返回系統調用值,和普通函數同樣 :"=m"(tt) ); t = localtime(&tt); printf("time:%d:%d:%d:%d:%d:%d:\n",t->tm_year+1960,t->tm_mon,t->tm_mda,t->tm_hour,t->tm_min,t->tm_sec); return 0; }
系統調用返回值使用eax存儲
選擇24號和47號系統調用,分別獲取當前用戶uid(用戶ID)和gid(組ID),即模擬Linux系統「id」命令。
編寫兩段代碼,分別使用庫函數API和C代碼中嵌入彙編代碼,源碼以下:
uidgid.c(使用庫函數API方式):
程序中經過調用getuid()和getgid()函數來獲取當前執行用戶uid和gid
uidgid_asm.c(使用C代碼中嵌入彙編代碼方式):
內嵌彙編代碼版本源碼中將原來兩行經過API函數獲取uid和gid的代碼註釋掉,用匯編代碼替換。
首先將ebx寄存器清零,表示無參數傳入。
而後分別將0x18和0x2f(十進制24和47)賦值給eax寄存器,表示須要調用的系統調用號,24爲getuid,47爲getgid。
執行int 0x80來執行系統調用。
以後eax寄存器保存了返回值,將它分別賦值給輸出uid或gid變量。
完成整個彙編代碼的系統調用。
分別編譯兩個源碼文件:
分別執行系統id命令以及兩個編譯好的程序:
上面的截圖分別表示普通用戶ubuntu和管理員用戶root分別執行系統自帶命令id,庫函數API方式uidgid,內嵌彙編方式uidgid_asm這三種方式運行獲得的結果是同樣的。
經過實驗執行結果可知,程序成功完成了系統調用獲取當前用戶uid和gid的操做,經過內嵌彙編代碼能夠清晰的看出調用系統調用的工做過程。
首先將ebx寄存器清零,表示無參數傳入。
而後分別將0x18和0x2f(十進制24和47)賦值給eax寄存器,表示須要調用的系統調用號,24爲getuid,47爲getgid。
執行int 0x80來執行系統調用。
以後eax寄存器保存了返回值,將它分別賦值給輸出uid或gid變量。
完成整個彙編代碼的系統調用。
在Linux系統中是經過激活0x80中斷來觸發系統調用的,須要調用的系統調用號實現賦值給eax存儲器,若是有傳入參數可賦值給ebx寄存器,若是多於1個則按順序賦值給ebx、ecx、edx、esi、edi、ebp,若是超過6個則經過指針變量指向另外一片堆棧區,若是無參數傳入則賦值爲0。
雖然Intel X86 CPU有4種執行級別0~3,可是在Linux系統中僅使用了0和3級,分別表示內核態和用戶態。
一些涉及底層、硬件、核心的操做必須在內核態下才容許執行,爲操做系統程序和驅動程序專享,普通程序僅能執行在用戶態下。若是普通程序須要涉及內核態的操做,就須要經過系統調用來實現。這樣作的好處是屏蔽平臺相關操做下降了軟件開發難度,加強了系統安全性,使程序具備更好的移植性(Linux系統及其餘Unix系統遵循統一標準,系統調用基本同樣)。