Linux 0.01 中断部分简要及注释
Willweb 于4.10
中断是linux 系统中的一个重要并且复杂的组成部分,它提供给外设一种运行cpu 的方
式,一个完善的支持外设扩充的系统中,由于有着多种多样的外部设备,这些设备通过中断
方式来平衡速度和获得资源。
中断可以分为cpu 的内部中断和外设的外部中断两种,cpu 的内部中断又可以叫做异常,
异常的主要作用是报告一些程序运行过程中的错误和处理缺页中断(page_fault),这些异
常都是通过set_trap_gate 来设置的,intel 的cpu 为trap 保留了32 个,具体在idt 中的
号是从00 到20。外部设备中断是通过set_intr_gate 来设置的,它从21 开始到ff。另外
还有一种软件中断,也就是前一次讲到的系统调用,它是在用户端可以调用的。
Linux2。4 的中断处理比较复杂,因为涉及的设备比较多,同时顾及到许多处理的问题,
但是0。01 由于比较简单,没有处理很复杂的操作,它里面主要做了几个部分的工作:
1. Trap.s 将各个cpu 的trap 信息填入到idt 中
2. Asm.s 处理当出现trap 的时候后续的一系列处理
3. 另外,0。01 中的处理的外部中断有time(0x20),串口(0x23 和0x24),硬盘(0x2e),
键盘(0x21),具体的处理函数在rs_io.s,hd.c 和keyboard.s 中
linux 下都是采用两片8259 作为接收外部的中断控制器,其初始化是在boot.s 中初始
化的,具体的连接是8259 的从片接主片的第二个中断口,其他的中断线就可以通过其他设
备共享,到外设产生一个中断之后,8259 自己将irq 号转换为中断向量(一般是加上20),
然后发给cpu 并进行等待,当cpu 应该之后再清intr 线,cpu 接收到中断之后在内核堆栈
中保留irq 值和寄存器值,同时发送一个pic 应答并执行isr 中断服务程序。
如果多个设备共享一个中断,那么每当一个设备产生一个中断的时候cpu 会执行所有的
isr 中断服务程序。具体的一个完整的中断产生和处理流程是:
产生中断――>cpu 应答――>查找idt 中的对应向量――>在gdt 中查找idt 项的代码
段――>对比当前的cpl 和描述符的dpl 看是否产生越级保护――>检查是否发生特权级的
变化,如果是就保存ss 和esp,否则不保存――>保存eflags、cs、eip 和错误码――>将
idt 对应描述符地址装入cs 和eip 中以便执行――>执行irq_interrupt――>执行
do_irq――>循环执行isr――>返回
/KERNEL/TRAPS.C 简要注释
/*
* 'Traps.c' handles hardware traps and faults after we have saved some
* state in 'asm.s'. Currently mostly a debugging-aid, will be extended
* to mainly kill the offending process (probably by giving it a signal,
* but possibly by killing it outright if necessary).
*/
#include
#include
#include
#include
#include
#include
//得到段寄存器中addr 地址的数据,返回一个字节
#define get_seg_byte(seg,addr) ({ \
register char __res; \
__asm__("push %%fs;mov %%ax,%%fs;movb %%fs:%2,%%al;pop %%fs" \
:"=a" (__res):"0" (seg),"m" (*(addr))); \
__res;})
//返回四个字节的addr 地址的段寄存器内容
#define get_seg_long(seg,addr) ({ \
register unsigned long __res; \
__asm__("push %%fs;mov %%ax,%%fs;movl %%fs:%2,%%eax;pop %%fs" \
:"=a" (__res):"0" (seg),"m" (*(addr))); \
__res;})
//取得fs 段寄存器的内容
#define _fs() ({ \
register unsigned short __res; \
__asm__("mov %%fs,%%ax":"=a" (__res); \
__res;})
//当出现程序内部错误的时候(例如除0 等异常),打印出cpu 中各个寄存器的值然后退出程
序
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 */
}
//异常处理程序
void do_double_fault(long esp, long error_code)
{
die("double fault",esp,error_code);
}
void do_general_protection(long esp, long error_code)
{
die("general protection",esp,error_code);
}
void do_divide_error(long esp, long error_code)
{
die("divide error",esp,error_code);
}
//do_int3 处理断点的trap,具体是将所有的寄存器值打印出来
void do_int3(long * esp, long error_code,
long fs,long es,long ds,
long ebp,long esi,long edi,
long edx,long ecx,long ebx,long eax)
{
int tr;
__asm__("str %%ax":"=a" (tr):"0" (0));
printk("eax\t\tebx\t\tecx\t\tedx\n\r%8x\t%8x\t%8x\t%8x\n\r",
eax,ebx,ecx,edx);
printk("esi\t\tedi\t\tebp\t\tesp\n\r%8x\t%8x\t%8x\t%8x\n\r",
esi,edi,ebp,(long) esp);
printk("\n\rds\tes\tfs\ttr\n\r%4x\t%4x\t%4x\t%4x\n\r",
ds,es,fs,tr);
printk("EIP: %8x CS: %4x EFLAGS: %8x\n\r",esp[0],esp[1],esp[2]);
}
//以下的trap 操作转化为die 函数操作
void do_nmi(long esp, long error_code)
{
die("nmi",esp,error_code);
}
void do_debug(long esp, long error_code)
{
die("debug",esp,error_code);
}
void do_overflow(long esp, long error_code)
{
die("overflow",esp,error_code);
}
void do_bounds(long esp, long error_code)
{
die("bounds",esp,error_code);
}
void do_invalid_op(long esp, long error_code)
{
die("invalid operand",esp,error_code);
}
void do_device_not_available(long esp, long error_code)
{
die("device not available",esp,error_code);
}
void do_coprocessor_segment_overrun(long esp, long error_code)
{
die("coprocessor segment overrun",esp,error_code);
}
void do_invalid_TSS(long esp,long error_code)
{
die("invalid TSS",esp,error_code);
}
void do_segment_not_present(long esp,long error_code)
{
die("segment not present",esp,error_code);
}
void do_stack_segment(long esp,long error_code)
{
die("stack segment",esp,error_code);
}
void do_coprocessor_error(long esp, long error_code)
{
die("coprocessor error",esp,error_code);
}
void do_reserved(long esp, long error_code)
{
die("reserved (15,17-31) error",esp,error_code);
}
//初始化trap 信息,将前面的32 个idt 填入异常处理函数的地址
void trap_init(void)
{
int i;
//系统门和陷阱门的类型都是1111(15),而中断门类型是1110(14),但是中断门和陷阱门的
dpl 都是0,而系统门的dpl 是3
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<32;i++)
set_trap_gate(i,&reserved);
/* __asm__("movl $0x3ff000,%%eax\n\t"
"movl %%eax,%%db0\n\t"
"movl $0x000d0303,%%eax\n\t"
"movl %%eax,%%db7"
:::"ax");*/
}
/KERNEL/ASM.S 具体的异常处理程序
/*
* 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 _device_not_available,_double_fault,_coprocessor_segment_overrun
.globl _invalid_TSS,_segment_not_present,_stack_segment
.globl _general_protection,_coprocessor_error,_reserved
//这里的asm.s 的处理流程是在每个处理函数中先把处理函数的地址压栈,然后调用
no_error_code 或error_code,在no_error_code 或error_code 中调用压入的地址再执行具
体的函数
_divide_error:
pushl $_do_divide_error
no_error_code:
//eax 得到压入的函数地址
xchgl %eax,(%esp)
//保存各个寄存器变量
pushl %ebx
pushl %ecx
pushl %edx
pushl %edi
pushl %esi
pushl %ebp
push %ds
push %es
//压入一个错误码,由于这里是no_error_code 那么错误码是0
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
//每一个处理函数的流程都一样,先压入一个处理函数地址,然后在转跳到no_error_code
执行
_debug:
pushl $_do_int3 # _do_debug
jmp no_error_code
_nmi:
pushl $_do_nmi
jmp no_error_code
_int3:
pushl $_do_int3
jmp no_error_code
_overflow:
pushl $_do_overflow
jmp no_error_code
_bounds:
pushl $_do_bounds
jmp no_error_code
_invalid_op:
pushl $_do_invalid_op
jmp no_error_code
math_emulate:
popl %eax //弹出eax,因为在调用math_emulate 的时候push 了eax
pushl $_do_device_not_available
jmp no_error_code
_device_not_available:
pushl %eax
movl %cr0,%eax
//测试是否需要模拟协处理器
bt $2,%eax # EM (math emulation bit)
jc math_emulate
//如果不模拟的话,就用协处理器
clts # clear TS so that we can use math
movl _current,%eax
cmpl _last_task_used_math,%eax
je 1f # shouldn't happen really ...
pushl %ecx
pushl %edx
push %ds
movl $0x10,%eax
mov %ax,%ds
call _math_state_restore
pop %ds
popl %edx
popl %ecx
1: popl %eax
iret
_coprocessor_segment_overrun:
pushl $_do_coprocessor_segment_overrun
jmp no_error_code
_reserved:
pushl $_do_reserved
jmp no_error_code
_coprocessor_error:
pushl $_do_coprocessor_error
jmp no_error_code
//如果有错误码的话,那么在执行对应的函数之前自动会把错误码压入的
_double_fault:
pushl $_do_double_fault
error_code:
xchgl %eax,4(%esp) # error code <-> %eax
xchgl %ebx,(%esp) # &function <-> %ebx
//保存寄存器变量
pushl %ecx
pushl %edx
pushl %edi
pushl %esi
pushl %ebp
push %ds
push %es
push %fs
//这里压入的是错误码而不是0 了
pushl %eax # error code
lea 44(%esp),%eax # offset
pushl %eax
movl $0x10,%eax
mov %ax,%ds
mov %ax,%es
mov %ax,%fs
//调用处理函数
call *%ebx
addl $8,%esp
pop %fs
pop %es
pop %ds
popl %ebp
popl %esi
popl %edi
popl %edx
popl %ecx
popl %ebx
popl %eax
iret
_invalid_TSS:
pushl $_do_invalid_TSS
jmp error_code
_segment_not_present:
pushl $_do_segment_not_present
jmp error_code
_stack_segment:
pushl $_do_stack_segment
jmp error_code
_general_protection:
pushl $_do_general_protection
jmp error_code