作者:Sandy 原創作品轉載請註明出處
《Linux核心分析》MOOC課程http://mooc.study.163.com/course/USTC-1000029000 ”
實驗環境:c+Linux64位 (32位系統可能結果會不同)
依照學術誠信條款,我保證此回答為本人原創,所有回答中引用的外部材料已經做了出處標記。 一,系統調用的意義
1,使用者態與核心態
現代CPU一般都有幾種不同的指令執行層級;在高執行層級下,代碼可以執行特權指令,訪問任意的物理地址,這種CPU執行層級就對應著核心態;在相應的低層級執行狀態下,代碼的掌控範圍 會受到限制。只能在對應層級允許的範圍內活動;例如: intel x86 CPU有四種不同的執行層級 0-3,Linux只使用了其中的 0 級和 3級分別來表示核心態和使用者態。
CS寄存器的最低兩位表明了當前代碼的特權級;CPU每條指令的讀取都是通過cs:eip這兩個寄存器:cs是程式碼片段選擇寄存器,eip是位移量寄存器;
上述判斷由硬體完成
一般來說在Linux中,地址空間(32位共4G)是一個顯著的標誌:
0xc0000000以上的地址空間只能在核心態下訪問,0x00000000——0xbfffffff的地址空間在兩種狀態之下都可以訪問。
這裡的地址空間指的是邏輯地址二不是物理地址
每一個進程都有一個獨立的地址空間(32位,4GB),在linux中,3G以上是核心空間,3G以下是使用者空間。
中斷處理是從使用者態進入核心態的主要方式;系統調用是一種特殊的中斷
2,系統調用
作業系統為使用者態進程與硬體裝置進行互動提供了一組介面——系統調用,系統調用的意義: 把使用者從底層的硬體編程中解放出來 極大地提高了系統的安全性 使使用者程式具有可移植性 二,API與系統調用
應用編程介面(application program interface, API)和系統調用是不同的。 API只是一個函數定義 系統調用通過非強制中斷向核心發出一個明確的請求
Libc庫定義的一些API引用了封裝常式(wrapper routine,唯一目的就是發布系統調用)。 一般每個系統調用對應一個封裝常式 庫再用這些封裝常式定義出給使用者的API
Libc庫定義的一些API引用了封裝常式(wrapper routine,唯一目的就是發布系統調用) 一般每個系統調用對應一個封裝常式 庫再用這些封裝常式定義出給使用者的API
不是每個API都對應一個特定的系統調用。 API可能直接提供使用者態的服務 一個單獨的API可能調用幾個系統調用 不同的API可能調用了同一個系統調用
傳回值 大部分封裝常式返回一個整數,其值的含義依賴於相應的系統調用 -1在多數情況下表示核心不能滿足進程的請求 三,應用程式、封裝常式、系統調用處理常式、系統調用服務常式之間的關係
當使用者態進程調用了一個系統調用時,CPU切換到核心態並開始執行一個核心功能。 Linux中是通過執行 int $0x80 來執行系統調用的,這條彙編指令產生向量為128的編程異常
傳參:核心實現了不同的系統調用,進程必須指明需要哪個系統調用,這需要傳遞一個名為系統調用號的參數
使用eax寄存器來傳遞系統調用號
系統調用也需要輸入輸入參數,如實際的值、使用者態進程地址空間的變數的地址、指向使用者態函數的指標的資料結構的地址等;system_call是Linux中所有的系統調用的進入點,每個系統調用至少有一個參數,即eax傳遞的系統調用號。
寄存器傳遞參數的限制: 每個參數的長度不能超過寄存器的長度,即32位 在系統調用號(eax)之外,參數的個數不能超過6個(ebx,ecx,edx,esi,edi,ebp)
當參數超過6個時,將某個寄存器當做指標,指向一塊記憶體,此時進入核心態之後可以訪問任意地址空間
應用程式、封裝常式、系統調用處理常式、系統調用服務常式之間的關係:
系統調用執行過程:
1,程式調用庫的封裝函數
2,調用非強制中斷:int $0x80 進入核心
3,在核心中首先執行system_call() 函數,然後根據系統調用號在系統調用表中尋找對應的系統調用服務常式
4,執行該服務常式
5, 執行完畢後,轉入ret_from_sys_call()常式從系統調用返回 四,使用庫函數API和C代碼中嵌入彙編代碼兩種方式使用同一個系統調用
下面使用庫函數API和C代碼中嵌入彙編代碼兩種方式使用同一個系統調用。在這裡選出系統調用號為64的系統調用sys_getppid(),該系統調用用於返回當前進程的父進程進程號數。系統調用列表參見http://codelab.shiyanlou.com/xref/linux-3.18.6/arch/x86/syscalls/syscall_32.tbl
下面是直接使用庫函數API使用系統調用,getppid.c代碼如下:
#include <stdio.h>#include<unistd.h>int main(){ pid_t pid; pid=getppid(); printf("The number of parent process is: %d\n",pid); return 0;}
下面再使用C語言內嵌彙編代碼的方式實現同一個系統調用。getpid_asm.c代碼如下:
#include <stdio.h>#include<unistd.h>int main(){ pid_t pid; asm volatile( "mov $0,%%ebx\n\t" /*ebx用來傳遞參數,getppid(void)的參數是void所以設定為零*/ "mov $0x40,%%eax\n\t" /*eax用來傳遞系統調用號,getppid的系統調用號是64,所以是0x40*/ "int $0x80\n\t" /* 非強制中斷彙編指令,系統進入核心態 */ "mov %%eax,%0\n\t" /*eax保留傳回值,把傳回值放到輸出參數中,即pid變數中*/ :"=m" (pid) /*輸出參數是pid*/ ); printf("The number of parent process is: %d\n",pid); return 0;}
原理:函數getppid()是glibc對系統調用sys_getppid的封裝,用於擷取當前進程的父進程的進程號。sys_getppid系統調用號為64.在使用者態時候,如果使用者調用了getppid(),系統會產生一中斷,進入到了核心態執行sys_getppid。getppid()的功能是返回當前進程的父進程的ID,它本身是不能完成的,必須請求作業系統服務即sys_getppid,讓作業系統把當前進程的ID告訴給getppid()。
實驗截圖:
從結果中可以看出來兩個函數實現了相同的功能。
請參考這些文獻
http://blog.csdn.net/maochengtao/article/details/23598433
https://git.oschina.net/exiahan/LinuxKernelStudy/blob/master/4/asmSCI.md
http://blog.sina.com.cn/s/blog_b35e31b90101cso6.html
http://wenku.baidu.com/link?url=ik5zKB0jpQ3OPzzj-ehB-DqUYEAp-tGTNqAg_HyYaj-6Zp6C9vWY2tFlqIGAH20D9VwEVqoyvzzEIiJUQHndiPifV9oeyGr_A97pEYGl4Wu