中断——中断处理程序的进入与退出(三) (基于3.16-rc4),中断3.16-rc4


上一篇博文我们分析了中断描述符表的中断门初始化过程,并且在interrupt数组中初始化过程中,可以看到每个中断处理程序都会跳入common_interrupt中。下面我们分析下common_interrupt汇编片段(arch/x86/kernel/entrt_32.S)

 1     .p2align CONFIG_X86_L1_CACHE_SHIFT
 2 common_interrupt:
 3     ASM_CLAC
 4     addl $-0x80,(%esp)    /* Adjust vector into the [-256,-1] range */
 5     SAVE_ALL
 6     TRACE_IRQS_OFF
 7     movl %esp,%eax
 8     call do_IRQ
 9     jmp ret_from_intr
10 ENDPROC(common_interrupt)
11     CFI_ENDPROC 

第5行SAVE_ALL也是一个汇编片段(宏),用来将当前多个寄存器压栈,因为在do_IRQ中可能会用到这些寄存器。第8行调用了do_IRQ函数,接下来我们分析do_IRQ函数(arch/x86/kernel/irq.c)。

 1 __visible unsigned int __irq_entry do_IRQ(struct pt_regs *regs)
 2 {
 3     struct pt_regs *old_regs = set_irq_regs(regs);
 4 
 5     /* high bit used in ret_from_ code  */
 6     unsigned vector = ~regs->orig_ax;
 7     unsigned irq;
 8 
 9     irq_enter();
10     exit_idle();
11 
12     irq = __this_cpu_read(vector_irq[vector]);
13 
14     if (!handle_irq(irq, regs)) {
15         ack_APIC_irq();
16 
17         if (irq != VECTOR_RETRIGGERED) {
18             pr_emerg_ratelimited("%s: %d.%d No irq handler for vector (irq %d)\n",
19                          __func__, smp_processor_id(),
20                          vector, irq);
21         } else {
22             __this_cpu_write(vector_irq[vector], VECTOR_UNDEFINED);
23         }
24     }
25 
26     irq_exit();
27 
28     set_irq_regs(old_regs);
29     return 1;
30 }

第12行的vector_irq数组保存了中断向量号和中断线号(中断号)的对应关系,利用__this_cpu_read函数获得当前中断向量号所对应的中断号。第14行中handle_irq函数,使用中断号irq作为参数,进入该中断号所对应的中断服务例程中,下面分析下handle_irq函数(arch/x86/kernel/irq_32.c)。

 1 bool handle_irq(unsigned irq, struct pt_regs *regs)
 2 {
 3     struct irq_desc *desc;
 4     int overflow;
 5 
 6     overflow = check_stack_overflow();
 7 
 8     desc = irq_to_desc(irq);
 9     if (unlikely(!desc))
10         return false;
11 
12     if (user_mode_vm(regs) || !execute_on_irq_stack(overflow, desc, irq)) {
13         if (unlikely(overflow))
14             print_stack_overflow();
15         desc->handle_irq(irq, desc);
16     }
17 
18     return true;
19 }

第8行获取到中断号irq所对应的struct irq_desc结构体指针,内核使用struct irq_desc类型结构体数组来存放所有的中断服务例程,中断号irq作为数组元素下标,如下所示(kernel/irq/irqdesc.c)。

1 struct irq_desc irq_desc[NR_IRQS] __cacheline_aligned_in_smp = {
2     [0 ... NR_IRQS-1] = {
3         .handle_irq    = handle_bad_irq,
4         .depth        = 1,
5         .lock        = __RAW_SPIN_LOCK_UNLOCKED(irq_desc->lock),
6     }
7 };

再来看下struct irq_desc结构体(include/linux/irqdesc.h)。

 1 struct irq_desc {
 2     struct irq_data        irq_data;
 3     unsigned int __percpu    *kstat_irqs;
 4     irq_flow_handler_t    handle_irq;
 5 #ifdef CONFIG_IRQ_PREFLOW_FASTEOI
 6     irq_preflow_handler_t    preflow_handler;
 7 #endif
 8     struct irqaction    *action;    /* IRQ action list */
 9     unsigned int        status_use_accessors;
10     unsigned int        core_internal_state__do_not_mess_with_it;
11     unsigned int        depth;        /* nested irq disables */
12     unsigned int        wake_depth;    /* nested wake enables */
13     unsigned int        irq_count;    /* For detecting broken IRQs */
14     unsigned long        last_unhandled;    /* Aging timer for unhandled count */
15     unsigned int        irqs_unhandled;
16     atomic_t        threads_handled;
17     int            threads_handled_last;
18     raw_spinlock_t        lock;
19     struct cpumask        *percpu_enabled;
20 #ifdef CONFIG_SMP
21     const struct cpumask    *affinity_hint;
22     struct irq_affinity_notify *affinity_notify;
23 #ifdef CONFIG_GENERIC_PENDING_IRQ
24     cpumask_var_t        pending_mask;
25 #endif
26 #endif
27     unsigned long        threads_oneshot;
28     atomic_t        threads_active;
29     wait_queue_head_t       wait_for_threads;
30 #ifdef CONFIG_PROC_FS
31     struct proc_dir_entry    *dir;
32 #endif
33     int            parent_irq;
34     struct module        *owner;
35     const char        *name;
36 } ____cacheline_internodealigned_in_smp;

真正的中断处理程序并不直接放在struct irq_desc结构体中,而是存放在struct irq_desc结构体的action成员所指向的struct irqaction结构体中,第8行。下面看下struct irqaction结构体类型(include/linux/interrupt.h)。

 1 struct irqaction {
 2     irq_handler_t        handler;                    
 3     void            *dev_id;
 4     void __percpu        *percpu_dev_id;
 5     struct irqaction    *next;
 6     irq_handler_t        thread_fn;
 7     struct task_struct    *thread;
 8     unsigned int        irq;
 9     unsigned int        flags;
10     unsigned long        thread_flags;
11     unsigned long        thread_mask;
12     const char        *name;
13     struct proc_dir_entry    *dir;
14 } ____cacheline_internodealigned_in_smp;

第1行handler中存放着中断服务例程。第5行next中存放下一个该类型结构体指针。因为一个中断号可以对应多个中断服务例程(中断线共享),内核将中断号相同的多个中断服务例程组织成一个链表,挂到以irq号作为下标的irq_desc数组元素中。

回到handle_irq函数中,第8行获取到irq号所对应的struct irq_desc结构体指针desc,接着第15行执行了desc->handle_irq函数,在该函数中执行irq号的所有中断服务例程。

在这里,一定要区别清楚IDT表idt_table和irq_desc数组的区别,idt_table中存放的是中断处理程序,而且这些中断处理程序的开头部分代码都是相同的,都要跳到common_interrupt函数中,进而去寻找中断服务例程。而irq_desc数组中存放的是中断服务例程,中断处理程序最终要通过该数组找到对应的中断服务例程并执行它。我们在编写驱动程序时,很多时候需要编写设备的中断服务例程,我们将中断服务例程存放在申请的struct irqaction结构体当中,并将该结构体挂到irq_desc数组的对应链表中,当中断发生后,系统会自动通过IDT--->GDT--->中断处理程序--->common_interrupt(属于中断处理程序)--->do_IRQ--->handle_irq,然后将irq_desc[NR_IRQS]数组中irq号对应的所有中断服务例程全部执行一遍。

 

当中断服务例程执行结束后,就会返回文章最开始的common_interrupt函数中,开始执行第9行,跳入到ret_from_intr函数中(arch/x86/kernel/entrt_32.S)

 1 ret_from_intr:
 2     GET_THREAD_INFO(%ebp)
 3 #ifdef CONFIG_VM86
 4     movl PT_EFLAGS(%esp), %eax    # mix EFLAGS and CS
 5     movb PT_CS(%esp), %al
 6     andl $(X86_EFLAGS_VM | SEGMENT_RPL_MASK), %eax
 7 #else
 8     /*
 9      * We can be coming here from child spawned by kernel_thread().
10      */
11     movl PT_CS(%esp), %eax
12     andl $SEGMENT_RPL_MASK, %eax
13 #endif
14     cmpl $USER_RPL, %eax
15     jb resume_kernel        # not returning to v8086 or userspace
16 
17 ENTRY(resume_userspace)
18     LOCKDEP_SYS_EXIT
19      DISABLE_INTERRUPTS(CLBR_ANY)    # make sure we don't miss an interrupt
20                     # setting need_resched or sigpending
21                     # between sampling and the iret
22     TRACE_IRQS_OFF
23     movl TI_flags(%ebp), %ecx
24     andl $_TIF_WORK_MASK, %ecx    # is there any work to be done on
25                     # int/exception return?
26     jne work_pending
27     jmp restore_all
28 END(ret_from_exception)

第11行从当前栈中将cs寄存器的值存入eax中,第12行通过掩码计算,将eax中的DPL字段提取出来再存入eax,第14行比较eax(DPL)和用户空间权限的大小,如果DPL权限大的话,执行15行,恢复到内核态中,否则恢复到用户态,17行。

 


汇编语言的中断服务的中断服务程序是什?

嗯,我猜你问的是PC的,不是单片机
一、汇编语言的中断分以下几种:
1.BIOS中断,这是固化到BIOS程序中的,每次开机BIOS会自动加载到指定内存
2.186下的DOS中断,在DOS系统被加载后,系统会延用BIOS的中断向量,并向里面添加一些新的向量,这些功能便是DOS系统自带的中断服务程序
3.286及以上的系统中断,PC会进入保护模式,在OS被加载后,中断由IDT控制,这一机制类似于中断向量表,只不过中断向量换成了选择子。这样的中断机制对不同型号的CPU有略微的差别,这里不细说了,我自己也没全弄明白。

二、中断实现的方式(8086下的普通中断)
听说过“优先级编码器”没?——如果同时有两个信号被接收,会指定某一个信号的优先级高,先执行它。中断就是类似的处理方法。
当CPU获取到某一高操作优先级的信号时(比如时钟,每固定时间就会触发一次;比如键盘响应,用户希望通过Ctrl+C来退出任何正在执行的DOS程序),CPU会将当前正在执行的程序挂起来,转而去处理该信号(类似于Call,但略有不同,你看的书应该会讲到)。
处理中断时,系统会将其解释为一个标号,比如int 9h、int 21h等等。这个标号是一个序号,在内存某处存放着连续的一个表格,这个标号便是表格中的“行号”,只不过,每一行是两列,包括了该中断的处理程序的段基址和偏移量。中断向量表是从0000:0000开始的,每4字节为一个表项。中断标号x4就是对应的中断向量表项所存的地址,高地址是基地址,低地址是偏移。
这么说不知道你懂不懂。。。
反正总结一下你的问题吧,中断服务程序是加载到内存中的,它在加载前可能是存在BIOS芯片上,也可能是存在硬盘里的;中断向量表里只能写上中断处理程序的入口地址,要知道每个表项只有4字节;具体的中断服务程序,我不信你学汇编的书上不讲,我大概讲一下:CPU的INTR引脚获得了中断信号,得到了标号,比方说是5号,中断向量表项为0000:000A,读取这个内存,得到中断程序入口地址比方说是AAAA:BBBB,那么它会将当前的CS/IP、Flags寄存器入栈,然后转到AAAA:BBBB处去执行一直到iret指令返回原任务(或许该中断结束了这个任务,就不会返回了)。

至于保护模式的中断,相信你暂时还没遇到。到后面还有操控8259A芯片来实现高级中断的,这个就不是一般需要学的了。
 

什叫矢量中断?叙述中断类型号、中断向量表与中断服务程序入口地址三者的关系?

矢量中断。。其实就是 处理器产生中断。
会在某个寄存器里面产生对应中断的数字 1 2 3 这种。
如果处理器支持开启矢量中断功能。那么会直接跳转到中断想量表进行执行
如果不支持。。那么大家一起进入某一个中断。难后在程序里面判断哪个东西产生了中断,,难后执行对应的中断服务程序

中断类型号。。。估计和上面的数字一回事

中断向量表,,用来存放中断处理程序的一个表格。

中断服务程序入口地址。。中断产生的时候。程序会自动跳到该位置执行的地址

一般来说。。中断入口地址可能只有几个,
但是中断类型还有好多。
那么只好 很多中断类型 进入一个中断入口地址中。
这个时候就需要在中断入口的地址那边判断是那个中断,然后处理
因为这样要判断是什么中断。不能达到中断快速的目的
所有有了矢量中断和中断向量表
矢量中断的目的是。中断时候自动产生偏移量,
处理自动跳转到 中断向量表+偏移量的地址,,这样就不需要判断是什么中断。加速了中断的进入
 

相关内容

    暂无相关文章