2.2 Vectors & IDT
[Home] [Top] [Previous] [Next]
2.2.1 Overview
为了区分不同的Interrupts和Exceptions,处理器要求必须为每一个Interrupt和Exception指定一个唯一的ID。在Intel x86体系下,这个ID被规定为[0, 255]范围内的数字,这个数字ID就被称作Interrupt/Exception Vector。
每一个Interrupt/Exception都可能会存在一个Service Routine(为了保证简洁性,我们统称ISR),这些ISR可以被无规则的放置在内存中,但它们的入口地址,却会被按照固定的格式,按照对应的Vector Number,整齐的存放在一张放置于内存中的一张表中,这张表在Real Mode下,被叫做IVT(Interrupt Vector Table),在Protected Mode下,被叫做IDT(Interrupt Descriptor Table),它们的格式并不相同,另外,IVT被放置在固定的位置[0H, 3FFH],而IVT却可以放在一个任意的位置,但这个位置的地址必须汇报给一个叫做IDTR的寄存器。
IDT/IVT都是一种数组结构。按照这种结构,当一个Interrupt/Exception发生时,CPU只需要以此Interrupt/Exception的Vector Number为索引,到IDT/IVT中查到相应的ISR入口地址并执行它就可以了。
下图表现的是在Real Mode下的IVT布局,IVT中的每一项占用4 bytes,用来保存指向当前Interrupt/Exception的ISR的入口地址的指针。格式为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。
范围[0,31]内的32个向量被Exception和NMI使用,但当前并非所有这32个向量都已经被使用,有几个当前没有被使用的,你也不要擅自使用它们,它们被保留,以备将来可能增加新的Exception。
范围[32,255]内的向量被保留给用户定义的Interrupts。Intel没有定义,也没有保留这些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 Descriptor由8个字节组成。每一个Gate Descriptor对应一个Interrupt/Exceptions Vector。由于最多可能存在256个Vectors,所以你完全没有必要创建包含多于256个Gate Descriptor的IDT(当然你那么做不会引起错误,但却造成了浪费)。但你可以创建少于256个Gate Descriptor的IDT,如果你根本不需要用到那么多的话。但一旦你创建了一个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 address和16-bit limit。
使用LIDT和SIDT指令可以操作IDTR寄存器。LIDT(Load IDTR Register)可以将IDTR寄存器的Base address和Limit装入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:
Interrupts/Exceptions应该使用Interrupt Gate和Trap 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);
}