linux下X86架构IDT解析

来源:互联网 发布:jsonobject数组 编辑:程序博客网 时间:2024/06/06 06:42

一、中断描述符表IDT

1.中断描述符表IDT是一个系统表,它与每一个中断或异常向量相联系,每一个向量在表中有相应的中断或异常处理程序的入口地址。内核在允许中断发生前,必须适当的初始化IDT.

2.IDTR寄存器可以使IDT位于内存的任何地方,它制定IDT的线性基地址及其限制(最大长度)。可以通过sidt汇编语句获得IDT的基地址及限制长度。

3.表中的每一项对应一个中断或异常向量,每个向量由8个字节组成,最多需要256*8=2048个字节来存放IDT。表中的每一项由如下所示: 

116-3116位是中断处理程序所在的段选择符。

20-15位和48-64位组合起来形成32位偏移量,也就是中断处理程序所在段(16-31位给出)的段内偏移。

34043位共4位表示描述符的类型。(0111:中断描述符,1010:任务门描述符,1111:陷阱门描述符)

44546两位标识描述符的访问特权级(DPL,Descriptor Privilege Level)

547位标识段是否在内存中。如果为1则表示段当前不再内存中。

4. linux内核的中断描述符表IDT是一个全局的数据,在x86平台上被定义为:

gate_desc idt_table[NR_VECTORS] __page_aligned_bss;
#define NR_VECTORS      256

其中每一个表项均是一个desc_struct结构,该结构被定以为:

typedef struct desc_struct gate_desc;

struct desc_struct {
        union {
               struct {
                       unsigned int a;
                       unsigned int b;
               };
               struct {
                       u16 limit0;
                       u16 base0;
                       unsigned base1: 8, type: 4, s: 1, dpl: 2, p: 1;
                       unsigned limit: 4, avl: 1, l: 1, d: 1, g: 1, base2: 8;
               };
        };
} __attribute__((packed));

二、IDT初始化过程

当计算机还运行在实模式时,IDT被初始化并由BIOS使用。然而一旦linux接管,IDT就被移到RAM的另一个区域,并进行第二次初始化,因为linux没有利用任何BIOS例程。

1. 首先在head_32.S文件中会调用setup_once局部汇编初始化IDT,本文要讲的head_32.S函数定义在\arch\x86\kernel文件中。

__INIT

setup_once:

     movl $idt_table,%edi

     movl $early_idt_handlers,%eax

     movl $NUM_EXCEPTION_VECTORS,%ecx

1:

     movl %eax,(%edi)

     movl %eax,4(%edi)

     /* interrupt gate, dpl=0, present */

     movl $(0x8E000000 + __KERNEL_CS),2(%edi)

     addl $9,%eax

     addl $8,%edi

     loop 1b

     movl $256 - NUM_EXCEPTION_VECTORS,%ecx

     movl $ignore_int,%edx

     movl $(__KERNEL_CS << 16),%eax

     movw %dx,%ax         /* selector = 0x0010 = cs */

     movw $0x8E00,%dx     /* interrupt gate - dpl=0, present */

2:

     movl %eax,(%edi)

     movl %edx,4(%edi)

     addl $8,%edi

     loop 2b

这段汇编将IDT表分为两部分初始化,前一部分0- 32NUM_EXCEPTION_VECTORS)表项的偏移量的地址设为early_idt_handlers的地址,32-256的表项偏移量的地址设为ignore_int的地址。

2.第二次对IDT初始化是在start_kernel()函数中通过调用trap_init()函数对IDT进行最终初始化。调用set_trap_gate()将其初始化为陷阱门,调用set_intr_gate()将其初始化为中断门,调用set_system_gate()将其初始化为访问特权级为3的陷阱门,调用set_task_gate()将其初始化为任务门,调用set_system_intr_gate()将其初始化为访问特权级为3的中断门。

void __init trap_init(void)
{
726         int i;
736         set_intr_gate(X86_TRAP_DE, &divide_error);
737         set_intr_gate_ist(X86_TRAP_NMI, &nmi, NMI_STACK);
739         set_system_intr_gate(X86_TRAP_OF, &overflow);
740         set_intr_gate(X86_TRAP_BR, &bounds);
741         set_intr_gate(X86_TRAP_UD, &invalid_op);
742         set_intr_gate(X86_TRAP_NM, &device_not_available);
743 #ifdef CONFIG_X86_32
744         set_task_gate(X86_TRAP_DF, GDT_ENTRY_DOUBLEFAULT_TSS);
745 #else
746         set_intr_gate_ist(X86_TRAP_DF, &double_fault, DOUBLEFAULT_STACK);
747 #endif
748         set_intr_gate(X86_TRAP_OLD_MF, &coprocessor_segment_overrun);
749         set_intr_gate(X86_TRAP_TS, &invalid_TSS);
750         set_intr_gate(X86_TRAP_NP, &segment_not_present);
751         set_intr_gate_ist(X86_TRAP_SS, &stack_segment, STACKFAULT_STACK);
752         set_intr_gate(X86_TRAP_GP, &general_protection);
753         set_intr_gate(X86_TRAP_SPURIOUS, &spurious_interrupt_bug);
754         set_intr_gate(X86_TRAP_MF, &coprocessor_error);
755         set_intr_gate(X86_TRAP_AC, &alignment_check);
759         set_intr_gate(X86_TRAP_XF, &simd_coprocessor_error);
760 
761         /* Reserve all the builtin and the syscall vector: */
762         for (i = 0; i < FIRST_EXTERNAL_VECTOR; i++)
763                 set_bit(i, used_vectors);
764 
765 #ifdef CONFIG_IA32_EMULATION
766         set_system_intr_gate(IA32_SYSCALL_VECTOR, ia32_syscall);
767         set_bit(IA32_SYSCALL_VECTOR, used_vectors);
768 #endif
769 
770 #ifdef CONFIG_X86_32
771         set_system_trap_gate(SYSCALL_VECTOR, &system_call);
772         set_bit(SYSCALL_VECTOR, used_vectors);
773 #endif
774 
775         /*
776          * Set the IDT descriptor to a fixed read-only location, so that          * the "sidt" instruction will not leak the location of the kernel, and
778          * to defend the IDT against arbitrary memory write vulnerabilities.
779          * It will be reloaded in cpu_init() */
780         __set_fixmap(FIX_RO_IDT, __pa_symbol(idt_table), PAGE_KERNEL_RO);
781         idt_descr.address = fix_to_virt(FIX_RO_IDT);
782 
783         /*
784          * Should be a barrier for any external CPU state:
785          */
786         cpu_init();
787 
788         x86_init.irqs.trap_init();
789 
795 }

3.对剩余表项的初始化由init_IRQ()完成。init_IRQ()通过调用native_init_IRQ()函数完成初始化。

void __init native_init_IRQ(void)
193 {
194         int i;
195 
196         /* Execute any quirks before the call gates are initialised: */
197         x86_init.irqs.pre_vector_init();
198 
199         apic_intr_init();
200 
201         /*
202          * Cover the whole vector space, no vector can escape
203          * us. (some of these will be overridden and become
204          * 'special' SMP interrupts)
205          */
206         i = FIRST_EXTERNAL_VECTOR;
207         for_each_clear_bit_from(i, used_vectors, NR_VECTORS) {
               /* IA32_SYSCALL_VECTOR could be used in trap_init already. */
209                 set_intr_gate(i, interrupt[i - FIRST_EXTERNAL_VECTOR]);
210         }
211 
212         if (!acpi_ioapic && !of_ioapic)
213                 setup_irq(2, &irq2);
214 
215 #ifdef CONFIG_X86_32
216         irq_ctx_init(smp_processor_id());
217 #endif
218 }

可以看出调用set_intr_gate()将剩下的233项全部初始化为中断门。其中的NR_VECTOR256FIRST_EXTERNAL_VECTOR0x20(也就是32),所以循环共进行224次,其中当要初始化的中断号等于SYSCALL_VECTOR(系统调用中断号)时,就跳过,所以也就是前面说的初始化剩下的223项。现在的关键是给这些中断的处理程序是什么?这又要说到interrupt数组了。该数组定义在arch/x86_64/kernel/i8259.c中:

#define IRQ(x,y)  IRQ##x##y##_interrupt

#define IRQLIST_16(x) \

IRQ(x,0), IRQ(x,1), IRQ(x,2), IRQ(x,3), \

IRQ(x,4), IRQ(x,5), IRQ(x,6), IRQ(x,7), \

IRQ(x,8), IRQ(x,9), IRQ(x,a), IRQ(x,b), \

IRQ(x,c), IRQ(x,d), IRQ(x,e), IRQ(x,f)

/* for the irq vectors */

static void (*interrupt[NR_VECTORS - FIRST_EXTERNAL_VECTOR])(void) = {

IRQLIST_16(0x2), IRQLIST_16(0x3),

IRQLIST_16(0x4), IRQLIST_16(0x5), IRQLIST_16(0x6), IRQLIST_16(0x7),

IRQLIST_16(0x8), IRQLIST_16(0x9), IRQLIST_16(0xa), IRQLIST_16(0xb),

IRQLIST_16(0xc), IRQLIST_16(0xd), IRQLIST_16(0xe), IRQLIST_16(0xf)

};

现在结合native_init_IRQ()分析一下。当进行第一次循环也就是i=0的时候,vector=32,所以set_intr_gate()的调用是:

set_intr_gate(32,interrupt[0]);

对应的是:

IRQLIST_16(0x2)

--->IRQ(0x2,0)

   |--->IRQ0x20_interrupt()

这样就将IRQ0x20_interrupt()设置为0x20中断的处理程序了。

下来我们看看这些IRQn_interrupt()(n=0x20~0xff)是如何建立的。

#define BUILD_IRQ(nr) \

asmlinkage void IRQ_NAME(nr); \

__asm__( \

"\n.p2align\n" \

"IRQ" #nr "_interrupt:\n\t" \

"push $~(" #nr ") ; " \

"jmp common_interrupt");

common_interrupt是一个汇编标号,在它里面会调用do_IRQ()函数去处理中断。

三、中断处理流程

1.确定与中断或异常关联的向量i.

2.读取由IDTR寄存器指向的IDT表中的第i项中断描述符。

3.GDTR寄存器获得GDT的基地址,并在GDT中查找,以获取IDT表中的第i项中断描述符的段选择符。

4.特权级比较,以及一些入栈保护操作。

5.装载cseip寄存器,其值分别为IDT表中第i项门的段选择符合偏移量字段。这样就可以转到对应的中断处理程序IRQn_interrupt执行(就是对IDT初始化的中断函数)。

6.对应的中断处理程序IRQn_interrupt最后都会调用do_IRQ(i).再执行用户程序设置的中断处理函数。

如图所示:

0 0
原创粉丝点击