Linux系统调用过程分析.doc_第1页
Linux系统调用过程分析.doc_第2页
Linux系统调用过程分析.doc_第3页
Linux系统调用过程分析.doc_第4页
Linux系统调用过程分析.doc_第5页
已阅读5页,还剩8页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

Linux系统调用分析 计算机962班 周从余一. 与系统调用有关的一些基本知识1. 系统调用的定义 在OS的核心中都设置了一组用于实现各种系统共能的子程序,并将它们提供给用户程序调用.每当用户在程序中需要OS提供某种服务时,便可利用一条系统调用命令,去调用所需的系统过程.所以说系统调用在本质上是一种过程调用.系统调用是进程和操作系统之间的接口,这些调用一般就是一些汇编指令集,在Linux系统中这些调用是用C语言和汇编编写的。用户只有通过这些系统调用才能使用操作系统提供的一些功能. 2.系统调用与过程调用的区别 过程调用调用的是用户程序,它运行在用户态;其被调用过程是系统过程,运行在系统态下. 系统调用是通过软中断机制进入OS核心,经过核心分析后,才能转向响应的命令 处理程序.系统调用返回时通常需要重新调度.系统调用允许嵌套调用. 3.中断与异常 中断(interrupt)是由外部事件的,可以随时随地发生(包括在执行程序时)所以 用来响应硬件信号。在80386中,又把中断分为两种: 可屏蔽中断(Miscible Interrupt)MI 不可屏蔽中断(NonMaskable Interrupt)NMI 异常(exception)是响应某些系统错误引起的,也可以是响应某些可以在程序中 执行的特殊机器指令引起的. 异常也分为两种: 处理器异常,(指令内部异常 如overflow 等) 编程(调试)异常(debugger) 每一个异常或中断都有一个唯一的标识符,在linux文献中被称为向量。指令内 部 异常和NMI(不可屏蔽中断)的中断向量的范围从031。32-255的任何向量都 可 以用做 可屏蔽中断 编程(调试)异常至于可屏蔽中断则取决于该系统的硬件配置。外部中断控制器(External interrupt controler)在中断响应周期(interrtupt acknowledge cycle)把中断向量放到总线上。 中断和异常的优先级: 最高 :除调试错误以外的所有错误 最低: INTR中断。 中断指令INTO,INTn,INT3 当前指令的调试中断 下一指令的调试中断 不可屏蔽中断 4.Intel386 提供的功能 Intel386认识两种事件类:异常与中断。两者都会强制性创建一个进程或任务。 中断能在任何不可预料的时间发生,来响应硬件的信号.386能辨认两种中断来源 可屏蔽中断和不可屏蔽中断.并能辨认两种异常来源:处理器检测异常和程序异常 每一个中断和异常都有一个号码,都对应着一个相应的矢量地址,不可屏蔽中断 和处理器检测异常都已经被安排在0到31的矢量表中了,可屏蔽中断的矢量地 址由硬件决定,外部中断控制器在中断认可时钟周期时将矢量地址放到总线上。 任何在32到255范围内的矢量,都可以作为可屏蔽中断和程序异常用。以下是所 有可能的中断和异常的列表: 0 Divide error 1 Debug exception 3 NMI interrupt 4 INTO-detected overflow 5 Bound range exceeded 6 Invalid opcode 7 coprocessor not available 8 double fault 9 coprocessor segment overrun 10 invalid task state segment 11 segment not present 12 stack fault 13 general protection 14 page fault 15 reserved 16 coprocessor error 17-31 reserved 32-255 maskable interrupt 二. Linux系统调用的流程1. Linux系统调用的简单流程 通常,在OS的核心中都设置了一组用于实现各种系统功能的子程序(过程),并将它们提供给用户调用。每当用户在程序中需要OS提供某种服务时,变可利用一条系统调用命令,去调用系统过程。它一般运行在系统态;通过中断进入;返回时通常需要重新调度(因此不一定直接返回到调用过程)。 Linux系统调用的流程非常简单,它由0x80号中断进入系统调用入口,通过使用系统调用表保存系统调用服务函数的入口地址来实现. Processor 调用syscallN( )调用 int $0x80 System_call调用实际服务程序 返回2.Linux系统中断和异常的使用 Linux中,系统调用的执行是通过中断或异常的方式来进行的,他将执行相应的机器代码指令,来产生中断或异常信号,产生中断或异常的重要效果是系统自动将用户3模式切换为核心模式,并安排异常处理程序的执行。 Linux设置了一个可屏蔽中断int 0x80,我们用向量0x80来把控制传给kernel这个中断向量的设置(初始化)将在下文提到,这里就不多说了。得一提的是,存在一个syscallX()宏(x是作为实际程序调用时的参数)可以方便的调用那么多的syscall.(syscallX() / usr/src/libc/syscall)每个syscallX()宏都可以扩展成为一段汇编代码,通过一个中断来初始化系统调用堆栈和调用_system_call()函数。有关syscallX( )的介绍 0x80将控制传递给核心。0x80就是系统调用的一个矢量地址。这个中断矢量表是在系统启动时就初始化好的,以及一些矢量地址,如系统时钟。当用户系统调用时,执行如下: 每个系统调用都通过lib库体现。每一个系统调用在lib库中一般是一个宏syscallX(),X是具体某个调用的数字参数。有的系统调用更复杂,因为它们有可变的参数列表,但它们仍用一样的入口指针。 每个系统调用宏将展开成一个汇编段,用来建立调用的堆栈段,然后通过 调用中断int $0x80调用-ENTRY(system_call). 注:syscallX()宏在/usr/include/linux/unistd.h中, 以下是用_syscallX()宏定义的一些系统调用。 static inline _syscall0(int,idle) static inline _syscall0(int,fork) static inline _syscall2(int,clone,unsigned long,flags,char *,esp) static inline _syscall0(int,pause) static inline _syscall0(int,setup) static inline _syscall0(int,sync) static inline _syscall0(pid_t,setsid) static inline _syscall3(int,write,int,fd,const char *,buf,off_t,count) static inline _syscall1(int,dup,int,fd) static inline _syscall3(int,execve,const char *,file,char *,argv,char *,envp) static inline _syscall3(int,open,const char *,file,int,flag,int,mode) static inline _syscall1(int,close,int,fd) static inline _syscall1(int,_exit,int,exitcode) static inline _syscall3(pidt,t,waitpid,pid_t,pid,int *,wait_stat,int options) static inline _syscall0(int,idle) static inline _syscall0(int,fork) static inline _syscall2(int,clone,unsigned long,flags,char *,esp) static inline _syscall0(int,pause) static inline _syscall0(int,setup) static inline _syscall0(int,sync) static inline _syscall0(pid_t,setsid) static inline _syscall3(int,write,int,fd,const char *,buf,off_t,count) static inline _syscall1(int,dup,int,fd) static inline _syscall3(int,execve,const char *,file,char *,argv,char *,envp) static inline _syscall3(int,open,const char *,file,int,flag,int,mode) static inline _syscall1(int,close,int,fd) static inline _syscall1(int,_exit,int,exitcode) static inline _syscall3(pid_t,waitpid,pid_t,pid,int *,wait_stat,int,options) 当int $0x80执行后,调用才传送到核心入口指针ENTRY(system_call)。 在宏_syscallX(Parameter) 中x 表示系统调用所需的参数的数目。Parameter 是一组参数。SyscallX() 宏的第一个参数表明,该系统调用最后调用的同名函数的返回值的类型。SyscallX() 宏的第二个参数表明,该系统调用的同名函数名。后面是系统调用所需要的每个参数,例:setuid()syscall1(int,setuid,uid_t,uid)该例中,int是setuid的返回类型,setuid是函数名。Uid_t是参数类型,Uid是参数 。用做系统调用的参数类型有一个限制,他们的容量不能超过4个字节,因为在执行int 0x80 时,所有的参数都是通过寄存器传递的,而在386体系结构中,寄存器是32位的.所以,他们的容量不能超过4个字节(32位)。使用CPU寄存器做参数传递的另一个限制是,可以传递的参数的数目,使用CPU寄存器做参数传递最多可以传递五个参数,所以,一共定义了六个不同的syscallX()宏。(从syscall0()到 syscall5()宏)一旦syscallX()宏被调用,系统使用其调用的特定参数进行扩展,(宏展开)得到的结果是一个与系统调用同名的函数。他可以在用户的程序中被调用。当syscall()被调用后,并没有任何的系统代码被执行,直到syscall()调用了int 0x80 ,中断0x80 把调用(控制)传给核心入口地址中的_system_call(),这个入口地址对任何系统调用都是一样的。_System_call() 负责保护所有的寄存器,并检查系统调用是否合法,如果合法那么根据从_sys_call_table中找出的偏移量,把控制权转给真正的系统。最后,当系统调用完成后,_system_call() 还要负责调用_ret_from_sys_call()来断后。_Ret_from_sys_call()检查是否有必要重新调度,如果有的话,调用他。3.Linux系统对系统调用矢量的初始化 为中断向量准备空间在head.S中调用(head.S在保护模式下的核心初始化中执行) Startup_32() /linux/boot/head.s Setup_idt() /linux/boot/head.s Startup_32() 调用 setup_idt来把一切都设置好。Setup_idt()函数初始化了IDT表,包括256个函数入口(每个入口4字节,共1024字节),但是,没有一个中断向量在这时被真正的设置好了,现在的IDT只是一个空架子,Setup_idt()是在paging机制刚起作用的时候被调用的,这时,kernel 刚被移到0xC0000000的地方。IDT表 属性字0X8E00,所有entry的中断服务程序为ignore_int() ignore_int()只打印“unknown interrupt”。此时,idt寄存器尚未指向本表。 说白了,刚才这一段的作用就是为idt表准备空间。Setup_idt()的代码如下: setup_idt:lea ignore_int,%edxmovl $(KERNEL_CS 16),%eaxmovw %dx,%ax/* selector = 0x0010 = cs */movw $0x8E00,%dx/* interrupt gate - dpl=0, present */lea SYMBOL_NAME(idt),%edimov $256,%ecx rp_sidt:movl %eax,(%edi)movl %edx,4(%edi)addl $8,%edidec %ecxjne rp_sidtret 设置系统中断Linux进入保护模式对一些必要的核心数据进行初始化后,转入start_kernel()模块。该模块调用trap_init函数设置IDT表各项内容(arch/i386/kernel/traps.c).void trap_init(void)set_call_gate(&default_ldt,lcall7);set_trap_gate(0,÷_error);set_trap_gate(1,&debug);set_trap_gate(2,&nmi);set_system_gate(3,&int3); /* int3-5 设置成system_gate(实际为DPL设置成3的 */set_system_gate(4,&overflow); /* 386陷阱门)可以让任意用户访问和调用. */set_system_gate(5,&bounds);set_trap_gate(6,&invalid_op); /* 各项只能由操作系统访问的出错陷阱处理入口 */set_trap_gate(7,&device_not_available); /* trap_gate 实际为DPL设置成0的 */set_trap_gate(8,&double_fault); /* 386陷阱门 */set_trap_gate(9,&coprocessor_segment_overrun);set_trap_gate(10,&invalid_TSS);set_trap_gate(11,&segment_not_present);set_trap_gate(12,&stack_segment);set_trap_gate(13,&general_protection);set_trap_gate(14,&page_fault);set_trap_gate(15,&spurious_interrupt_bug);set_trap_gate(16,&coprocessor_error);set_trap_gate(17,&alignment_check);for (i=18;i48;i+)set_trap_gate(i,&reserved);set_system_gate(0x80,&system_call); /*把中断0x80的入口设置为system_call*/ 其中与系统调用相关的是:set_system_gate(0x80,&system_call);设定了0x80中断set_system_gate的原形(在文件arch/i386/kernel/traps.c中)定义如下:#define set_system_gate(n,addr) _set_gate(&idtn,15,3,addr)其中“_set_gate()”也是在该文件中定义的宏:#define _set_gate(gate_addr,type,dpl,addr) _asm_ _volatile_ (“movw %dx,%axnt” “movw %2,%dxnt” “movl %eax,%0nt” “movl %edx,%1” :”=m” (*(long *) (gate_addr), “=m” (*(1+(long *) (gate_addr) :”i” (short) (0x8000+(dpl13)+(type8), “d” (char *) (addr),”a” (KERNEL_CS 16) :”ax”,”dx”)gate_addr是一个指向64位门描述符的指针.上述代码所做的实际上是把门描述符对应的32位偏移地址(offset)设置成addr(处理程序的入口地址),段选择子(selector)设置成KERNEL_CS核心段的段地址(因为各类中断和陷阱的处理程序都在核心部分),门描述符属性字中的类型字段(Type)设置成type的值,而描述符的DPL字段设置成dpl的值。因此, set_system_gate(0x80,&system_call)用宏展开后,实际上就是把中断描述表(IDT)的第0x80项设置成为入口地址为system_call,描述符特权级DPL为3的386陷阱门。这样,当用户程序使用INT 0x80指令时,就实现了应用程序从处于Ring 3用户地址空间向Ring 0级的操作系统核心空间的切换,并把CPU的控制权交给了操作系统,由操作系统来执行具体的各项系统调用。3.INT 0x80 (即system_call)的具体实现当用户调用INT 0x80而进入system_call函数后,首先检查用来存放系统调用编号的eax的值是否超出IDT表的项数NR_syscalls(NR_syscalls是在“/include/linux/sys.h”文件中定义的宏,其值为256,表示80x86微机上最多可容纳的系统调用个数)。如没有超出的话,就根据eax的值从系统调用表(sys_call_table)中得到对应的系统调用入口,并通过call 指令转入各个具体函数(sys_*)的处理过程。系统调用表(sys_call_table) 在“/arch/i386/entry.S”中定义,该表保存了所有Linux基于Intel x86系列体系结构的计算机的166个系统调用入口地址,其中每项都被说明成 long型。下面是其中几项:.dataENTRY(sys_call_table).long SYMBOL_NAME(sys_setup).long SYMBOL_NAME(sys_exit).long SYMBOL_NAME(sys_fork).long SYMBOL_NAME(sys_read) .long SYMBOL_NAME(sys_chmod).long SYMBOL_NAME(sys_chown) .long 0/* 专门为afs_syscall保留的系统调用 */ .long SYMBOL_NAME(sys_mremap) .long 0,0 /* 2 个被保留的系统调用 */.long SYMBOL_NAME(sys_vm86).space (NR_syscalls-166)*4这个sys_call_table以偏移量的方式来确定实际相应的系统调用代码,如sys_setup,sys_fork等,这些都是实际服务函数的入口地址,当系统调用被认为是合法的时候(即调用 INT 0x80时,eax的值小于NR_syscalls),将会进入这些具体的系统服务过程,执行相应的工作,完成所要求的功能。system_call的原代码也在Entry.S文件中,下面将对其作一分析,以清晰它的主要流程. ENTRY(system_call)pushl %eax# save orig_eaxSAVE_ALL # 调用宏“SAVE_ALL” 保存现有通用寄存器.关于该宏的具体作用以 及所牵涉的数据结构将与“RESTORE_ALL”一起在下文介绍.movl $-ENOSYS,EAX(%esp) # 将返回码ENOSYS(表示由于调用了不存在的sys_ call而出现错误)存入刚才由SAVE_ALL压进堆栈的 EAX字段,以便当下面的代码检测到这种错误时,向用 户程序反馈信息 cmpl $(NR_syscalls),%eax# 检测该系统调用是否合法,是否调用了不存在的 jae ret_from_sys_call # sys_call,如是,则出错返回.movl SYMBOL_NAME(sys_call_table)(,%eax,4),%eax # 表示将eax的值乘以4个字节,找到在sys_call_table中 的实际地址,(因为在sys_call_table中,每一个项长度 为4个字节),并把相应系统调用代码的线性地址存入eax 寄存器,以便使用. testl %eax,%eax# 检测是否调用了被保留的sys_calls(此时eax=0),je ret_from_sys_call # 如是,则出错返回.movl SYMBOL_NAME(current_set),%ebx # 把指向当前进程PCB的指针赋与ebx寄存器andl $CF_MASK,EFLAGS(%esp)# clear carry - assume no errorsmovl %db6,%edxmovl %edx,dbgreg6(%ebx) # 保存当前硬件调试状态寄存器(DR6 ,当出现调试异常 事故时,处理机就把DR6置位,以表明异常事故的类型) 的信息.注:dbgreg6已在Entry.S中定义为值52,即当前 进程控制块偏移量为52字节处是用来保存硬件调试状 态寄存器的(相应的还定义了其他字段的偏移量). testb $0x20,flags(%ebx) # 检测当前进程控制块的flags字段的PF_TRACESYS位 是否置位,即进程是否处于调试状态.jne 1f # 如果处于调试状态,则转入相应的处理过程. call *%eax# 正式调用所选的系统调用(返回值存放在eax寄存器中).movl %eax,EAX(%esp)# save the return valuejmp ret_from_sys_call # 系统调用返回.ALIGN1:call SYMBOL_NAME(syscall_trace)movl ORIG_EAX(%esp),%eaxcall *SYMBOL_NAME(sys_call_table)(,%eax,4)movl %eax,EAX(%esp)# save the return valuemovl SYMBOL_NAME(current_set),%eaxcall SYMBOL_NAME(syscall_trace =m (*(1+(long *) (gate_addr) :i (short) (0x8000+(dpl13)+(type8), d (char *) (addr),a (KERNEL_CS =0)return (type) _res; errno=-_res; return -1; 即为: int open(const char* file,int flag,int mode) long _res; _asm_volatile(int$0x80:=a(_res):0(_NR_#open), b(long)file),c(long)flag), d(long)(mode); if(_res=0)return(int)_res;errno=-_res; return -1; 这就是一个完整的系统调用,也就是我们编程中常常遇到的函数open。用来打开一个 文件.。在这里,open被定义为内联,主要是为了提高该系统调用的速度,并不是所 有的系统调用都使用内联的,在这个版本的Linux中,只定义了如上一些内联函数。 (见Linux如何处理中断和异常) 在这个函数中,有三个参数,file,

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论