我是靠谱客的博主 纯情大山,这篇文章主要介绍[fw]LINUX中断描述符初始化,现在分享给大家,希望可以做个参考。

LINUX中断描述符初始化

@CopyLeft by ICANTHI Can do ANy THing that I CAN THink~

Author WenHui WuHan University2012-6-4

硬件产生中断之后,需要通过门描述符来寻找中断的处理程序入口。门描述符和段描述符一样,8个字节。门描述符大体分为:段偏移、段选择子以及DPL。段选择子用于在GDT中寻找到门段基址,DPL用于控制当前进程中断或异常访问权限。当发生中断时,将门描述符所指向的段基地放入%cs,将段偏移放入%eip,转入相应服务。

门描述符结构如下:

clip_image002

任务门描述符:用于在发生中断时调度相应进程

中断门描述符:描述中断处理程序所在段选择子和段内偏移值。据此修改EIP及关闭中断Eflags的IT标识位

陷阱门描述符:与中断门描述符一样,但不关中断

中断描述符表IDT(Interrupt Descriptor Table)用于存放256个门描述符,对应256个中断向量。其中IA32规定前0~31号固定向量用于异常。寄存器idtr为门描述符表IDT的物理地址。根据向量寻找基对应门描述符,并将中断或异常程序加载过程下图所示:

clip_image004

LINUX初始化中断描述符表分两步:初步初始化,以及最终初始化。初步初始化在head.S文件:http://os1a.cs.columbia.edu/lxr/source/arch/x86/kernel/head_32.S

中断描述符表初步初始化

1)声明256个门描述符的IDT表空间。

复制代码
1
701 idt_descr:
复制代码
1
702         .word IDT_ENTRIES*8-1           # idt contains 256 entries
复制代码
1
703         .long idt_table

2)设置指向IDT表地址的寄存器ldtr

复制代码
1
425         lgdt early_gdt_descr
复制代码
1
426         lidt idt_descr
复制代码
1
427         ljmp $(__KERNEL_CS),$1f
复制代码
1
428 1:      movl $(__KERNEL_DS),%eax        # reload all the segment registers

2)初始化256个门描述符。对于每个门描述符,段选择子都指向内核段,段偏移都指向igore_int函数,该函数只打印一句话:“哥们,别急,俺还没被真正初始化勒!~”。

复制代码
1
490 /*
复制代码
1
491  *  setup_idt
复制代码
1
492  *
复制代码
1
493  *  sets up a idt with 256 entries pointing to
复制代码
1
494  *  ignore_int, interrupt gates. It doesn't actually load
复制代码
1
495  *  idt - that can be done only after paging has been enabled
复制代码
1
496  *  and the kernel moved to PAGE_OFFSET. Interrupts
复制代码
1
497  *  are enabled elsewhere, when we can be relatively
复制代码
1
498  *  sure everything is ok.
复制代码
1
499  *
复制代码
1
500  *  Warning: %esi is live across this function.
复制代码
1
501  */
复制代码
1
502 setup_idt:
复制代码
1
503         lea ignore_int,%edx
复制代码
1
504         movl $(__KERNEL_CS << 16),%eax
复制代码
1
505         movw %dx,%ax            /* selector = 0x0010 = cs */
复制代码
1
506         movw $0x8E00,%dx        /* interrupt gate - dpl=0, present */
复制代码
1
507
复制代码
1
508         lea idt_table,%edi
复制代码
1
509         mov $256,%ecx
复制代码
1
510 rp_sidt:
复制代码
1
511         movl %eax,(%edi)
复制代码
1
512         movl %edx,4(%edi)
复制代码
1
513         addl $8,%edi
复制代码
1
514         dec %ecx
复制代码
1
515         jne rp_sidt
中断描述符表最终初始化

中断描述符表最终初始化分为两部分:异常和中断,分别在函数trap_init(void)和init_IRQ(void)中实现,都由系统初始化入口函数start_kernel()所调用。对于特定异常,其处理异常函数预先已经设定好;但对于特定异步中断,由于需要捕获设备I/O中断的程序数目不定,所以得采用特定数据结构来对irq及其action进行描述。

trap_init:http://os1a.cs.columbia.edu/lxr/source/arch/x86/kernel/traps.c#830

do_IRQ:

1)异常部分最终初始化

由于IA32规定异常所对应的固定向量,所以直接调用set_trap_gate()、set_intr_gate()、set_system_gate()、set_system_intr_gate()、set_task_gate()、set_intr_gate_ist()设置异常处理函数、初始相应异常。而这些函数,都只是_set_gate宏的封装而已。异常处理函数由宏定义DO_ERROR生成。其调用关系如下:

clip_image006

在trap_init(void)函数中,调用在set_intr_gate_ist()设置“stack exception”的12号中断门描述符。set_intr_gate_ist是设置中断描述符函数_set_gate的一层封装,给_set_gate传入异常处理函数stack_segment作为门描述符的偏移地址,传入__KERNEL_CS作为段选择子,传入GATE_INTERRUPT表示中断门描述符类型,传入DPL = 0表示只能由内核态访问、而不能由用户调用。

_set_gate函数中,其调用pack_gate()组装成一个门描述符格式,并调用write_idt_entry写入IDT表中相应描述符。stack_segment函数压入do_stack_segment函数地址并跳转到error_code汇编代码进行处理。具体代码(/arch/x86/include/asm/desc.h)如下:

复制代码
1
830 void __init trap_init(void)
复制代码
1
831 {
复制代码
1
857         set_intr_gate_ist(12, &stack_segment, STACKFAULT_STACK);
复制代码
1
898         x86_init.irqs.trap_init(); /* 初始化该平台x86的特定设备 */
复制代码
1
899 }
复制代码
1
 
复制代码
1
383 static inline void set_intr_gate_ist(int n, void *addr, unsigned ist)
复制代码
1
384 {
复制代码
1
385         BUG_ON((unsigned)n > 0xFF);
复制代码
1
386         _set_gate(n, GATE_INTERRUPT, addr, 0, ist, __KERNEL_CS);
复制代码
1
387 }
复制代码
1
 
复制代码
1
312 static inline void _set_gate(int gate, unsigned type, void *addr,
复制代码
1
313                              unsigned dpl, unsigned ist, unsigned seg)
复制代码
1
314 {
复制代码
1
315         gate_desc s;
复制代码
1
316         pack_gate(&s, type, (unsigned long)addr, dpl, ist, seg);
复制代码
1
317         /*
复制代码
1
318          * does not need to be atomic because it is only done once at
复制代码
1
319          * setup time
复制代码
1
320          */
复制代码
1
321         write_idt_entry(idt_table, gate, &s);
复制代码
1
322 }
复制代码
1
 
复制代码
1
064 static inline void pack_gate(gate_desc *gate, unsigned char type,
复制代码
1
065                              unsigned long base, unsigned dpl, unsigned flags,
复制代码
1
066                              unsigned short seg)
复制代码
1
067 {
复制代码
1
068         gate->a = (seg << 16) | (base & 0xffff);
复制代码
1
069         gate->b = (base & 0xffff0000) |
复制代码
1
070                   (((0x80 | type | (dpl << 5)) & 0xff) << 8);
复制代码
1
071 }
复制代码
1
 
复制代码
1
115 static inline void native_write_idt_entry(gate_desc *idt, int entry,
复制代码
1
116                                           const gate_desc *gate)
复制代码
1
117 {
复制代码
1
118         memcpy(&idt[entry], gate, sizeof(*gate));
复制代码
1
119 }
复制代码
1
 
复制代码
1
然异常12号处理者stack_segment,何许人也?在/arch/x86/kernel/entry_32.S中定义如下:
复制代码
1
1014 ENTRY(stack_segment)
复制代码
1
1015         RING0_EC_FRAME
复制代码
1
1016         pushl $do_stack_segment
复制代码
1
1017         CFI_ADJUST_CFA_OFFSET 4
复制代码
1
1018         jmp error_code
复制代码
1
1019         CFI_ENDPROC
复制代码
1
1020 END(stack_segment)

有的异常,例如stack_segment,系统由硬件产生错误码并压入栈中。另外一些异常,系统将不产生异常,例如overflow,为了统一中断、产生错误码的异常和不产生错误码的异常的栈内存布局,故“人为地”pushl $0。

复制代码
1
958 ENTRY(overflow)
复制代码
1
959         RING0_INT_FRAME
复制代码
1
960         pushl $0
复制代码
1
961         CFI_ADJUST_CFA_OFFSET 4
复制代码
1
962         pushl $do_overflow
复制代码
1
963         CFI_ADJUST_CFA_OFFSET 4
复制代码
1
964         jmp error_code
复制代码
1
965         CFI_ENDPROC
复制代码
1
966 END(overflow)

在error_code中,首先保存中断寄存器上下文等,其次调用do_stack_segment进行处理,最后调用ret_from_exception进行善后工作,代码如下:

复制代码
1
1291 error_code:
复制代码
1
1292         /* the function address is in %gs's slot on the stack */
复制代码
1
1293         pushl %fs
复制代码
1
1294         CFI_ADJUST_CFA_OFFSET 4
复制代码
1
1295         /*CFI_REL_OFFSET fs, 0*/
复制代码
1
1296         pushl %es
复制代码
1
1297         CFI_ADJUST_CFA_OFFSET 4
复制代码
1
1298         /*CFI_REL_OFFSET es, 0*/
复制代码
1
1299         pushl %ds
复制代码
1
1300         CFI_ADJUST_CFA_OFFSET 4
复制代码
1
1301         /*CFI_REL_OFFSET ds, 0*/
复制代码
1
1302         pushl %eax
复制代码
1
1303         CFI_ADJUST_CFA_OFFSET 4
复制代码
1
1304         CFI_REL_OFFSET eax, 0
复制代码
1
1305         pushl %ebp
复制代码
1
1306         CFI_ADJUST_CFA_OFFSET 4
复制代码
1
1307         CFI_REL_OFFSET ebp, 0
复制代码
1
1308         pushl %edi
复制代码
1
1309         CFI_ADJUST_CFA_OFFSET 4
复制代码
1
1310         CFI_REL_OFFSET edi, 0
复制代码
1
1311         pushl %esi
复制代码
1
1312         CFI_ADJUST_CFA_OFFSET 4
复制代码
1
1313         CFI_REL_OFFSET esi, 0
复制代码
1
1314         pushl %edx
复制代码
1
1315         CFI_ADJUST_CFA_OFFSET 4
复制代码
1
1316         CFI_REL_OFFSET edx, 0
复制代码
1
1317         pushl %ecx
复制代码
1
1318         CFI_ADJUST_CFA_OFFSET 4
复制代码
1
1319         CFI_REL_OFFSET ecx, 0
复制代码
1
1320         pushl %ebx
复制代码
1
1321         CFI_ADJUST_CFA_OFFSET 4
复制代码
1
1322         CFI_REL_OFFSET ebx, 0
复制代码
1
1323         cld
复制代码
1
1324         movl $(__KERNEL_PERCPU), %ecx
复制代码
1
1325         movl %ecx, %fs
复制代码
1
1326         UNWIND_ESPFIX_STACK   /* 用于处理系统栈不是32位的情况 */
复制代码
1
1327         GS_TO_REG %ecx        /* 把%gs存入%ecx中 */
复制代码
1
1328         movl PT_GS(%esp), %edi          # get the function address
复制代码
1
1329         movl PT_ORIG_EAX(%esp), %edx    # get the error code
复制代码
1
1330         movl $-1, PT_ORIG_EAX(%esp)     # no syscall to restart
复制代码
1
1331         REG_TO_PTGS %ecx      /* 把%gs的值存入栈的%gs中,即处理代码指针位置 */
复制代码
1
1332         SET_KERNEL_GS %ecx
复制代码
1
1333         movl $(__USER_DS), %ecx
复制代码
1
1334         movl %ecx, %ds
复制代码
1
1335         movl %ecx, %es
复制代码
1
1336         TRACE_IRQS_OFF
复制代码
1
1337         movl %esp,%eax                  # pt_regs pointer
复制代码
1
1338         call *%edi
复制代码
1
1339         jmp ret_from_exception
复制代码
1
1340         CFI_ENDPROC
复制代码
1
1341 END(page_fault)

在error_code中执行在1338行call *edi之前,栈的内存布局如下图。

clip_image008

在error_code处理“栈异常”处理时,call *edi = call do_stack_segment,do_stack_segment函数由DO_ERROR宏生成。error_code给do_stack_segment函数传入regs参数时,由ABI规范规定,eax为第一个参数 struct pt_regs regs。pt_regs与栈内存布局相同,其结构体如下:

复制代码
1
021 struct pt_regs {
复制代码
1
022         long ebx;
复制代码
1
023         long ecx;
复制代码
1
024         long edx;
复制代码
1
025         long esi;
复制代码
1
026         long edi;
复制代码
1
027         long ebp;
复制代码
1
028         long eax;
复制代码
1
029         int  xds;
复制代码
1
030         int  xes;
复制代码
1
031         int  xfs;
复制代码
1
032         int  xgs;     /* 对应%gs,对于异常而言是处理器代码 */
复制代码
1
033         long orig_eax; /* 对应于内存布局中的error code或vector */
复制代码
1
034         long eip;
复制代码
1
035         int  xcs;
复制代码
1
036         long eflags;
复制代码
1
037         long esp;
复制代码
1
038         int  xss;
复制代码
1
039 };

stack_segment函数是“栈异常”的异常处理函数,由DO_ERROR宏产生:

复制代码
1
215 #ifdef CONFIG_X86_32
复制代码
1
216 DO_ERROR(12, SIGBUS, "stack segment", stack_segment)
复制代码
1
217 #endif
复制代码
1
183 #define DO_ERROR(trapnr, signr, str, name)                             
复制代码
1
184 dotraplinkage void do_##name(struct pt_regs *regs, long error_code)    
复制代码
1
185 {                                                                      
复制代码
1
186         if (notify_die(DIE_TRAP, str, regs, error_code, trapnr, signr) 
复制代码
1
187                                                         == NOTIFY_STOP)
复制代码
1
188                 return;                                                
复制代码
1
189         conditional_sti(regs);                                         
复制代码
1
190         do_trap(trapnr, signr, str, regs, error_code, NULL);           
复制代码
1
191 }

可见,do_stack_segment最后调用do_trap进行异常处理。

2)中断描述符表中断部分最终初始化

由start_kernel()调用init_IRQ(void)进行中断最终初始化。其操作流程如下:

clip_image010

1) 将CPU0 IRQ0…IRQ15的vectors设置为0…15。对于CPU0的vecotr_irq而言,若其IRQ是PIC中断,则其vector早已由PIC固定,所以设置其IRQ0…IRQ15的vector为0…15。若是I/O APIC,由于其vector分配是可由OS动态设置,所以vector 0…15可能会被重新分配;

2) 本质上调用native_init_IRQ()函数对irq_desc、以及中断部分门描述符进行初始化,并针对CONFIG_4KSTACKS配置、协处理器模拟浮点运算等进行配置;

a、 调用init_ISA_irqs()初始化8259A可断控制器,并对相应中断请求线IRQ进行初始化、使其对应中断控制器irq_desc的操作函数为8259A操作接口函数;

b、 调用apic_intr_init()函数针对采取I/O APIC中断处理器的情况,对APIC中断处理器进行初始化工作;

c、 将调用set_intr_gate为系统中每根中断请求线IRQ地应的中断向量号设置了相应的中断门描述门,其中断处理函数定义在interrupt数组中

d、 在PC平台下set_irq(2, &rq2)对从8259A中断控制器的第三根IRQ请求做特殊处理;

e、 irq_ctx_init函数用于在配置CONFIG_4KSTACK的情况下配置当前CPU的中断栈相关项。LINUX内核在未配置CONFIG_4KSTACK时,共享所中断进程的内核栈,内核栈为两页,即8K。在2.6版本时,增加了CONFIG_4KSTACK选项,将栈大小从两页减小至一页,为了应对栈的减少,故中断处理程序拥有自己的中断处理程序线,为原先共享栈的一半,即4K,每个CPU拥有一个中断栈。

复制代码
1
125 void __init init_IRQ(void)
复制代码
1
126 {
复制代码
1
127         int i;
复制代码
1
128
复制代码
1
129         /*
复制代码
1
130          * On cpu 0, Assign IRQ0_VECTOR..IRQ15_VECTOR's to IRQ 0..15.
复制代码
1
131          * If these IRQ's are handled by legacy interrupt-controllers like PIC,
复制代码
1
132          * then this configuration will likely be static after the boot. If
复制代码
1
133          * these IRQ's are handled by more mordern controllers like IO-APIC,
复制代码
1
134          * then this vector space can be freed and re-used dynamically as the
复制代码
1
135          * irq's migrate etc.
复制代码
1
136          */
复制代码
1
137         for (i = 0; i < legacy_pic->nr_legacy_irqs; i++)
复制代码
1
138                 per_cpu(vector_irq, 0)[IRQ0_VECTOR + i] = i;
复制代码
1
 
复制代码
1
/* intr_init()本质上调用native_init_IRQ()初始化。用x86_init.irqs.intr_init()函数对irq_desc、以及中断部分门描述符进行初始化。x86_init是x86_init_ops类型,集成针对x86特定平台的各种初始化工作,包含初始化PCI总线、中断、异常等,其中irqs就是针对中断进行初始化操作。*/
复制代码
1
140         x86_init.irqs.intr_init();
复制代码
1
141 }

/arch/x86/include/asm/x86_init.h

复制代码
1
115 /**
复制代码
1
116  * struct x86_init_ops - functions for platform specific setup
复制代码
1
117  *
复制代码
1
118  */
复制代码
1
119 struct x86_init_ops {
复制代码
1
122         struct x86_init_irqs            irqs;
复制代码
1
124         struct x86_init_paging          paging;
复制代码
1
125         struct x86_init_timers          timers;
复制代码
1
127         struct x86_init_pci             pci;
复制代码
1
128 };
复制代码
1
 
复制代码
1
047 /**
复制代码
1
048  * struct x86_init_irqs - platform specific interrupt setup
复制代码
1
049  * @pre_vector_init:            init code to run before interrupt vectors
复制代码
1
050  *                              are set up.
复制代码
1
051  * @intr_init:                  interrupt init code
复制代码
1
052  * @trap_init:                  platform specific trap setup
复制代码
1
053  */
复制代码
1
054 struct x86_init_irqs {
复制代码
1
055         void (*pre_vector_init)(void);
复制代码
1
056         void (*intr_init)(void);
复制代码
1
057         void (*trap_init)(void);
复制代码
1
058 };

/arch/x86/kernel/x86_init.c

复制代码
1
029 /*
复制代码
1
030  * The platform setup functions are preset with the default functions
复制代码
1
031  * for standard PC hardware.
复制代码
1
032  */
复制代码
1
033 struct x86_init_ops x86_init __initdata = {
复制代码
1
051         .irqs = {
复制代码
1
052                 .pre_vector_init        = init_ISA_irqs,
复制代码
1
053                 .intr_init              = native_init_IRQ,
复制代码
1
054                 .trap_init              = x86_init_noop,
复制代码
1
055         },
复制代码
1
082 };
复制代码
1
235 void __init native_init_IRQ(void)
复制代码
1
236 {
复制代码
1
237         int i;
复制代码
1
238
复制代码
1
239         /* Execute any quirks before the call gates are initialised: */
复制代码
1
/* 调用init_ISA_irqs()初始化irq_desc[0…NR_IRQS] = {.status = IRQ_DISABLED,
复制代码
1
.action = NULLL, .depth = 1}。并且对于irq0 … 15,
复制代码
1
将其handler = &i8259A_irq_type*/
复制代码
1
240         x86_init.irqs.pre_vector_init();
复制代码
1
241
复制代码
1
242         apic_intr_init();
复制代码
1
243
复制代码
1
244         /*
复制代码
1
245          * Cover the whole vector space, no vector can escape
复制代码
1
246          * us. (some of these will be overridden and become
复制代码
1
247          * 'special' SMP interrupts)
复制代码
1
248          */
复制代码
1
/* 调用set_intr_gate()为每根IRQ对应的中断向量号设置了相应的中断门描述符 */
复制代码
1
249         for (i = FIRST_EXTERNAL_VECTOR; i < NR_VECTORS; i++) {
复制代码
1
250                 /* IA32_SYSCALL_VECTOR could be used in trap_init already. */
复制代码
1
251                 if (!test_bit(i, used_vectors))
复制代码
1
252                         set_intr_gate(i, interrupt[i-FIRST_EXTERNAL_VECTOR]);
复制代码
1
253         }
复制代码
1
/* 系统若非I/O APIC,则IRQ3用于8259A从片的级联,故进行特殊处理 */
复制代码
1
255         if (!acpi_ioapic)
复制代码
1
256                 setup_irq(2, &irq2);
复制代码
1
257
复制代码
1
258 #ifdef CONFIG_X86_32
复制代码
1
259         /*
复制代码
1
260          * External FPU? Set up irq13 if so, for
复制代码
1
261          * original braindamaged IBM FERR coupling.
复制代码
1
262          */
复制代码
1
/* 在处理器集成协处理器,而该协处理器没有浮点处理单元的情况下设置针对浮点计算异常的处理函数fpu_irq,该函数用软件模拟的方法来进行浮点数运算 */
复制代码
1
263         if (boot_cpu_data.hard_math && !cpu_has_fpu)
复制代码
1
264                 setup_irq(FPU_IRQ, &fpu_irq);
复制代码
1
/* 在内核选中CONFIG_4KSTACKS时,为系统中每一CPU设置中断、异常处理函数所需的内核态栈;在没有选中该选项的情况下,irq_ctx_init是个空语句 */
复制代码
1
266         irq_ctx_init(smp_processor_id());
复制代码
1
267 #endif
复制代码
1
268 }
复制代码
1
101 void __init init_ISA_irqs(void)
复制代码
1
102 {
复制代码
1
103         int i;
复制代码
1
/* 若存在LOCAL_APIC,则对Local APIC模块进行设置 */
复制代码
1
105 #if defined(CONFIG_X86_64) || defined(CONFIG_X86_LOCAL_APIC)
复制代码
1
106         init_bsp_APIC();
复制代码
1
107 #endif
复制代码
1
/* 初始化中断控制器8259A */
复制代码
1
108         legacy_pic->init(0);
复制代码
1
109
复制代码
1
110         /*
复制代码
1
111          * 16 old-style INTA-cycle interrupts:
复制代码
1
112          */
复制代码
1
/* 系统中断老式处理方式是由两片8259A级联而成,每片可有8个IRQ,故两片最多初始化16个IRQ,但由于IRQ 2用于级联,故实际仅有15个有效IRQ。 */
复制代码
1
113         for (i = 0; i < legacy_pic->nr_legacy_irqs; i++) {
复制代码
1
114                 struct irq_desc *desc = irq_to_desc(i);
复制代码
1
115
复制代码
1
116                 desc->status = IRQ_DISABLED;
复制代码
1
117                 desc->action = NULL;
复制代码
1
118                 desc->depth = 1;
复制代码
1
119
复制代码
1
120                 set_irq_chip_and_handler_name(i, &i8259A_chip,
复制代码
1
121                                               handle_level_irq, "XT");
复制代码
1
122         }
复制代码
1
123 }

interrupt数组

interrupt数组符号由汇编代码定义,其源码如下:

/arch/x86/kernel/entry_32.S

复制代码
1
819 /*
复制代码
1
820  * Build the entry stubs and pointer table with some assembler magic.
复制代码
1
821  * We pack 7 stubs into a single 32-byte chunk, which will fit in a
复制代码
1
822  * single cache line on all modern x86 implementations.
复制代码
1
823  */
复制代码
1
824 .section .init.rodata,"a"
复制代码
1
825 ENTRY(interrupt)
复制代码
1
826 .text
复制代码
1
827         .p2align 5
复制代码
1
828         .p2align CONFIG_X86_L1_CACHE_SHIFT
复制代码
1
829 ENTRY(irq_entries_start)
复制代码
1
830         RING0_INT_FRAME
复制代码
1
831 vector=FIRST_EXTERNAL_VECTOR /* =0x20,0~31号内部中断 */
复制代码
1
832 .rept (NR_VECTORS-FIRST_EXTERNAL_VECTOR+6)/7
复制代码
1
833         .balign 32 /* 32字节对齐 */
复制代码
1
834   .rept 7
复制代码
1
835     .if vector < NR_VECTORS
复制代码
1
836       .if vector <> FIRST_EXTERNAL_VECTOR
复制代码
1
/* 按照CFA规则修改前一个offset,以达4字节对齐。CFA标准(Call Frame Information), help a debugger create a reliable backtrace through functions. */
复制代码
1
837         CFI_ADJUST_CFA_OFFSET -4
复制代码
1
838       .endif
复制代码
1
839 1:      pushl $(~vector+0x80)   /* Note: always in signed byte range ,[-256 ~ -1] */
复制代码
1
840         CFI_ADJUST_CFA_OFFSET 4         /* 按CFA规则4字节对齐 */
复制代码
1
841       .if ((vector-FIRST_EXTERNAL_VECTOR)%7) <> 6
复制代码
1
842         jmp 2f
复制代码
1
843       .endif
复制代码
1
844       .previous
复制代码
1
845         .long 1b
复制代码
1
846       .text
复制代码
1
847 vector=vector+1
复制代码
1
848     .endif
复制代码
1
849   .endr /* end of rep 7 */
复制代码
1
850 2:      jmp common_interrupt
复制代码
1
851 .endr /* end of rep (NR_VECTORS – FIRST_...) */
复制代码
1
852 END(irq_entries_start)
复制代码
1
853
复制代码
1
854 .previous
复制代码
1
855 END(interrupt)

ENTRY(interrupt)通过伪指令.rept、.endr,将在代码段产生(NR_VECTORS - FIRST_EXTERNAL_VECTOR)个跳转到common_interrupt的汇编代码片段,起始地址是irq_entries_start;在数据段产生一个中断数组的符号interrupt,用于记录产生代码段中每个中断向量处理的汇编代码片段地址,在C语言中将interrupt符号作为中断数组变量导入:

复制代码
1
132 extern void (*__initconst interrupt[NR_VECTORS-FIRST_EXTERNAL_VECTOR])(void);

ENTRY(interrupt)编译之后,所生成的代码段和数据段内存布局如下:

clip_image012

ENTRIY(interrupt)汇编代码段主要由两个rept构成,外层rept循环(NR_VECTORS-FIRST_EXTERNAL_VECTOR+6)/7次,而每次内层rept循环7次,内层循环所产生的代码以32字节对齐,内层rept循环产生的代码如上图irq_entries_start中以粗黑方框表示。

以两层rept循环生成jmp common_interrupt的目的在于:

在内循环内,前6次循环产生的代码指令为:push和short jmp,而第7次产生的代码指令为:push和long jmp。push占2字节,short jmp占2字节,long jmp占5字节,故采取此种方式内层rept循环7次产生的代码大小为:6 * (2 + 2) + 2 + 5 = 31 字节。而外层循环以32字节对齐,相比于老版本每次都为push和long jmp而言,所以利用short jmp节省了内存开销。参见:http://didat.sprg.uniroma2.it/la/docs/la12-10.pdf

每个中断门描述符在将vector压入后都跳转到common_interrupt进行处理。common_interrupt在保存中断现场之后,跳转到do_IRQ进行中断函数处理,最后调用iret_from_intr进行中断返回、恢复中断上下文。

复制代码
1
863 common_interrupt:
复制代码
1
864         addl $-0x80,(%esp)      /* Adjust vector into the [-256,-1] range */
复制代码
1
865         SAVE_ALL /* 宏定义,负责完成宏定义中断现场的保护工作 */
复制代码
1
866         TRACE_IRQS_OFF
复制代码
1
867         movl %esp,%eax
复制代码
1
868         call do_IRQ
复制代码
1
869         jmp ret_from_intr
复制代码
1
870 ENDPROC(common_interrupt)
复制代码
1
195 .macro SAVE_ALL
复制代码
1
196         cld  /* 清除系统标志寄存器EFLAGS中的方向标志位DF,使%si、%di寄存器的值在每次字符串指令操作后自动+1,使自字符串从低地址到高地址方向处理 */
复制代码
1
197         PUSH_GS
复制代码
1
198         pushl %fs
复制代码
1
199         CFI_ADJUST_CFA_OFFSET 4
复制代码
1
200         /*CFI_REL_OFFSET fs, 0;*/
复制代码
1
201         pushl %es
复制代码
1
202         CFI_ADJUST_CFA_OFFSET 4
复制代码
1
203         /*CFI_REL_OFFSET es, 0;*/
复制代码
1
204         pushl %ds
复制代码
1
205         CFI_ADJUST_CFA_OFFSET 4
复制代码
1
206         /*CFI_REL_OFFSET ds, 0;*/
复制代码
1
207         pushl %eax
复制代码
1
208         CFI_ADJUST_CFA_OFFSET 4
复制代码
1
209         CFI_REL_OFFSET eax, 0
复制代码
1
210         pushl %ebp
复制代码
1
211         CFI_ADJUST_CFA_OFFSET 4
复制代码
1
212         CFI_REL_OFFSET ebp, 0
复制代码
1
213         pushl %edi
复制代码
1
214         CFI_ADJUST_CFA_OFFSET 4
复制代码
1
215         CFI_REL_OFFSET edi, 0
复制代码
1
216         pushl %esi
复制代码
1
217         CFI_ADJUST_CFA_OFFSET 4
复制代码
1
218         CFI_REL_OFFSET esi, 0
复制代码
1
219         pushl %edx
复制代码
1
220         CFI_ADJUST_CFA_OFFSET 4
复制代码
1
221         CFI_REL_OFFSET edx, 0
复制代码
1
222         pushl %ecx
复制代码
1
223         CFI_ADJUST_CFA_OFFSET 4
复制代码
1
224         CFI_REL_OFFSET ecx, 0
复制代码
1
225         pushl %ebx
复制代码
1
226         CFI_ADJUST_CFA_OFFSET 4
复制代码
1
227         CFI_REL_OFFSET ebx, 0
复制代码
1
228         movl $(__USER_DS), %edx
复制代码
1
229         movl %edx, %ds
复制代码
1
230         movl %edx, %es
复制代码
1
231         movl $(__KERNEL_PERCPU), %edx
复制代码
1
232         movl %edx, %fs
复制代码
1
233         SET_KERNEL_GS %edx
复制代码
1
234 .endm

common_interrupt在调用do_IRQ之前中断栈内存布局如下:

clip_image014

转载于:https://www.cnblogs.com/bittorrent/p/3378911.html

最后

以上就是纯情大山最近收集整理的关于[fw]LINUX中断描述符初始化的全部内容,更多相关[fw]LINUX中断描述符初始化内容请搜索靠谱客的其他文章。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(84)

评论列表共有 0 条评论

立即
投稿
返回
顶部