小男孩‘自慰网亚洲一区二区,亚洲一级在线播放毛片,亚洲中文字幕av每天更新,黄aⅴ永久免费无码,91成人午夜在线精品,色网站免费在线观看,亚洲欧洲wwwww在线观看

分享

Linux系統(tǒng)調(diào)用全過程詳解

 印度阿三17 2019-07-14

系統(tǒng)調(diào)用(SYSTEM CALL)

OS內(nèi)核中都有一組實(shí)現(xiàn)系統(tǒng)功能的過程,系統(tǒng)調(diào)用就是對(duì)上述過程的調(diào)用。編程人員利用系統(tǒng)調(diào)用,向OS提出服務(wù)請(qǐng)求,由OS代為完成。

一般情況下,進(jìn)程是不能夠存取系統(tǒng)內(nèi)核的。它不能存取內(nèi)核使用的內(nèi)存段,也不能調(diào)用內(nèi)核函數(shù),CPU的硬件結(jié)構(gòu)保證了這一點(diǎn)。只有系統(tǒng)調(diào)用是一個(gè)例外。

統(tǒng)調(diào)用是用戶態(tài)進(jìn)入內(nèi)核態(tài)的唯一入口:一夫當(dāng)關(guān),萬夫莫開。常用系統(tǒng)調(diào)用:

  • 控制硬件:如write/read調(diào)用。

  • 設(shè)置系統(tǒng)狀態(tài)或讀取內(nèi)核數(shù)據(jù)——getpid()、getpriority()、setpriority()、sethostname()

  • 進(jìn)程管理:如 fork()、clone()、execve()、exit()等

優(yōu)點(diǎn) 編程容易,從硬件設(shè)備的低級(jí)編程中解脫出來 提高了系統(tǒng)的安全性,可以先檢查請(qǐng)求的正確性

Int 0x80指令

Linux中實(shí)現(xiàn)系統(tǒng)調(diào)用利用了i386體系結(jié)構(gòu)中的軟件中斷。即調(diào)用了int  $0x80匯編指令。

這條匯編指令將產(chǎn)生向量為128的編程異常,CPU便被切換到內(nèi)核態(tài)執(zhí)行內(nèi)核函數(shù),轉(zhuǎn)到了系統(tǒng)調(diào)用處理程序的入口:system_call()。

int $0x80指令將用戶態(tài)的執(zhí)行模式轉(zhuǎn)變?yōu)閮?nèi)核態(tài),并將控制權(quán)交給系統(tǒng)調(diào)用過程的起點(diǎn)system_call()處理函數(shù)。

system_cal()檢查系統(tǒng)調(diào)用號(hào),該號(hào)碼告訴內(nèi)核進(jìn)程請(qǐng)求哪種服務(wù)。

內(nèi)核進(jìn)程查看系統(tǒng)調(diào)用表(sys_call_table)找到所調(diào)用的內(nèi)核函數(shù)入口地址。

接著調(diào)用相應(yīng)的函數(shù),在返回后做一些系統(tǒng)檢查,最后返回到進(jìn)程。

system_call()函數(shù)

系統(tǒng)調(diào)用和普通函數(shù)調(diào)用

API是用于某種特定目的的函數(shù),供應(yīng)用程序調(diào)用,而系統(tǒng)調(diào)用供應(yīng)用程序直接進(jìn)入系統(tǒng)內(nèi)核。

Linux內(nèi)核提供了一些C語言函數(shù)庫,這些庫對(duì)系統(tǒng)調(diào)用進(jìn)行了一些包裝和擴(kuò)展,因?yàn)檫@些庫函數(shù)與系統(tǒng)調(diào)用的關(guān)系非常緊密,所以習(xí)慣上把這些函數(shù)也稱為系統(tǒng)調(diào)用。

有的API函數(shù)在用戶空間就可以完成工作,如一些用于數(shù)學(xué)計(jì)算的函數(shù),因此不需要使用系統(tǒng)調(diào)用。

有的API函數(shù)可能會(huì)進(jìn)行多次系統(tǒng)調(diào)用。

不同的API 函數(shù)也可能會(huì)有相同的系統(tǒng)調(diào)用。比如malloc(),calloc(),free()等函數(shù)都使用相同的方法分配和釋放內(nèi)存。

系統(tǒng)命令、內(nèi)核函數(shù)

系統(tǒng)調(diào)用與系統(tǒng)命令

  • 系統(tǒng)命令相對(duì)API來說,更高一層。每個(gè)系統(tǒng)命令都是一個(gè)執(zhí)行程序,如ls命令等。這些命令的實(shí)現(xiàn)調(diào)用了系統(tǒng)調(diào)用。

系統(tǒng)調(diào)用與內(nèi)核函數(shù)

  • 系統(tǒng)調(diào)用是用戶進(jìn)入內(nèi)核的接口層,它本身并非內(nèi)核函數(shù),但是它由內(nèi)核函數(shù)實(shí)現(xiàn)。

  • 進(jìn)入內(nèi)核后,不同的系統(tǒng)調(diào)用會(huì)找到各自對(duì)應(yīng)的內(nèi)核函數(shù),這些內(nèi)核函數(shù)被稱為系統(tǒng)調(diào)用的“服務(wù)例程”。如系統(tǒng)調(diào)用getpid實(shí)際調(diào)用的服務(wù)例程為sys_getpid(),或者說系統(tǒng)調(diào)用getpid()是服務(wù)例程sys_getpid()的封裝例程。

封裝例程(wrapper routine)

由于陷入指令是一條特殊指令,依賴操作系統(tǒng)實(shí)現(xiàn)的平臺(tái),如在i386體系結(jié)構(gòu)中,這條指令是int $0x80(陷入指令),不是用戶在編程時(shí)應(yīng)該使用的語句,因?yàn)檫@將使得用戶程序難于移植。

在標(biāo)準(zhǔn)C庫函數(shù)中,為每個(gè)系統(tǒng)調(diào)用設(shè)置了一個(gè)封裝例程,當(dāng)一個(gè)用戶程序執(zhí)行了一個(gè)系統(tǒng)調(diào)用時(shí),就會(huì)調(diào)用到C函數(shù)庫中的相對(duì)應(yīng)的封裝例程。

系統(tǒng)調(diào)用過程

system_call()片段

...
   pushl ?x    /*將系統(tǒng)調(diào)用號(hào)壓棧*/
SAVE_ALL
...
cmpl$(NR_syscalls), ?x    /*檢查系統(tǒng)調(diào)用號(hào)
Jb nobadsys
Movl $(-ENOSYS), 24(%esp)   /*堆棧中的eax設(shè)置為-ENOSYS, 作為返回值
Jmp ret_from_sys_call
nobadsys:

…
call  *sys_call_table(,?x,4) #調(diào)用系統(tǒng)調(diào)用表中調(diào)用號(hào)為eax的系統(tǒng)調(diào)用例程
movl ?x,EAX(%esp) #將返回值存入堆棧中
Jmp ret_from_sys_call

首先將系統(tǒng)調(diào)用號(hào)(eax)和可以用到的所有CPU寄存器保存到相應(yīng)的堆棧中(由SAVE_ALL完成);

對(duì)用戶態(tài)進(jìn)程傳遞過來的系統(tǒng)調(diào)用號(hào)進(jìn)行有效性檢查(eax是系統(tǒng)調(diào)用號(hào),它應(yīng)該小于 NR_syscalls)

如果是合法的系統(tǒng)調(diào)用,再進(jìn)一步檢測(cè)該系統(tǒng)調(diào)用是否正被跟蹤;

根據(jù)eax中的系統(tǒng)調(diào)用號(hào)調(diào)用相應(yīng)的服務(wù)例程。

服務(wù)例程結(jié)束后,從eax寄存器獲得它的返回值,并把這個(gè)返回值存放在堆棧中,讓其位于用戶態(tài)eax寄存器曾存放的位置。

然后跳轉(zhuǎn)到ret_from_sys_call(),終止系統(tǒng)調(diào)用程序的執(zhí)行。

SAVE_ALL宏定義

#define SAVE_ALL  
cld; 
pushl %es;  
pushl %ds; 
pushl ?x;  
pushl ?p;  
pushl ?i;  
pushl %esi;  
pushl ?x; 
pushl ?x; 
pushl ?x; 
movl $(__KERNEL_DS),?x; 
movl ?x,%ds;  
movl ?x,%es;  
  • 將寄存器中的參數(shù)壓入到核心棧中(這樣內(nèi)核才能使用用戶傳入的參數(shù)。)

  • 因?yàn)樵诓煌貦?quán)級(jí)之間控制轉(zhuǎn)換時(shí),INT指令不同于CALL指令,它不會(huì)將外層堆棧的參數(shù)自動(dòng)拷貝到內(nèi)層堆棧中。所以在調(diào)用系統(tǒng)調(diào)用時(shí),必須把參數(shù)指定到各個(gè)寄存器中

系統(tǒng)調(diào)用表與調(diào)用號(hào)

這樣系統(tǒng)調(diào)用處理程序一旦運(yùn)行,就可以從eax中得到系統(tǒng)調(diào)用號(hào),然后再去系統(tǒng)調(diào)用表中尋找相應(yīng)服務(wù)例程。  

一個(gè)應(yīng)用程序調(diào)用fork()封裝例程,那么在執(zhí)行int $0x80之前就把eax寄存器的值置為2(即__NR_fork)。

這個(gè)寄存器的設(shè)置是libc庫中的封裝例程進(jìn)行的,因此用戶一般不關(guān)心系統(tǒng)調(diào)用號(hào)

核心中為每個(gè)系統(tǒng)調(diào)用定義了一個(gè)唯一的編號(hào),這個(gè)編號(hào)的定義在linux/include/asm/unistd.h中(最大為NR_syscall)

同時(shí)在內(nèi)核中保存了一張系統(tǒng)調(diào)用表,該表中保存了系統(tǒng)調(diào)用編號(hào)和其對(duì)應(yīng)的服務(wù)例程地址。第n個(gè)表項(xiàng)包含系統(tǒng)調(diào)用號(hào)為n的服務(wù)例程的地址。

系統(tǒng)調(diào)用陷入內(nèi)核前,需要把系統(tǒng)調(diào)用號(hào)一起傳入內(nèi)核。而該標(biāo)號(hào)實(shí)際上是系統(tǒng)調(diào)用表(  sys_call_table)的下標(biāo) 在i386上,這個(gè)傳遞動(dòng)作是通過在執(zhí)行int  $0x80前把調(diào)用號(hào)裝入eax寄存器實(shí)現(xiàn)。 這樣系統(tǒng)調(diào)用處理程序一旦運(yùn)行,就可以從eax中得到系統(tǒng)調(diào)用號(hào),然后再去系統(tǒng)調(diào)用表中尋找相應(yīng)服務(wù)例程。

系統(tǒng)調(diào)用號(hào)

#define __NR_exit      1
#define __NR_fork      2
#define __NR_read      3
#define __NR_write     4
#define __NR_open      5
#define __NR_close     6
#define __NR_waitpid   7
#define __NR_creat     8
#define __NR_link      9
#define __NR_unlink    10
#define __NR_execve    11
#define __NR_chdir     12
#define __NR_time      13

系統(tǒng)調(diào)用表 (arch/i386/kernel/entry.s)

data
ENTRY(sys_call_table)
.long SYMBOL_NAME(sys_ni_syscall)      
.long SYMBOL_NAME(sys_exit)
.long SYMBOL_NAME(sys_fork)
.long SYMBOL_NAME(sys_read)
.long SYMBOL_NAME(sys_write)
.long SYMBOL_NAME(sys_open)                  
.long SYMBOL_NAME(sys_close)
.long SYMBOL_NAME(sys_waitpid)
.long SYMBOL_NAME(sys_creat)
.long SYMBOL_NAME(sys_link)
.long SYMBOL_NAME(sys_unlink)                
.long SYMBOL_NAME(sys_execve)
.long SYMBOL_NAME(sys_chdir)
.long SYMBOL_NAME(sys_time)
.long SYMBOL_NAME(sys_mknod)
  • 系統(tǒng)調(diào)用表記錄了各個(gè)系統(tǒng)調(diào)用的服務(wù)例程的入口地址。

  • 以系統(tǒng)調(diào)用號(hào)為偏移量能夠在該表中找到對(duì)應(yīng)處理函數(shù)地址。

  • 在linux/include/linux/sys.h中定義的NR_syscalls表示該表能容納的最大系統(tǒng)調(diào)用數(shù),一般NR_syscalls = 256。

系統(tǒng)調(diào)用的返回

當(dāng)服務(wù)例程結(jié)束時(shí),system_call( ) 從eax獲得系統(tǒng)調(diào)用的返回值,并把這個(gè)返回值存放在曾保存用戶態(tài) eax寄存器棧單元的那個(gè)位置上,然后跳轉(zhuǎn)到ret_from_sys_call( ),終止系統(tǒng)調(diào)用處理程序的執(zhí)行。

當(dāng)進(jìn)程恢復(fù)它在用戶態(tài)的執(zhí)行前,RESTORE_ALL宏會(huì)恢復(fù)用戶進(jìn)入內(nèi)核前被保留到堆棧中的寄存器值。其中eax返回時(shí)會(huì)帶回系統(tǒng)調(diào)用的返回碼(負(fù)數(shù)說明調(diào)用錯(cuò)誤,0或正數(shù)說明正常完成)

ret_from_sys_call

cli              # 關(guān)中斷
cmpl $0,need_resched(?x) 
jne    reschedule          #如果進(jìn)程描述符中的 need_resched位不為0,則重新調(diào)度
cmpl   $0,sigpending(?x)
jne      signal_return   #若有未處理完的信號(hào),則處理
restore_all:
RESTORE_ALL     #堆棧彈棧,返回用戶態(tài)

系統(tǒng)調(diào)用的返回值

所有的系統(tǒng)調(diào)用返回一個(gè)整數(shù)值。

  1. 正數(shù)或0表示系統(tǒng)調(diào)用成功結(jié)束

  2. 負(fù)數(shù)表示一個(gè)出錯(cuò)條件

這里的返回值與封裝例程返回值的約定不同

  1. 內(nèi)核沒有設(shè)置或使用errno變量

  2. 封裝例程在系統(tǒng)調(diào)用返回取得返回值之后設(shè)置這個(gè)變量

  3. 當(dāng)系統(tǒng)調(diào)用出錯(cuò)時(shí),返回的那個(gè)負(fù)值將要存放在errno變量中返回給應(yīng)用程序

系統(tǒng)調(diào)用-實(shí)例分析

假設(shè)源文件名為getpid.c,內(nèi)容是:

#include <syscall.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
int main(void)
{
    long ID;
    ID = getpid();
    printf ("getpid()=%ld\n", ID);
    return(0);
}
  1. 該程序調(diào)用封裝例程getpid()。該封裝例程將系統(tǒng)調(diào)用號(hào)_NR_getpid(第20個(gè))壓入EAX寄存器

  2. CPU通過int $0x80 進(jìn)入內(nèi)核,找到system_call(),并調(diào)用它     (以下進(jìn)入內(nèi)核態(tài))

  3. 在內(nèi)核中首先執(zhí)行system_call(),接著執(zhí)行根據(jù)系統(tǒng)調(diào)用號(hào)在調(diào)用表中查找到的對(duì)應(yīng)的系統(tǒng)調(diào)用服務(wù)例程sys_getpid()。

  4. 執(zhí)行sys_getpid()服務(wù)例程。

  5. 執(zhí)行完畢后,轉(zhuǎn)入ret_from_sys_call()例程,系統(tǒng)調(diào)用返回到用戶態(tài)。

系統(tǒng)調(diào)用的參數(shù)傳遞

很多系統(tǒng)調(diào)用需要不止一個(gè)參數(shù)

普通C函數(shù)的參數(shù)傳遞是通過把參數(shù)值寫入堆棧(用戶態(tài)堆?;騼?nèi)核態(tài)堆棧)來實(shí)現(xiàn)的。但因?yàn)橄到y(tǒng)調(diào)用是一種特殊函數(shù),它由用戶態(tài)進(jìn)入了內(nèi)核態(tài),所以既不能使用用戶態(tài)的堆棧也不能直接使用內(nèi)核態(tài)堆棧

在int $0x80匯編指令之前,系統(tǒng)調(diào)用的參數(shù)被寫入CPU的寄存器。然后,在進(jìn)入內(nèi)核態(tài)調(diào)用系統(tǒng)調(diào)用服務(wù)例程之前,內(nèi)核再把存放在CPU寄存器中的參數(shù)拷貝到內(nèi)核態(tài)堆棧中。因?yàn)楫吘狗?wù)例程是C函數(shù),它還是要到堆棧中去尋找參數(shù)的

系統(tǒng)調(diào)用使用寄存器來傳遞參數(shù),要傳遞的參數(shù)有:

  • 系統(tǒng)調(diào)用號(hào)

  • 系統(tǒng)調(diào)用所需的參數(shù)

用于傳遞參數(shù)的寄存器有:

  • eax:用于保存系統(tǒng)調(diào)用號(hào)和系統(tǒng)調(diào)用返回值

  • 系統(tǒng)調(diào)用參數(shù)保存在:ebx,ecx,edx,esi和edi中

進(jìn)入內(nèi)核態(tài)后,system_call通過使用SAVE_ALL宏把這些寄存器的值保存在內(nèi)核態(tài)堆棧中。

用寄存器傳遞參數(shù)必須滿足3個(gè)條件:  

  1. 每個(gè)參數(shù)的長(zhǎng)度不能超過寄存器的長(zhǎng)度  

  2. 參數(shù)的個(gè)數(shù)不能超過6個(gè)(包括eax中傳遞的系統(tǒng)調(diào)用號(hào));否則,需要用一個(gè)單獨(dú)的寄存器指向進(jìn)程地址空間中這些參數(shù)值所在的一個(gè)內(nèi)存區(qū)即可

  3. 返回值必須寫到eax寄存器中

參數(shù)傳遞舉例

處理write系統(tǒng)調(diào)用的sys_write服務(wù)例程聲明如下

該函數(shù)期望在棧頂找到fd,buf和count參數(shù)     在封裝sys_write()的封裝例程中,將會(huì)在ebx、ecx和edx寄存器中分別填入這些參數(shù)的值,然后在進(jìn)入system_call時(shí),SAVE_ALL會(huì)把這些寄存器保存在堆棧中,進(jìn)入sys_write服務(wù)例程后,就可以在相應(yīng)的位置找到這些參數(shù)

asmlinkage使得編譯器不通過寄存器(x=0)而 使用堆棧傳遞參數(shù)

SAVE_ALL

設(shè)C庫中封裝的系統(tǒng)調(diào)用號(hào)為3的函數(shù)原形如下:

   int sys_func(int para1, int para2)
C編譯器產(chǎn)生的匯編偽碼如:
…movl  0x8(%esp),?x
  /*將用戶態(tài)堆棧中的para2放入ecx
Movl 0x4(%esp),?x  
 /*#將用戶態(tài)堆棧中的para1放入ebx
Movl $0x3,?x 
  /*系統(tǒng)調(diào)用號(hào)保存在eax中int$0x80 #引發(fā)系統(tǒng)調(diào)用
…
Movl ?x,errno /*將結(jié)果存入全局變量errno中
Movl $-1,?x   /*eax置為-1,表示出錯(cuò)注

練習(xí):添加一個(gè)系統(tǒng)調(diào)用mysyscall

功能要求  

首先,自定義一個(gè)系統(tǒng)調(diào)用mysyscall ,它的功能是使用戶的uid等于0 。然后,編寫一段測(cè)試程序進(jìn)行調(diào)用。

執(zhí)行步驟如下

  1. 添加系統(tǒng)調(diào)用號(hào)

  2. 在系統(tǒng)調(diào)用表中添加相應(yīng)的表項(xiàng)

  3. 實(shí)現(xiàn)系統(tǒng)調(diào)用服務(wù)例程

  4. 重新編譯內(nèi)核,啟動(dòng)新內(nèi)核

  5. 編寫一段測(cè)試程序檢驗(yàn)實(shí)驗(yàn)結(jié)果

(1)添加系統(tǒng)調(diào)用號(hào):它位于unistd.h,每個(gè)系統(tǒng)調(diào)用號(hào)都以“_NR_開頭”,

  • 系統(tǒng)調(diào)用的編號(hào)命名為  __NR_mysyscall

  • 改寫/usr/include/asm/unistd.h

240 #define __NR_llistxattr        	 	233
241 #define __NR_flistxattr        	 	234
242 #define __NR_removexattr     		235
243 #define __NR_lremovexattr    	  	236
244 #define __NR_fremovexattr    	  	237
245 #define __NR_mysyscall			238

(2)在系統(tǒng)調(diào)用表中添加相應(yīng)的表項(xiàng)

  • 內(nèi)核中實(shí)現(xiàn)該系統(tǒng)調(diào)用的例程的名字    sys_mysyscall

  • 改寫arch/i386/kernel/entry.S

398 ENTRY(sys_call_table)
399         .long SYMBOL_NAME(sys_ni_syscall)
                ……
636         .long SYMBOL_NAME(sys_ni_syscall)       
637         .long SYMBOL_NAME(sys_mysyscall)
638 
639         .rept NR_syscalls-(.-sys_call_table)/4
640                 .long SYMBOL_NAME(sys_ni_syscall)
641         .endr 

3)實(shí)現(xiàn)系統(tǒng)調(diào)用服務(wù)例程   把一小段程序添加在kernel/sys.c

asmlinkage int sys_mysyscall(void)
{
		current->uid = current->euid = current->suid = current->fsuid = 0;
		return 0;
} 

(4)重新編譯內(nèi)核,啟動(dòng)新內(nèi)核

(5)編寫一段測(cè)試程序檢驗(yàn)實(shí)驗(yàn)結(jié)果

#include <linux/unistd.h>
_syscall0(int,mysyscall)/* 注意這里沒有分號(hào) */
int main()
{
    mysyscall();
    printf(“This is my uid: %d. \n”, getuid());
}

_syscall1(int,print_info,int,testflag)

如果要在用戶程序中使用系統(tǒng)調(diào)用函數(shù),那么在主函數(shù)main前必須申明調(diào)用_syscall,其中1 表示該系統(tǒng)調(diào)用只有一個(gè)入口參數(shù),第一個(gè)int 表示系統(tǒng)調(diào)用的返回值為整型,print_info為系統(tǒng)調(diào)用函數(shù)名,第二個(gè)int 表示入口參數(shù)的類型為整型,testflag為入口參數(shù)名。

來源:https://www./content-3-326651.html

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多