Chinaunix首页 | 论坛 | 博客
  • 博客访问: 192507
  • 博文数量: 73
  • 博客积分: 5000
  • 博客等级: 大校
  • 技术积分: 1160
  • 用 户 组: 普通用户
  • 注册时间: 2009-04-23 15:53
文章分类

全部博文(73)

文章存档

2011年(1)

2009年(72)

我的朋友

分类: LINUX

2009-04-23 16:42:18

                     Linux系统调用分析

                              计算机962 周从余

 

一.    与系统调用有关的一些基本知识

 

1.系统调用的定义

       OS的核心中都设置了一组用于实现各种系统共能的子程序,并将它们提供

给用户程序调用.每当用户在程序中需要OS提供某种服务时,便可利用一条系统

调用命令,去调用所需的系统过程.所以说系统调用在本质上是一种过程调用.系统

调用是进程和操作系统之间的接口,这些调用一般就是一些汇编指令集,Linux

系统中这些调用是用C语言和汇编编写的。用户只有通过这些系统调用才能使

用操作系统提供的一些功能.   

 

  2.系统调用与过程调用的区别

 

  过程调用调用的是用户程序,它运行在用户态;其被调用过程是系统过程,运行在系统态下.

      系统调用是通过软中断机制进入OS核心,经过核心分析后,才能转向响应的命令

   处理程序.系统调用返回时通常需要重新调度.系统调用允许嵌套调用.                

            3.中断与异常

         中断(interrupt)是由外部事件的,可以随时随地发生(包括在执行程序时)所以

     用来响应硬件信号。在80386中,又把中断分为两种:

              可屏蔽中断(Miscible Interrupt                               MI

              不可屏蔽中断(NonMaskable Interrupt                    NMI

         异常(exception)是响应某些系统错误引起的,也可以是响应某些可以在程序中

     执行的特殊机器指令引起的. 异常也分为两种:

     处理器异常,(指令内部异常 overflow 等)

     编程(调试)异常(debugger

 每一个异常或中断都有一个唯一的标识符,在linux文献中被称为向量。指令内

 异常和NMI(不可屏蔽中断)的中断向量的范围从0—3132-255的任何向量都

 以用做

           可屏蔽中断

           编程(调试)异常

       至于可屏蔽中断则取决于该系统的硬件配置。外部中断控制器(External interrupt

    controler)在中断响应周期(interrtupt acknowledge cycle)把中断向量放到总线上。

 

  中断和异常的优先级                                                    

      最高 :除调试错误以外的所有错误       最低  INTR中断。                                                

             中断指令INTO,INTn,INT3                                         

             当前指令的调试中断                                             

             下一指令的调试中断                                                 

             不可屏蔽中断                                                   

   4.Intel386 提供的功能                                                         

      Intel386认识两种事件类:异常与中断。两者都会强制性创建一个进程或任务。

   中断能在任何不可预料的时间发生,来响应硬件的信号.386能辨认两种中断来源

   可屏蔽中断和不可屏蔽中断.并能辨认两种异常来源:处理器检测异常和程序异常

   每一个中断和异常都有一个号码,都对应着一个相应的矢量地址,不可屏蔽中断

   和处理器检测异常都已经被安排在031的矢量表中了,可屏蔽中断的矢量地

   址由硬件决定,外部中断控制器在中断认可时钟周期时将矢量地址放到总线上。

   任何在32255范围内的矢量,都可以作为可屏蔽中断和程序异常用。以下是所

   有可能的中断和异常的列表:

                                              

 

      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)

       该例中,

              intsetuid的返回类型,

              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,%edx

    movl $(KERNEL_CS << 16),%eax

    movw %dx,%ax           /* selector = 0x0010 = cs */

    movw $0x8E00,%dx     /* interrupt gate - dpl=0, present */

    lea SYMBOL_NAME(idt),%edi

    mov $256,%ecx

  rp_sidt:

    movl %eax,(%edi)

    movl %edx,4(%edi)

    addl $8,%edi

    dec %ecx

    jne rp_sidt

    ret

 

  设置系统中断

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;i<48;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(&idt[n],15,3,addr)

其中“_set_gate()”也是在该文件中定义的宏:

#define _set_gate(gate_addr,type,dpl,addr) \

__asm__ __volatile__ (movw %%dx,%%ax\n\t \    

movw %2,%%dx\n\t \

movl %%eax,%0\n\t \

movl %%edx,%1 \

:=m (*((long *) (gate_addr))), \

=m (*(1+(long *) (gate_addr))) \

:i ((short) (0x8000+(dpl<<13)+(type<<8))), \

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,描述符特权级DPL3386陷阱门。

这样,当用户程序使用INT 0x80指令时,就实现了应用程序从处于Ring 3用户地址空间向Ring 0级的操作系统核心空间的切换,并把CPU的控制权交给了操作系统,由操作系统来执行具体的各项系统调用。

 

3.INT 0x80 (即system_call)的具体实现

当用户调用INT 0x80而进入system_call函数后,首先检查用来存放系统调用编号的eax的值是否超出IDT表的项数NR_syscallsNR_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型。下面是其中几项:

.data

ENTRY(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_eax

    SAVE_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 errors

    movl %db6,%edx

    movl %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 value

jmp ret_from_sys_call    

                     # 系统调用返回.

ALIGN

1:  call SYMBOL_NAME(syscall_trace)

    movl ORIG_EAX(%esp),%eax

    call *SYMBOL_NAME(sys_call_table)(,%eax,4)

    movl %eax,EAX(%esp)     # save the return value

    movl SYMBOL_NAME(current_set),%eax

       call SYMBOL_NAME(syscall_trace

        "=m" (*(1+(long *) (gate_addr))) \

       :"i" ((short) (0x8000+(dpl<<13)+(type<<8))), \

        "d" ((char *) (addr)),"a" (KERNEL_CS << 16) \

       :"ax","dx")

 

4.总结Linux中系统调用的全部过程                                 

    1.系统使用宏syscallX()将相应的系统调用定义为其同名函数。调用中断

  int 0x80.并将参数传送到相应的寄存器中,供ENTRYsystem_call)使用。             

2.进入ENTRY(system_call)中,当系统调用合法时,根据索引值,在                  

  sys_call_table中找到相应的实际服务程序入口地址,并调用它。最后返回

  syscallX()中,包括返回值。                                                

3.进入sys_call_table指定的相应实际服务程序,在这里,完成真正的具体工作。      

           

.系统调用的实例分析                                                               

  在了解了系统调用大致流程后,我们以一个具体的调用实例来进一步说明这个流程,以加深印象.现在,我们就以系统调用sys_open来加以说明:                                      

1.    syscallX()宏的初始化                                                          

1.     

       static inline _syscall3(int,open,const char *,file,int,flag,int,mode)           

这是一个用来作为打开文件的系统调用宏,其原形由_syscall3宏决定:                 

 

   #define _syscall3(type,name,type1,arg1,type2,arg2,type3,arg3) \                 

        type name(type1 arg1,type2 arg2,type3 arg3) \                                   

{ \                                                                             

     long __res; \                                                                   

     __asm__ volatile ("int $0x80" "=a" (__res)   : "0"\                          

             (__NR_##name),"b" ((long)(arg1)),"c" ((long)(arg2)), \                       

              "d" ((long)(arg3))); \                                        

     if (__res>=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,flag,mode.它们的值分别被存入寄存器ebx,ecx,edx    

并且eax指定为将返回值传回给_reseax在这里被初始化为__NR_open,__NR_open       

unistd.h中被宏定义为数值5                                                     

           #define __NR_open          5                                             

这个数值就是实际系统调用程序段sys_open入口地址在sys_call_table中的索引,        

这个参数被放入eax,将会作为sys_call_table的索引使用。__asm__volatile将所有       

参数指定,并调用中断int 0x80,如上所述由于中断矢量表初始化时,已经将其指向矢量地址&system_call,因此,系统转入该代码入口--ENTRY(system_call)                                    

                                                                                

2. 系统调用的通用入口地址:                                                      

    所有的系统调用都将使用这个入口ENTRY(system_call),无论其参数到底是什么。         

其最重要的参数--sys_call_table的索引都是放在寄存器eax中的,因此总能以           

此找到相应的系统调用服务程序。                                                  

如上所述,首先SAVE_ALL保存现场,然后将 eax的值与$(NR_syscalls)比较               

因为__NR_open的值为5,没有超过256个系统调用的限制,因此合法,继续执行下去。     

Call *SYMBOL_NAME(sys_call_table)(,%eax,4)将以eax的值5,在sys_call_table        

中找到sys_open的入口地址,进入该程序。                                          

  

3.进入实际的系统调用服务程序:                                                  

  asmlinkage int sys_open(const char * filename, int flags, int mode)           

      {                                                                              

              char * tmp;                                                        

              int fd, error;                                                    

                 /*关闭中断,在执行该服务程序时,不许其它中断响应                  

              lock_kernel();                                                    

                /*找到一个空的文件描述符入口,然后将其空闲位置为否*/              

                /*如果找不到,返回负值*/                                          

              fd = get_unused_fd();                                             

              if (fd < 0)                                                        

                goto out;                                                       

                /*大概尝试在内存页面内查找该文件*/                                

              tmp = getname(filename);                                          

              error = PTR_ERR(tmp);                                             

                 /*如果打开文件有错,退出*/                                        

              if (IS_ERR(tmp))                                                  

                - goto out_fail;                                                

              /*调用do_open打开文件*/                                           

              error = do_open(tmp, flags, mode, fd);                            

              /*将文件名装入quicklist,放入内存中,加快下次的寻找*/              

              putname(tmp);                                                     

              if (error)                                                        

              goto out_fail;                                                     

out:                                                                            

              /*解锁,打开中断*/                                                

              unlock_kernel();                                                  

              return fd;                                                        

out_fail:                                                                       

              /*fd号的空闲标志位置位,表示已释放*/                            

              put_unused_fd(fd);                                                 

              fd = error;                                                       

              goto out;                                                         

}                                                                                

当程序执行完了之后,系统返回到ENTRY(system_call)中,然后按照第二节              

所叙述的流程,一步步进行。直到返回到原来的syscall3()宏定义的函数                

              error = PTR_ERR(tmp);                                             

              /*如果打开文件有错,退出*/                                         

              if (IS_ERR(tmp))                                                  

                - goto out_fail;                                                

              /*调用do_open打开文件*/                                           

              error = do_open(tmp, flags, mode, fd);                            

              /*将文件名装入quicklist,放入内存中,加快下次的寻找*/              

              putname(tmp);                                                     

              if (error)                                                         

              goto out_fail;                                                    

out:                                                                            

              /*解锁,打开中断*/                                                 

              unlock_kernel();                                                  

              return fd;                                                        

out_fail:                                                                        

              /*fd号的空闲标志位置位,表示已释放*/                            

              put_unused_fd(fd);                                                

              fd = error;                                                       

              goto out;                                                         

}                                                                               

当程序执行完了之后,系统返回到ENTRY(system_call)中,然后按照第二节              

所叙述的流程,一步步进行。直到返回到原来的syscall3()宏定义的函数                 

              putname(tmp);                                                     

              if (error)                                                        

              goto out_fail;                                                     

out:                                                                            

              /*解锁,打开中断*/                                                

              unlock_kernel();                                                  

              return fd;                                                        

out_fail:                                                                       

              /*fd号的空闲标志位置位,表示已释放*/                            

              put_unused_fd(fd);                                                 

              fd = error;                                                       

              goto out;                                                         

}                                                                                

当程序执行完了之后,系统返回到ENTRY(system_call)中,然后按照第二节              

所叙述的流程,一步步进行。直到返回到原来的syscall3()宏定义的函数                

open中,此时,变量_res已取得返回值,如果文件打开时出错,此时_res                

为负值,并将其绝对值赋给全局变量errno,作为以后处理错误信息                     

的参数,并且返回-1。如果文件打开成功,将返回_res.                                

到此为止,就是一个完整的Linux中的系统调用. 

 

.Linux系统调用分析的总结                                                       

   系统调用是操作系统与用户程序间的主要接口,系统调用是很底层的操作,这部分         

直接关系到用户对整个系统资源的使用问题。系统调用构架是否做的好,直接关系        

到这个操作系统的效率和稳定性。因此,能够有一个成熟,完善的系统调用方式          

是非常重要的,而它的设计也是较复杂的,它与进程的调度,中断的响应都有直接关系。在我分析的这个版本的Linux中,系统调用是很有层次性的,用户不能直接的使用系统提供的服务程序,而必须经过一个宏调用,进入系统调用入口,经过系统的许多确认和调度后,才允许被调用,这也是几乎所有操作系统所必须做到的                              

    经过对系统调用部分的分析,加深了我对操作系统的理解。在没有接触过操作            

系统原代码以前,总认为操作系统是很神秘的,在这之后,我才发现其实它也是用我们已经学过的C和汇编写的,只是需要巧妙的数据结构和高效率的算法.如果我们在这两方面有了一定的造诣,我想我们也能写出一个操作系统来.通过这次实验,我学到了许多编程方面的技巧,如模块的思想,C与汇编的连用等等.同时,我阅读程序的能力也有了一定的提高,这次实验受益非浅.                                                               

                                                                                

                                

 

 

参考文献:

 

  David A Rusling The Linux Kernel

  Michael K. JohnsonStanley Scalsky  How System Calls Work on Linux/i86

  <系统调用>>         徐峻

  <系统调用分析>>     忻尚波

阅读(1707) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~