Chinaunix首页 | 论坛 | 博客
  • 博客访问: 972555
  • 博文数量: 403
  • 博客积分: 27
  • 博客等级: 民兵
  • 技术积分: 165
  • 用 户 组: 普通用户
  • 注册时间: 2011-12-25 22:20
文章分类

全部博文(403)

文章存档

2016年(3)

2015年(16)

2014年(163)

2013年(222)

分类: LINUX

2013-11-14 17:35:46

原文地址:中断分析(五) 作者:idx001

中断分析(五)
    ——初始化中断描述符表的具体实现    
中断描述符表的初始化:将中断描述符表的起始地 址装入IDTR,并初始化表中的每一项。具体过程如下图所示:
    
    
一、中断描述符表的预初始化:
用256 个指向ignore_int中断门的入口地址填充中断描述符表。它不是真正的初始化idt,等到分页和内核跳转到PAGE_OFFSET处时才真正的进行 初始化。确定所有相关的准备都已就绪之后,中断可以在任何地方发生。
setup_idt:
    lea ignore_int,%edx  //取ignore_int的有效地址到edx寄存器
    movl $(__KERNEL_CS << 16),%eax    #把内核代码段选择符左移16位,送到eax寄              存器,此时eax的高16位存放选择符。
    movw %dx,%ax        /* selector = 0x0010 = cs */ #ignore_int的有效地址存入eax的底16位。此时,eax中含有门描述符底4字节(32位)的值。
    movw $0x8E00,%dx    /* interrupt gate - dpl=0, present */  #edx中含有门描述符高4字                       节的值。

    lea SYMBOL_NAME(idt_table),%edi //取中断描述符表的地址到edx中
    mov $256,%ecx
rp_sidt:
    movl %eax,(%edi)    #//将通用的中断描述符存入表中(将ignore_int存入edi所指向的地址中)
    movl %edx,4(%edi)    #eax内容放到edi+4所指内存位置处
    addl $8,%edi          #edi指向表中下一项 
    dec %ecx
    jne rp_sidt         #条件跳转,使得idt表有256项
    Ret
Ignore_int()中断处理程序,可以看作是一个空的处理程序,它执行的主要动 作有:
1、在栈中保存一些寄存器的内容。
2、调用printk()函数打印“Unknown interrupt”系统消息。
3、 恢复栈中寄存器的内容。
4、执行iret指令,恢复被中断的程序。
ignore_int:   
    cld
    pushl %eax
    pushl %ecx
    pushl %edx
    pushl %es
    pushl %ds
    movl $(__KERNEL_DS),%eax
    movl %eax,%ds
    movl %eax,%es
    pushl $int_msg
    call SYMBOL_NAME(printk)
    popl %eax
    popl %ds
    popl %es
    popl %edx
    popl %ecx
    popl %eax
    iret
二、中断描述符表的第二遍初始化
在上述预初始化之后后,内核将在 IDT中进行第二遍初始化,用有意义的陷阱和中断处理程序替换空处理程序。第二遍处理过程完成后,针对控制单元产生的每个不同的异常,IDT都有一个专门 的陷阱门或系统门;而对于可编程控制器确认的每一个IRQ,IDT都将包含一个专门的中断门。Trap_init()函数的工作就是将一些处理异常的函数 插入到IDT的非屏蔽中断及异常表项中。
由于内核中只用到了陷阱门和系统门,因此只考虑对这两个门的初始化。Trap_init()函数用于设置 中断描述符表开头的19个陷阱门和Linux系统中唯一用到的一个系统门。这些中断向量都是CPU保留,用于异常处理的。

void __init trap_init(void)
{   
    set_trap_gate(0,÷_error);
    set_trap_gate(1,&debug);
    set_intr_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_intr_gate(14,&page_fault);
    set_trap_gate(15,&spurious_interrupt_bug);
    set_trap_gate(16,&coprocessor_error);
    set_trap_gate(17,&alignment_check);
    set_trap_gate(18,&machine_check);
    set_trap_gate(19,&simd_coprocessor_error);

    set_system_gate(SYSCALL_VECTOR,&system_call);
        }
我们可以看到 将异常处理函数插入IDT的表项是由set_trap_gate()和set_system_gate()函数来完成的。具体实现如下:
设置中断 门函数。其中,参数n表示中断号;addr是中断程序偏移地址(相对于内核代码或数le据段来说的)。
void set_intr_gate(unsigned int n, void *addr)
{
    _set_gate(idt_table+n,14,0,addr);//idt_table是中断描述符表的起始地址。Idt_table+n是中断描 述符表中中断号n对应项的偏移值。中断描述符的类型是14,特权级是0.
}

设置陷阱门函数。
static void __init set_trap_gate(unsigned int n, void *addr)
{
     _set_gate(idt_table+n,15,0,addr);    //陷阱门描述符的类型是15,特权级是0.
}
设置系统陷 阱门函数。与陷阱门不同,系统陷阱门的特权级是3,即系统陷阱门设置的中断处理过程能够被所有进程调用(如单步调试、溢出出错和超出边界出错等)
static void __init set_system_gate(unsigned int n, void *addr)
{
    _set_gate(idt_table+n,15,3,addr);//系统陷阱门描述符的类型是15,特权级是0.
}

上述门 函数都是调用_set_gate宏来实现的。该宏的具体实现如下:
   其中,参数的含义为:gate_addr—门描述符地址;type—门描述符类型域值;dpl—门的特权级;addr—偏移地址。
#define _set_gate(gate_addr,type,dpl,addr) \
do { \
  int __d0, __d1; \    #定义两个整型变量_d0,_d1;
  __asm__ __volatile__ ("movw %%dx,%%ax\n\t" \ #将偏移地址低字与选择符组合成描述符低4字节(eax);
    "movw %4,%%dx\n\t" \  #将类型标识字与偏移高字组合成描述符高4字节(edx);
    "movl %%eax,%0\n\t" \ #设置门描述符的低4字节
    "movl %%edx,%1" \    #设置门描述符的高4字节
    :"=m" (*((long *) (gate_addr))), \ #输出寄存器:%0(内存地址)指向描述符地址(偏        移值)
     "=m" (*(1+(long *) (gate_addr))), "=&a" (__d0), "=&d" (__d1) \#%1指向描述符地址强制转化为长整型加1字节后的地址处;%2(eax)输出到_d0;%3(edx)输出到_d1;
    :"i" ((short) (0x8000+(dpl<<13)+(type<<8))), \#输入寄存器:%4,dpl和type组合成类型标志字(门描述符中低2字节)
     "3" ((char *) (addr)),"2" (__KERNEL_CS << 16)); \#3表示与上面相同位置上的输出寄存器,(即%3,edx),2同理(eax),在这段代码开始运行时将__KERNEL_CS(内核代码段)左移16 位放到eax寄存器中。
} while(0)
阅读(826) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~