免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 1124 | 回复: 0
打印 上一主题 下一主题

中断处理和系统调用 [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2006-06-05 22:09 |只看该作者 |倒序浏览

            中断处理和系统调用
中断处理分为硬件中断和软件异常,什么是硬件中断,磁盘,I/O设备等硬件发出数据交换请求就叫硬件中断,所以需要相应的处理程序;什么是软件异常,顾名思义软件出错误了,比如程序在一个地方被0除的时候就会产生一个错误,叫软件异常,当然也需要相应的处理程序了;
系统调用是因为应用程序需要调用内核的功能内核给用户程序留的接口。
下面的trap_init(kernel/trap.c)函数用于安装相应中断号的中断处理函数;
void trap_init(void)
{
     int i;

     set_trap_gate(0,&divide_error);
     set_trap_gate(1,&debug);
     set_trap_gate(2,&nmi);
     set_system_gate(3,&int3);  /* int3-5 can be called from all */
     set_system_gate(4,&overflow);
     set_system_gate(5,&bounds);
     set_trap_gate(6,&invalid_op);
     set_trap_gate(7,&device_not_available);
     set_trap_gate(8,&double_fault);
     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,&reserved);
     set_trap_gate(16,&coprocessor_error);
     for
(i=17;i
            set_trap_gate(i,&reserved);
     set_trap_gate(45,&irq13);
     outb_p(inb_p(0x21)&0xfb,0x21);
     outb(inb_p(0xA1)&0xdf,0xA1);
     set_trap_gate(39,&parallel_interrupt);
}
硬件中断和软件异常的处理程序在主要在汇编文件asm.s中,比如divide_error这个过程为:
_divide_error:
     pushl
$_do_divide_error
no_error_code:
     xchgl
%eax,(%esp)
     pushl
%ebx
     pushl
%ecx
     pushl
%edx
     pushl
%edi
     pushl
%esi
     pushl
%ebp
     push
%ds
     push
%es
     push
%fs
     pushl
$0         # "error code"
     lea
44(%esp),%edx
     pushl
%edx
     movl
$0x10,%edx
     mov
%dx,%ds
     mov
%dx,%es
     mov
%dx,%fs
     call
*%eax
     addl
$8,%esp
     pop
%fs
     pop
%es
     pop
%ds
     popl
%ebp
     popl
%esi
     popl
%edi
     popl
%edx
     popl
%ecx
     popl
%ebx
     popl
%eax
     iret


汇编代码do_divide_error也是一个函数,所以0除的真正处理函数还是用C来实现的,do_divide_error(kernel/trap.c)的原型为:
void do_divide_error(long esp, long error_code)
{
     die("divide
error",esp,error_code);
}
调用die函数,给出一些信息然后死机,但是我们对函数的参数进行一些分析会发现很有意思。
call *%eax 这个语句就是调用do_divide_error这个函数执行。eax中放入函数的地址是通过:
xchgl %eax,(%esp) 这条语句实现的,因为在这之前,函数的地址刚好被压入堆栈,esp指向的正是函数的地址;
那么do_divide_error函数的参数esp是什么呢?通过:
     lea
44(%esp),%edx
     pushl
%edx
44(%esp)表示44+esp为引发该中断的指令的地址压入堆栈时的esp0指针的位置。
执行 call *%eax 这个语句时堆栈的状态:

  
   
  
  
  原ss
  


  
  
   
  
  
   
   
   
     
      
      
      中断返回地址
      
      
     
   
     
   
  
  原esp
  


  
  原eflags
  


  
  
   
   
  
   
  
  
   
   
   
     
      
      
      中断返回地址
      
      
      
     
   
     
   
  
  
  
  
  
  
   
   
   
     
      
      
      ßesp0
      
      
      
     
   
     
   
  
  cs
  


  
  
   
   
   
     
      
      
      -------------------------------------------------------
      
      
     
   
   
  
file:///C:%5CDOCUME%7E1%5CADMINI%7E1%5CLOCALS%7E1%5CTemp%5Cmsohtml1%5C01%5Cclip_image001.gif
eip
  


  
  
  
   
   
   
     
      
      
      ßesp1
      
      
      
     
   
     
   
  
  C函数地址(eax)
  


  
  ebx
  


  
  ecx
  


  
  edx
  


  
  
   
  
  
   
   
   
     
      
      
      44
      
      
      
     
   
     
   
  
  edi
  


  
  esi
  


  
  ebp
  


  
   
  
  
  ds
  


  
   
  
  
  es
  


  
   
  
  
  
   
  
  
   
   
   
     
      
      
      ßesp2
      
      
      
     
   
     
   
  
  fs
  


  
  Error_code
  


  
  Esp0
  


  
   
  


中断调用没有出错号的情景

所以do_divide_error(long
esp, long error_code)的esp是引发异常的地址,error_code就是异常码。







下面函数sched_init(kernel/sched.c)用于安装中断号0x80的系统调用函数处理函数system_call。

void sched_init(void)
{
     int i;
     struct
desc_struct * p;

     if
(sizeof(struct sigaction) != 16)
            panic("Struct
sigaction MUST be 16 bytes");
     set_tss_desc(gdt+FIRST_TSS_ENTRY,&(init_task.task.tss));
     set_ldt_desc(gdt+FIRST_LDT_ENTRY,&(init_task.task.ldt));
     p =
gdt+2+FIRST_TSS_ENTRY;
     for(i=1;i
            task
= NULL;
            p->a=p->b=0;
            p++;
            p->a=p->b=0;
            p++;
     }
/* Clear NT, so that we won't have troubles with that
later on */
     __asm__("pushfl
; andl $0xffffbfff,(%esp) ; popfl");
     ltr(0);
     lldt(0);
     outb_p(0x36,0x43);           /* binary, mode 3, LSB/MSB, ch 0 */
     outb_p(LATCH
& 0xff , 0x40); /* LSB */
     outb(LATCH
>> 8 , 0x40);       /* MSB */
     set_intr_gate(0x20,&timer_interrupt);
     outb(inb_p(0x21)&~0x01,0x21);
     set_system_gate(0x80,&system_call);
}
而system_call这个函数在kernel/system_call.s中实现,原型为:
reschedule:
     pushl $ret_from_sys_call
     jmp
_schedule
.align 2
system_call:
     cmpl
$nr_system_calls-1,%eax
     ja
bad_sys_call
     push %ds
     push %es
     push %fs
     pushl %edx
     pushl %ecx           # push %ebx,%ecx,%edx as parameters
     pushl %ebx           # to the system call
     movl
$0x10,%edx              # set up ds,es to
kernel space
     mov %dx,%ds
     mov %dx,%es
     movl
$0x17,%edx              # fs points to
local data space
     mov %dx,%fs
     call
_sys_call_table(,%eax,4)
     pushl %eax
     movl
_current,%eax
     cmpl
$0,state(%eax)           # state
     jne
reschedule
     cmpl
$0,counter(%eax)              # counter
     je
reschedule
ret_from_sys_call:
     movl
_current,%eax            # task[0] cannot
have signals
     cmpl
_task,%eax
     je 3f
     cmpw $0x0f,CS(%esp)             # was old code segment supervisor ?
     jne 3f
     cmpw
$0x17,OLDSS(%esp)            # was stack
segment = 0x17 ?
     jne 3f
     movl
signal(%eax),%ebx
     movl
blocked(%eax),%ecx
     notl %ecx
     andl
%ebx,%ecx
     bsfl
%ecx,%ecx
     je 3f
     btrl
%ecx,%ebx
     movl
%ebx,signal(%eax)
     incl %ecx
     pushl %ecx
     call
_do_signal
     popl %eax
3:  popl %eax

在程序的开始处,首先比较EAX中的功能号是不是有效.            
然后保存会用到的寄存器.
LINUX默认把ds,es作为内核数据段,0x10为内核数据段的选择符;fs用于用户数据段,0x1f为用户数据段的选择符。
接着通过一个地址跳转表sys_call_table调用相应系统调用的C函数。在C函数返回后,保存(push)了调用返回值(eax中的值)。
接下来,该程序查看执行本次调用进程的状态。如果由于上面C函数的操作或者其他情况而使进程的状态从执行态变成了其它状态,或者由于时间片已经用完(counter==0),则调用进程调度函数schedule()(jmp _schedule).由于在执行“jmp _schedule”之前已经把返回地址ret_from_sys_call入栈,因此在执行完schedule()后最终会返回到ret_from_sys_call处继续执行。
从ret_from_sys_call标号开始处的代码执行一些系统调用后的处理工作。
该段汇编代码调用sys_call_table+
%eax *4处的函数执行,sys_call_table是一个函数表在include/linux/sys.h文件中;

extern int sys_setup();
extern int sys_exit();
extern int sys_fork();
extern int sys_read();
…………………………
extern int sys_setreuid();
extern int sys_setregid();

fn_ptr sys_call_table[]
= { sys_setup, sys_exit, sys_fork, sys_read,
……………………………………………………………..
sys_setreuid,sys_setregid };

其中fn_ptr(include/linux/sched.h)是一个函数指针,定义为:
typedef   int  (*fn_ptr)();

下面我们来实现一个自己的系统调用。
在用户空间调用的函数原型为:
Int getval(int val);
则我们在include/unistd.h文件中要包括该文件,并且通过
_syscall1(int,getval,int,val)
实现该函数,注意上面的函数后面没有“;”这个符号。
_syscall1这个宏表示有一个参数的系统调用,定义在include/unistd.h文件中:
#define _syscall1(type,name,atype,a) \
type name(atype a) \
{ \
long __res; \
__asm__ volatile ("int $0x80" \
     :
"=a" (__res) \
     :
"0" (__NR_##name),"b" ((long)(a))); \
if (__res >= 0) \
     return
(type) __res; \
errno = -__res; \
return -1; \
}
我们还要在include/unistd.h文件中加上函数的功能号
#define __NR_getval       72

我们在include/linux/sys.h  还要加上
extern int sys_ getval();
在sys_call_table的最后加上函数sys_ getval。
然后在内核的某个文件(比如kernel/sched.c)中实现:
int    sys_ getval(
long  val)
{   
     return (int)val;
}
下面测试我们的系统调用的文件:
/* file name:test.c
/*
#define __LIBRARY__
#include
#define __NR_getval
72

_syscall1(int,getval,int,val)
main()
{
  printf(“our
value=%d”,getval(10));
}


               
               
               

本文来自ChinaUnix博客,如果查看原文请点:http://blog.chinaunix.net/u/4591/showart_123749.html
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP