总体来说,中断相关的汇编代码有两个,asm.s,systemcall.s ,其中定义了中断发生前的相关参数入栈,调用的C函数入口地址入栈,中断发生后的恢复。而各种中断的C函数代码分布在不同的C文件中。如下表所示。

中断前处理,调用,中断后恢复 执行中断函数的C文件
硬中断的处理 asm.s trap.c
软件及系统调用的处理 system_call.s fork.c signal.c exit.c sys.c

比如,asm.s中包含了各种错误的中断相关汇编代码。其中具体实现又调用了c的代码,使用了各种do函数,这些do函数在traps.c文件中有定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/*
 *  linux/kernel/asm.s
 *
 *  (C) 1991  Linus Torvalds
 */

/*
 * asm.s contains the low-level code for most hardware faults.
 * page_exception is handled by the mm, so that isn't here. This
 * file also handles (hopefully) fpu-exceptions due to TS-bit, as
 * the fpu must be properly saved/resored. This hasn't been tested.
 */

.globl _divide_error,_debug,_nmi,_int3,_overflow,_bounds,_invalid_op
.globl _double_fault,_coprocessor_segment_overrun
.globl _invalid_TSS,_segment_not_present,_stack_segment
.globl _general_protection,_coprocessor_error,_irq13,_reserved

_divide_error:
pushl $_do_divide_error

比如divide_error除法错误的中断,就调用了do_divide_error函数,这个在traps.c中定义如下

1
2
3
4
void do_divide_error(long esp, long error_code)
{
die("divide error",esp,error_code);
}

其中又调用了die函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
static void die(char * str,long esp_ptr,long nr)
{
long * esp = (long *) esp_ptr;
int i;

printk("%s: %04x\n\r",str,nr&0xffff);
printk("EIP:\t%04x:%p\nEFLAGS:\t%p\nESP:\t%04x:%p\n",
esp[1],esp[0],esp[2],esp[4],esp[3]);
printk("fs: %04x\n",_fs());
printk("base: %p, limit: %p\n",get_base(current->ldt[1]),get_limit(0x17));
if (esp[4] == 0x17) {
printk("Stack: ");
for (i=0;i<4;i++)
printk("%p ",get_seg_long(0x17,i+(long *)esp[3]));
printk("\n");
}
str(i);
printk("Pid: %d, process nr: %d\n\r",current->pid,0xffff & i);
for(i=0;i<10;i++)
printk("%02x ",0xff & get_seg_byte(esp[1],(i+(char *)esp[0])));
printk("\n\r");
do_exit(11); /* play segment exception */
}

die函数在traps.c中定义,入口参数有三个:一个字符串,一个栈顶指针(ESP为8086体系中的栈顶指针),一个(段号,在哪里出错的段号)

die函数的具体内容:首先,3:10打印一些入栈的参数。然后,11:21打印栈里的一些内容,get_seg_long函数是从段里取一个4字节的整数。do_exit帮忙退出。

整体来说,die函数就是打印一些栈的信息,错误号(中断号),可以当成一个print函数

在traps.c中基本都是背调用的中断的函数,最后有一个trap_init函数,比较特殊

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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<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,&parallel_interrupt);
}

这个函数是整个中断的初始化函数,主要由两个函数组成,set_trap_gate和set_system_gate。这两个函数主要是权限不同,set_trap_gate优先级为0,只能由用户程序产生,set_system_gate优先级为3,能被所有的程序产生,用户与系统都可以调用

1
2
3
4
#define set_trap_gate(n,addr) \
_set_gate(&idt[n],15,0,addr) # idt 为中断描述表
#define set_system_gate(n,addr) \
_set_gate(&idt[n],15,3,addr)

大部分中断函数只进行了打印,而没有真正的实现
在中断发生的时候,操作系统做了什么?

每一个进程,操作系统会分配一些内存,由代码段,数据段(程序的全局变量等),TSS段程序状态段(状态码),栈(临时运行,调用的参数)。

上面提到的asm.s里面的入栈,出栈,就是这个栈。比如CPU通用寄存器的数据,函数入口地址等临时放到了栈里。

但是system_call.s的调用中断函数的过程和asm不同,asm是把函数的入口地址放到了寄存器中,而system_call是放到了sys_call_table中,所有的系统调用C函数放到了这里面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
/*
 *  linux/kernel/system_call.s
 *
 *  (C) 1991  Linus Torvalds
 */

/*
 *  system_call.s  contains the system-call low-level handling routines.
 * This also contains the timer-interrupt handler, as some of the code is
 * the same. The hd- and flopppy-interrupts are also here.
 *
 * NOTE: This code handles signal-recognition, which happens every time
 * after a timer-interrupt and after each system call. Ordinary interrupts
 * don't handle signal-recognition, as that would clutter them up totally
 * unnecessarily.
 *
 * Stack layout in 'ret_from_system_call':
 *
 * 0(%esp) - %eax
 * 4(%esp) - %ebx
 * 8(%esp) - %ecx
 * C(%esp) - %edx
 * 10(%esp) - %fs
 * 14(%esp) - %es
 * 18(%esp) - %ds
 * 1C(%esp) - %eip
 * 20(%esp) - %cs
 * 24(%esp) - %eflags
 * 28(%esp) - %oldesp
 * 2C(%esp) - %oldss
 */

SIG_CHLD = 17

EAX = 0x00
EBX = 0x04
ECX = 0x08
EDX = 0x0C
FS = 0x10
ES = 0x14
DS = 0x18
EIP = 0x1C
CS = 0x20
EFLAGS = 0x24
OLDESP = 0x28
OLDSS = 0x2C

state = 0 # these are offsets into the task-struct.
counter = 4
priority = 8
signal = 12
sigaction = 16 # MUST be 16 (=len of sigaction)
blocked = (33*16)

# offsets within sigaction
sa_handler = 0
sa_mask = 4
sa_flags = 8
sa_restorer = 12

nr_system_calls = 72

/*
 * Ok, I get parallel printer interrupts while using the floppy for some
 * strange reason. Urgel. Now I just ignore them.
 */
.globl _system_call,_sys_fork,_timer_interrupt,_sys_execve
.globl _hd_interrupt,_floppy_interrupt,_parallel_interrupt
.globl _device_not_available, _coprocessor_error

.align 2
bad_sys_call:
movl $-1,%eax
iret
.align 2
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

eax中放的是系统调用的操作码,sys_call_table在sys.h中,里面放了所有的系统调用

1
2
3
4
5
6
7
8
9
10
11
12
13
fn_ptr sys_call_table[] = { sys_setup, sys_exit, sys_fork, sys_read,
sys_write, sys_open, sys_close, sys_waitpid, sys_creat, sys_link,
sys_unlink, sys_execve, sys_chdir, sys_time, sys_mknod, sys_chmod,
sys_chown, sys_break, sys_stat, sys_lseek, sys_getpid, sys_mount,
sys_umount, sys_setuid, sys_getuid, sys_stime, sys_ptrace, sys_alarm,
sys_fstat, sys_pause, sys_utime, sys_stty, sys_gtty, sys_access,
sys_nice, sys_ftime, sys_sync, sys_kill, sys_rename, sys_mkdir,
sys_rmdir, sys_dup, sys_pipe, sys_times, sys_prof, sys_brk, sys_setgid,
sys_getgid, sys_signal, sys_geteuid, sys_getegid, sys_acct, sys_phys,
sys_lock, sys_ioctl, sys_fcntl, sys_mpx, sys_setpgid, sys_ulimit,
sys_uname, sys_umask, sys_chroot, sys_ustat, sys_dup2, sys_getppid,
sys_getpgrp, sys_setsid, sys_sigaction, sys_sgetmask, sys_ssetmask,
sys_setreuid,sys_setregid };