系统调用 (转)

转自:http://www.cnblogs.com/forlina/archive/2011/09/13/2174841.html

1、内核与系统调用基础

对于应用程序进程来说,操作系统内核的作用体现在一组可供调用的函数,称为系统调用(也成"系统服务")。

从程序运行的角度来看,进程是主动、活性的,是发出调用请求的一方;而内核是被动的,只是应进程要求而提供服务。从整个系统运行角度看,内核也有活性的一面,具体体现在进程调度。

系统调用所提供的服务(函数)是运行在内核中的,也就是说,在"系统空间"中。而应用软件则都在用户空间中,二者之间有着空间的间隔(CPU运行模式不同)。

综上所述,应用软件若想进行系统调用,则应用层和内核层之间,必须存在"系统调用接口",即一组接口函数,这组接口运行于用户空间。对于windows来说,其系统调用接口并不公开,公开是的一组对系统调用接口的封装函数,称为windowsAPI。

2、用户空间中的进程如何进行系统调用?

用户空间与系统空间所在的内存区间不一样,同样,对于这两种区间,CPU的运行状态也不一样。

在用户空间中,CPU处于"用户态";在系统空间中,CPU处于"系统态"。

CPU从系统态进入用户态是容易的,因为可以执行一些系统态特有的特权指令,从而进入用户态。

而相反,用户态进入系统态则不容易,因为用户态是无法执行特权指令的。

所以,一般有三种手段,使CPU进入系统态(即转入系统空间执行):

  ①中断:来自于外部设备的中断请求。当有中断请求到来时,CPU自动进入系统态,并从某个预定地址开始执行指令。中断只发生在两条指令之间,不影响正在执行的指令。

  ②异常:无论是在用户空间或系统空间,执行指令失败时都会引起异常,CPU会因此进入系统态(如果原先不在系统空间),从而在系统空间中对异常做出处理。异常发生在执行一条指令的过程中,所以当前执行的指令已经半途而废了。

  ③自陷:以上两种都CPU被动进入系统态。而自陷是CPU通过自陷指令主动进入系统态。多数CPU都有自陷指令,系统调用函数一般都是靠自陷指令实现的。一条自陷指令的作用相当于一次子程序调用,子程序存在于系统空间。

3、通过自陷指令调用系统服务流程:

windows系统通过自陷指令"int 0x2e"进入系统空间实现系统调用。

①CPU执行int 0x2e,CPU运行状态切换为系统态

②从任务状态段TSS装入本线程的系统空间的SS和ESP

③依次把用户空间的SS、ESP、EFLAGS、CS、EIP的内容压入系统空间堆栈

  
每个线程都有自己的系统空间堆栈,其堆栈段寄存器SS和堆栈指针ESP的内容保存在一个称为"任务状态段"既TSS的数据结构里面.


与此相应,CPU中有个称为"任务寄存器"既TR的段寄存器.每当从用户空间进入系统空间时,CPU就自动根据TR的指引从TSS中获取当前


进程的SS和ESP两个寄存器的值. 然后在把上面提到的几个寄存器中的内容压入这个堆栈.当然,还要根据IDTR即中断描述符表寄存器


的指引获取CS和EIP. 这个过程所涉及的"时钟周期"显然不会少.正因为这样,后来才有了"快速系统调用"指令sysenter和sysexit的出现.

④从中断向量表中(Interrupt Descriptor Table)以0x2e为中断向量,开始执行系统空间中的程序。

⑤程序执行后,通过iret(中断返回)指令实现上述过程的逆过程


?


__declspec
(
naked
)__stdcall
NtReadFile(
int
dummy0,
int
dummy2,
int
dummy3)
{
   
__asm{
      
push ebp
      
mov ebp,esp
      
mov eax,152    
//将NtReadFile()的系统调用号存入EAX
      
lea edx,8[ebp]  
//使EDX指向堆栈上参数块的起点,返回地址+Filehandle = 8 bytes
      
int
0x2E       
//进入内核
      
pop ebp
      
ret 9          
//在堆栈上共有9个参数(win32汇编应该是9*4 :ret 36,)
     
}
}
//<SPAN style="COLOR: #ff0000">注意:此函数的参数在函数中并米有用到,因此其实是可有可无的.</SPAN>


 

4、windows中介函数详解

所有windows基本系统调用的中介函数存在于ntdll.dll中。这些中介函数拥有相同的样式(都是执行自陷指令,然后调用相应的内核函数),所以reactOS用工具自动生成这些中介函数。

函数名、系统调用号、参数个数,来自文件sysfuncs.lst中,例如:


?


NtAcceptConnectPort 6
 
 
NtAccessCheck 8
 
 
NtAccessCheckAndAuditAlarm 11
 
 
NtAccessCheckByType 11
 
 
NtAccessCheckByTypeAndAuditAlarm 16
 
 
NtAccessCheckByTypeResultList 11
 
 
NtAccessCheckByTypeResultListAndAuditAlarm 16
 
 
NtAccessCheckByTypeResultListAndAuditAlarmByHandle 17
 
 
NtAddAtom 3
 
 
...

这里,NtAcceptConnerPort就是调用号为0的系统调用函数,它有六个参数。其他的调用函数类似。


 

5、何谓CPU进入系统空间?

①CPU的运行状态从用户态转为系统态,拥有了执行"特权指令"的能力。

②CPU进入系统态后,可以访问内存中的系统区(内核所在的区域),而在用户态下是无法访问的。

③当前进程使用的堆栈,从用户态切换到系统态。堆栈原先的内容(用户态),以及用户空间的堆栈指针,被压入系统空间堆栈。同时被压入的,还有EFLAGS、CS、EIP的内容。

④关闭中断,依照中断向量从IDT中找到相应的表项,并根据表项提供的程序入口进入相应的中断服务程序。

6、windows提供的自陷指令

int 3:程序入口为_KiTrap3()。用于debug时设置断点。

int 0x2e:程序入口为_KiSystemService()。用于实现windows系统调用。

int 0x2a:程序入口为_KiGetTickCount()。让用户空间程序获取高精度时钟计数。

int 0x2b:程序入口为_KiCallbackReturn()。特殊自陷入口。用于在内核中回调用户空间中的子程序,执行完后,通过0x2b返回系统空间。

int 0x2c/int 0x2d:用于dubug。

 


 

7、_KiSystemService()

_KiSystemService()是所有的系统调用入口函数。对应于中断向量0x2e。所以,当中间函数执行int 0x2e时,CPU就进入系统空间中的这个函数。

进入系统空间时,CPU自动将下列信息压入系统空间堆栈:

①用户空间的堆栈位置,包括SS和ESP的内容

②EFLAGS的内容

③用户空间指令位置,包括CS和EIP的内容

这些信息是将来返回用户空间所必须的。

 

 

8、快速系统调用

x86系列从P2开始增设了两条指令和三个寄存器用来用来实现快速系统调用。分别是:

sysenter和sysexit(对应于自陷方式中的int/call和iret/ret)

sysenter_cs_msr

sysenter_ip_msr

sysenter_esp_msr

快速系统调用实现更为高效。

采用快速系统调用的总入口时_KiFastCallEntry(对应于_KiSystemService)。

快速系统调用代码摘要:



?


__decksoec(
naked
) __stdcall  
   
 
NtReadFile(
int
dummy,
int
dummy1,
int
dummy2)  
   
 
{  
   
 
      
__asm{  
   
 
           
mov eax,152  
   
 
           
mov ecx, KUSER_SHARED_SYSCALL  
   
 
           
CALL [ecx]  
   
 
           
ret 9  
   
 
      
}  
   
 
}

其中,KUSER_SHARED_SYSCALL指向用户空间的一个地址(0x7ffe0300),这个位置存储了一个函数指针,指向KiIntSystemCall()或KiFastSystemCall()两个函数其中之一。系统在初始化的时候根据CPU是否支持快速系统调用而使该指针指向KiIntSystemCall()或KiFastSystemCall()。



?


_KiIntSystemCall@0:  
   
 
   
lea edx, [esp+8]  
   
 
   
int
0x2e  
   
 
   
ret  
   
 
   
 
_KiFastSystemCall@0:  
   
 
   
mov edx,csp  
   
 
   
sysenter  
   
 
   
 
_KiFastSystemCallRet@0:  
   
 
   
ret

不难看出KiIntSystemCall()就是原先采用中断方式进入内核。而KiFastSystemCall()则是采用快速系统调用进入内核。
内核在初始化阶段根据处理器的CPUID判断其是否支持快速系统调用,而从ntdll.dll的映像中找到KiIntSystemCall()或KiFastSystemCall()的地址,将其写入用户空间的地址
0x7ffe0300.实际上CPU写入的时系统空间的地址0xffdf0300,但是系统空间0xffdf0000与用户空间0x7ffe0000的地方就好像是一条隧道的两端,两块64KB的虚存空间都映射到
同一块物理内存。

快速系统调用流程图:

 

9、从内核中发起系统调用

在内核中不能直接调用NtReadFile之类的实现函数。一般也是通过中介函数进行调用。

以NtReadFile为例,其在内核中的中介函数由工具生成,其名称为ZwReadFile。

代码:


?


__declspec
(
naked
) _stdcall  
ZwReadFile(
int
dummy0,
int
dummy1,
int
dummy2)  
{  
        
asm{  
   
 
             
mov eax 152  
   
 
             
lea edx,[esp+4]  
   
 
             
push KGDT_R0_CODE  
   
 
             
call _KiSystemservice  
   
 
             
ret 36  
        
}  
}

流程图:


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值