Chinaunix首页 | 论坛 | 博客
  • 博客访问: 657662
  • 博文数量: 151
  • 博客积分: 3498
  • 博客等级: 中校
  • 技术积分: 1570
  • 用 户 组: 普通用户
  • 注册时间: 2005-02-28 18:10
文章分类

全部博文(151)

文章存档

2014年(12)

2013年(17)

2012年(17)

2011年(5)

2010年(12)

2009年(2)

2007年(26)

2006年(22)

2005年(38)

分类: LINUX

2006-06-05 22:09:29

            中断处理和系统调用

中断处理分为硬件中断和软件异常,什么是硬件中断,磁盘,I/O设备等硬件发出数据交换请求就叫硬件中断,所以需要相应的处理程序;什么是软件异常,顾名思义软件出错误了,比如程序在一个地方被0除的时候就会产生一个错误,叫软件异常,当然也需要相应的处理程序了;

系统调用是因为应用程序需要调用内核的功能内核给用户程序留的接口。

下面的trap_initkernel/trap.c)函数用于安装相应中断号的中断处理函数;

void trap_init(void)

{

     int i;

 

     set_trap_gate(0,÷_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<48;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,¶llel_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_errorkernel/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

文本框: -------------------------------------------------------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[i] = 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默认把dses作为内核数据段,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_ptrinclude/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));

}

 

 

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