2.2 Vectors & IDT

[Home]  [Top]  [Previous]  [Next]


2.2.1 Overview

 

为了区分不同的InterruptsExceptions,处理器要求必须为每一个InterruptException指定一个唯一的ID。在Intel x86体系下,这个ID被规定为[0, 255]范围内的数字,这个数字ID就被称作Interrupt/Exception Vector

 

每一个Interrupt/Exception都可能会存在一个Service Routine(为了保证简洁性,我们统称ISR),这些ISR可以被无规则的放置在内存中,但它们的入口地址,却会被按照固定的格式,按照对应的Vector Number,整齐的存放在一张放置于内存中的一张表中,这张表在Real Mode下,被叫做IVTInterrupt Vector Table),在Protected Mode下,被叫做IDTInterrupt Descriptor Table),它们的格式并不相同,另外,IVT被放置在固定的位置[0H, 3FFH],而IVT却可以放在一个任意的位置,但这个位置的地址必须汇报给一个叫做IDTR的寄存器。

 

IDT/IVT都是一种数组结构。按照这种结构,当一个Interrupt/Exception发生时,CPU只需要以此Interrupt/ExceptionVector Number为索引,到IDT/IVT中查到相应的ISR入口地址并执行它就可以了。

 

下图表现的是在Real Mode下的IVT布局,IVT中的每一项占用4 bytes,用来保存指向当前Interrupt/ExceptionISR的入口地址的指针。格式为16-it segment:16-bit offset。整个IVT共有256个这样的实体,占用256*4 bytes = 1024 bytes。被放置于内存固定位置[0H,3FFH]

 

 


 

 2.2.2 Vectors in Protected Mode

 

Protected Mode下,最多会存在256个Interrupt/Exception Vectors。

 

范围[031]内的32个向量被Exception和NMI使用,但当前并非所有这32个向量都已经被使用,有几个当前没有被使用的,你也不要擅自使用它们,它们被保留,以备将来可能增加新的Exception。

 

范围[32255]内的向量被保留给用户定义的InterruptsIntel没有定义,也没有保留这些Interrupts。用户可以将它们用作外部I/O设备中断(8259A IRQ),或者System Call Software Interrupts)等。 

 

下表中定义了i386芯片Protected Mode下的Interrupt/Exception 向量表。



 

 2.2.3 IDT

 

Protected Mode下,你需要建立一张IDT(Interrupt Descriptor Table)以响应Interrupts/Exceptions

 

IDT是一个由Gate Descriptors组成的Array。每一个Gate Descriptor8个字节组成。每一个Gate Descriptor对应一个Interrupt/Exceptions Vector。由于最多可能存在256Vectors,所以你完全没有必要创建包含多于256Gate DescriptorIDT(当然你那么做不会引起错误,但却造成了浪费)。但你可以创建少于256Gate DescriptorIDT,如果你根本不需要用到那么多的话。但一旦你创建了一个IDT,但IDT中的某些Gate Descriptor你没有用到的话,你必须将其present flag清除,否则会造成异常或错误。

 

IDT可以放在RAM的任何位置,但你必须将它的起始位置的线形地址(base,32-bit),以及它的大小(limit, 16-bit)放到寄存器IDTR中。这样,CPU才能够通过IDTR知道IDT究竟放在哪儿。

 

当我们设置IDT的位置时,我们最好能将其起始位置按照8对齐,这样可以让CPU对IDT的存取性能最高。

 

IDTR寄存器长度为48 bits,包括32-bit base address16-bit limit

使用LIDTSIDT指令可以操作IDTR寄存器。LIDT(Load IDTR Register)可以将IDTR寄存器的Base addressLimit装入IDTR寄存器,这个指令只能在CPL(Current Privilege Level )为0,也就是说当前Privilege Level必须为特权等级的情况下被执行。SIDT(Store IDTR Register)可以将IDTR寄存器的内容读出并存储到某个RAM位置,这个指令可以在任何Privilege Level下被执行。

 

如果一个程序引用的Vector Number超过了IDTR中设置的IDT Limit,一个General-protection Exception会被触发。

 

我们可以用下列C++代码来实现这两个指令:

 

typedef unsigned long addr_t;

typedef unsigned long slimit_t;

 

inline void lidt(addr_t __base, slimit_t __limit)

{
   unsigned long __tmp[2];

   __tmp[0] = __limit << 16;
   __tmp[1] = (unsigned int)__base;
   __asm__ ("lidt (%0)": :"p" (((char *) __tmp)+2));

}

inline void sidt(addr_t& __base, slimit_t& __limit)

{
    unsigned long __tmp[2];
   

           __asm__ ("sidt (%0)": :"p" (((char *) __tmp)+2));

           memcpy((void*)&__base, (void*)&__tmp[1], 4);

           memcpy((void*)&__limit, (void*)(((char*) __tmp)+2), 2);

}


 2.2.4 IDT Descriptors

 

IDT中,可以包含如下3种类型的Descriptor

  • Task-gate descriptor
  • Interrupt-gate descriptor
  • Trap-gate descriptor

Interrupts/Exceptions应该使用Interrupt GateTrap Gate,它们之间的唯一区别就是:当调用Interrupt Gate时,Interrupt会被CPU自动禁止;而调用Trap Gate时,CPU则不会去禁止或打开中断,而是保留它原来的样子。

 

Task Gate一种通过硬件实现任务切换,将ISR作为一个Task的方法,我们在处理Interrupts/Excetpions的时候,通常不会用到这种方法。

 

由于Gate Descriptor是由2个32-bit字长的部分组成,我们可以定义:

 

struct descriptor_s{

        unsigned long low_dw;

        unsigned long high_dw;

};

 

由于IDT最多会有256个实体,所以我们可以定义:

 

const int  MAX_INTR_DESC_ENTITY = 256;

descriptor_s idt[MAX_INTR_DESC_ENTITY];

 

设置Interrupt Gate Descriptor和Trap Gate Descriptor,我们可以通过下面的C++代码实现:

 

typedef unsigned long addr_t;

typedef unsigned short selector_t;

 

inline void set_gate(descriptor_s& __gate_addr,  \

                              unsigned int __type, \

                              unsigned int __dpl, \

                              addr_t __addr, \

                              selector_t __sel)

{

         __gate_addr.low_dw = (__sel << 16) | (__addr&0xffff);

         __gate_addr.high_dw = (__addr&0xffff0000) | 0x8000 | (__dpl << 13) | (__type << 8 );

}

 

如果想更加高效,可以使用汇编:

 

inline void set_gate(descriptor_s& __gate_addr, \

                              unsigned int __type, \

                              unsigned int __dpl, \

                              addr_t __addr, \

                              selector_t __sel)

{

         __asm__ __volatile__ ("movw %%dx,%%ax\n\t" 
         "movw %2,%%dx\n\t" 
         "movl %%eax,%0\n\t" 
         "movl %%edx,%1" 
         :"=m" (*((long *) (__gate_addr))), 
          "=m" (*(1+(long *) (__gate_addr))) 
         :"i" ((short) (0x8000+(__dpl<<13)+(__type<<8))), 
          "d" ((char *) (__addr)),"a" (__sel << 16) 
         :"ax","dx");

}

 

下面的C++代码实现了设置Interrupt Gate和Trap Gate的3个Function。函数set_intr_gate/set_trap_gate只能被在CPL=0时调用,对于一个OS来说,只能被内核所引起的中断调用。对于只使用特权等级为0和3的OS来说,set_system_gate既可以被OS Kernel调用,又可以被Application调用。

 

const unsigned int INTR_GATE_TYPE = 0x0e;

const unsigned int TRAP_GATE_TYPE = 0x0f;

 

inline void set_intr_gate(unsigned int __n, addr_t __addr, selector_t __sel)

{
 set_gate(idt[__n], INTR_GATE_TYPE, 0, __addr, __sel);

}

 

inline void set_trap_gate(unsigned int __n, addr_t __addr, selector_t __sel)

{
 set_gate(idt[__n], TRAP_GATE_TYPE, 0, __addr, __sel);

}

 

 

inline void set_system_gate(unsigned int __n, addr_t __addr, selector_t __sel)

{
 set_gate(idt[__n], TRAP_GATE_TYPE, 3, __addr, __sel);

}