Linux下简单的系统调用
楊金龍 + 原創作品轉載請注明出處 + 《Linux內核分析》MOOC課程http://mooc.study.163.com/course/USTC-1000029000
??本周是Linux系統分析課程的第四周課程,本周主要講Linux系統調用的過程,具體知識點和實驗結果總結如下。
系統調用的相關知識
系統調用:系統調用只是一個特殊的中斷。我們通過庫函數和系統調用打交道,庫函數把系統調用封裝起來。
1、儲備知識——內核態和用戶態
內核態:在高執行級別下,代碼可以執行特權指令,訪問任意的物理內存,這種CPU執行級別就對應著內核態。
用戶態:在用戶態級別下,代碼的掌控范圍會受到限制,只能在對應級別允許的范圍內活動。
注:Intel x86CPU有四種不同的執行級別0-3,Linux只使用了其中的0級和3級分別來表示內核態和用戶態。
2、為什么有權限級別的劃分?
若沒有用戶態和內核態的劃分,用戶寫的不健壯的程序就可以執行特權指令時,就很容易是系統崩潰。操作系統發展過程中劃分了用戶態和內核態,是系統更穩定的機制。
3、寄存器在系統調用中的作用
Cs寄存器的最低兩位表明了當前代碼的特權級。CPU每條指令的讀取都是通過CS:EIP這兩個寄存器:其中CS是代碼段選擇寄存器,EIP是偏移量寄存器。
4、內存地址空間
一般在Linux中,地址空間是一個顯著的標志:0xc0000000以上的地址空間只能在內核態下訪問,0x0000000–0xbfffffff的地址空間在兩種狀態下都可以訪問。
注:產生中斷是從用戶態進入內核態的主要方式。
5、寄存器上下文:
5.1從用戶態切換到內核態時:
必須保存用戶態的寄存器上下文;
同時把內核態的寄存器的值放到寄存器中。
5.2中斷/int指令會在堆棧上保存一些寄存器的值:
如用戶態棧頂地址;
當前的狀態字;
當時的CS:EIP的值。
5.3中斷發生后的第一件事就是保存現場:
保存現場:就是進入中斷程序,保存需要用到的寄存器的數據。
5.4中斷處理結束前最后一件事就是恢復現場:
恢復現場:就是退出中斷程序恢復保存寄存器的數據。
注:Iret指令和中斷信號(包括int指令)發生時的CPU做的動作整好相反。
中斷處理的完整過程
如下圖所示
中斷指令interrupt(ex:int 0x80)開始進行系統調用;
保存當前CS:EIP,SS:ESP,eflags的值到內核堆棧,同時加載了中斷服務程序的地址到CS:EIP以及內核堆棧棧頂指針到SS:ESP中。Int指令完成上述操作過程。
內核代碼,完成中斷服務:
發生進程調度,則保存調度時的現場,進行調度,完成調度后再恢復現場;
不發生進程調度,則恢復之前的保存現場:iret - pop cs:EIP/SS:ESP/eflags from kernel stack.
系統調用的意義
1、操作系統為用戶態進程與硬件設備進行交互提供了一組接口——系統調用。
把用戶從底層的硬件編程中解放出來;
極大的提高了系統的安全性;
使用戶程序具有可移植性。
2、操作系統提供的API和系統調用的關系。
應用編程接口(Application program interface,API)和系統調用時不同的;
API只是一個函數定義;
系統調用通過軟中斷向內核發出一個明確的請求。
Libc庫定義的一些API引用了封裝例程(wrapper routine,唯一的目的就是發布系統調用):一般每個系統調用對應一個封裝例程。庫再用這些封裝例程定義給出用戶的API。
3、不是每個API都對應一個特定的系統調用:
API可能直接提供用戶態的服務;
一個單獨的API可能調用幾個系統調用;
不同的API可能調用了同一個系統調用。
4、返回值
大部分封裝例程返回一個整數,其值的含義依賴于相應的系統調用;
-1在多數情況下表示內核不能滿足進程的請求;
Libc中定義的errno變量包含特定的出錯碼。
注:系統調用的三層皮:API,中斷向量,中斷服務程序
5、系統調用的服務例程:
5.1 當用戶態進程調用一個系統調用時,CPU切換內核態并開始執行一個內核函數。
在Linux中是通過執行int $0x80來執行系統調用的,這條匯編指令產生向量為128的編程異常;
Inter Pentium II中引入了sysenter指令(快速系統調用),2,6已經支持。
5.2 傳參:
內核實現了很多不同的系統調用;
進程必須指明需要哪個系統調用,這需要使用EAX寄存器傳遞一個名為系統調用號的參數
5.3 系統調用也需要輸入輸出參數,例如:
實際的值;
用戶態進程地址空間的變量的地址;
甚至包含指向用戶態函數的指針的數據結構的地址。
System call是Linux中所有系統調用的入口點,每個系統調用至少有一個參數,即由eax
5.3 傳遞的系統調用號;
系統調用號將xyz和sys_xyz關聯起來;用eax寄存器來傳遞參數;
一個應用程序調用fork()封裝例程,那么在執行int $0x80之前就把EAX寄存器的值置為2(即 NR fork);
這個寄存器的設置是libc庫中的封裝例程進行的,因此用戶一般不關心系統調用號;
進入sys,call之后,立即將EAX的值壓入內核堆棧;
5.4 寄存器參數具有如下限制:
每個參數的長度不能超過寄存器的長度,即32位;
在系統調用號(EAX)之外,參數的個數不能超過6個(ebx,ecx,edx,esi,edi,ebp)。
實驗代碼
實驗過程中調用mkdir系統函數,mkdir調用號為39,函數原型如下:
int mkdir(const char *path, mode_t mode);
參數:
path是目錄名
mode是目錄權限
返回值:
返回0 表示成功, 返回 -1表示錯誤,并且會設置errno值。
通過C代碼調用
mkdir.c
#include <stdio.h> #include <sys/stat.h> #include <sys/types.h>int main() {int ret = 0;ret = mkdir("./test", 0777);if(ret == 0){printf("Mkdir success!\n") ; }else printf("Mkdir failed!\n");return 0; }通過嵌入式匯編調用
mkdir_asm.c
#include <stdio.h>int main() {int ret = 0;char *dir = "./test_asm";int mode = 0777;asm volatile("movl $39, %%eax\n\t""int $0x80\n\t""movl %%eax, %0\n\t": "=m"(ret): "b"(dir), "c"(mode) );if(ret == 0)printf("Mkdir through asm success!\n");else printf("Mkdir through asm failed!\n");return 0; }實驗總結
通過對系統調用的兩種代碼實現方法的分析,我們可以知道C語言的API只不過是對Linux底層系統調用的一次封裝而已,本質上是通過系統中斷實現的。
總結
以上是生活随笔為你收集整理的Linux下简单的系统调用的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: mysql 默认sql mode_MyS
- 下一篇: linux 找不到swap分区,Linu