引言 在计算机体系结构体中,CPU与外设的工作速度不匹配一直是存在的性能瓶颈点,而如何使CPU与外设正常的协调工作成为了重要问题。当然存在像轮询这样的解决办法,每隔一段时间去询问一次,但是这样无疑会降低处理效率。中断显然是更有效的解决办法。
基础 中断(Interrupt)是一种机制,允许计算机在执行当前任务的同时响应和处理来自外部设备或其他源的事件。
分类 按中断来源
硬件中断
硬件中断是由计算机硬件生成的,通常是外部设备请求 CPU 处理的事件。例如,键盘、鼠标、定时器、网络适配器等设备可以触发硬件中断。通常通过中断控制器(如 PIC 或 APIC)进行管理和分发,然后由操作系统处理。
软件中断
也称为异常或陷阱,是由计算机程序生成的,通常表示程序执行期间的异常情况。例如,除零错误、越界访问等情况可以触发软件中断。通常由程序执行指令(如 int
指令或系统调用)来触发,并由操作系统或程序本身进行处理。
同步方式
中断与异常区别
中断
异常
例子
敲击键盘、插入usb
除0、缺页中断
来源
通常是硬件
通常是软件由于编程失误导致
处理
do_IRQ
给进程发送信号
位置
根据架构有所不同,但每一种固定
可屏蔽中断的中断向量则是可配的
处理流程
中断触发及检测。满足中断触发条件中断来到,并检测是哪种原因的中断
现场保存。保存寄存器的值(下一条指令的地址)并屏蔽中断,确保中断正常执行和恢复
跳入中断向量表。根据原因进入中断向量表查看对应的中断处理流程
中断处理程序
恢复现场。清楚中断标志位并恢复寄存器的值
proc/interrupts
proc是一个虚拟文件系统存在于内核中。通过上述命令就可以查看系统中中断号、个数、名字等等内容。这个是和体系相关的,show_interrupts()函数实现的。
中断控制方法
中断上下半部 中断的执行会打断系统正常的运行,因此为了提高系统的响应度和效率就需要中断程序的执行尽可能要快速,中断恢复系统正常运行。但是实际中有些中断的工作就会导致阻塞,因此可以将这部分内容等到系统不忙的时候执行。因此将中断处理过程分为两个部分:上半部(Top Half)和下半部(Bottom Half)。
上半部:时间敏感、硬件相关、原子执行不希望被其它中断打断
下半部:执行耗时长
下半部实现方式
软中断(中断上下文。静态创建,操作多处理器变量需要注意)
tasklet(中断上下文。不能并发执行,如果不能确定是否是多处理器,选择这个)
工作队列(进程上下文。执行时间可以推迟到进程上下文完成,推荐!!)
工作队列的使用方式 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 struct work_struct my_wq;struct work_struct my_delay_wq;void xxx_do_work(unsigned long ){ } void xxx_do_delay_work(unsigned long ){ } irqreturn_t xxx_interrupt(int irq,void *dev_id) { schedule_work(&my_wq); schedule_delay_work(&my_delay_wq); } int xxx_init(void ){ request = request_irq(xxx_wq,xxx_interrupt,IRQF_DISABLED,"XXX" ,NULL); ... INIT_WORK(&my_wq,(void (*)(void *))xxx_do_work,NULL) INIT_DELAYED_WORK(&my_delay_wq,(void (*)(void *))xxx_do_delay_work,NULL) } void xxx_exit(void ){ free_irq(xxx_irq,xxx_interrupt); }
中断描述符 Linux中断机制的核心数据结构 irq_desc, 它完整地描述了一条中断线 (或可简单理解为 “一个中断源” )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 struct irq_desc { struct irq_common_data irq_common_data ; struct irq_data irq_data ; unsigned int __percpu *kstat_irqs; irq_flow_handler_t handle_irq; #ifdef CONFIG_IRQ_PREFLOW_FASTEOI irq_preflow_handler_t preflow_handler; #endif struct irqaction *action ; unsigned int status_use_accessors; unsigned int core_internal_state__do_not_mess_with_it; unsigned int depth; unsigned int wake_depth; unsigned int irq_count; unsigned long last_unhandled; unsigned int irqs_unhandled; atomic_t threads_handled; int threads_handled_last; raw_spinlock_t lock; struct cpumask *percpu_enabled ; #ifdef CONFIG_SMP const struct cpumask *affinity_hint ; struct irq_affinity_notify *affinity_notify ; #ifdef CONFIG_GENERIC_PENDING_IRQ cpumask_var_t pending_mask; #endif #endif unsigned long threads_oneshot; atomic_t threads_active; wait_queue_head_t wait_for_threads; #ifdef CONFIG_PM_SLEEP unsigned int nr_actions; unsigned int no_suspend_depth; unsigned int cond_suspend_depth; unsigned int force_resume_depth; #endif #ifdef CONFIG_PROC_FS struct proc_dir_entry *dir ; #endif int parent_irq; struct module *owner ; const char *name; } ____cacheline_internodealigned_in_smp;
中断初始化流程 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 asmlinkage __visible void __init start_kernel (void ) { local_irq_disable(); early_boot_irqs_disabled = true ; ... trap_init(); early_irq_init(); init_IRQ(); ... WARN(!irqs_disabled(), "Interrupts were enabled early\n" ); early_boot_irqs_disabled = false ; local_irq_enable(); ... }
trap_init 中断初始化函数入口 trap_init()
,不同体系结构下的实现可能有所不同,但是完成的功能基本一致,完成填写IDT描述符构成中断向量表的功能。
MIPS中断异常类型 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 #define EXCCODE_INT 0 #define EXCCODE_MOD 1 #define EXCCODE_TLBL 2 #define EXCCODE_TLBS 3 #define EXCCODE_ADEL 4 #define EXCCODE_ADES 5 #define EXCCODE_IBE 6 #define EXCCODE_DBE 7 #define EXCCODE_SYS 8 #define EXCCODE_BP 9 #define EXCCODE_RI 10 #define EXCCODE_CPU 11 #define EXCCODE_OV 12 #define EXCCODE_TR 13 #define EXCCODE_MSAFPE 14 #define EXCCODE_FPE 15 #define EXCCODE_TLBRI 19 #define EXCCODE_TLBXI 20 #define EXCCODE_MSADIS 21 #define EXCCODE_MDMX 22 #define EXCCODE_WATCH 23 #define EXCCODE_MCHECK 24 #define EXCCODE_THREAD 25 #define EXCCODE_DSPDIS 26 #define EXCCODE_GE 27 #define EXCCODE_CACHEERR 30 #define MIPS_EXCCODE_TLBPAR 16 #define LOONGSON_EXCCODE_GSEXC 16
x86中断异常类型 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 #define X86_TRAP_DE 0 #define X86_TRAP_DB 1 #define X86_TRAP_NMI 2 #define X86_TRAP_BP 3 #define X86_TRAP_OF 4 #define X86_TRAP_BR 5 #define X86_TRAP_UD 6 #define X86_TRAP_NM 7 #define X86_TRAP_DF 8 #define X86_TRAP_OLD_MF 9 #define X86_TRAP_TS 10 #define X86_TRAP_NP 11 #define X86_TRAP_SS 12 #define X86_TRAP_GP 13 #define X86_TRAP_PF 14 #define X86_TRAP_SPURIOUS 15 #define X86_TRAP_MF 16 #define X86_TRAP_AC 17 #define X86_TRAP_MC 18 #define X86_TRAP_XF 19 #define X86_TRAP_VE 20 #define X86_TRAP_CP 21 #define X86_TRAP_VC 29 #define X86_TRAP_IRET 32
ARM中断异常类型 1 2 3 4 5 6 7 8 9 10 __vectors_start: W(b) vector_rst W(b) vector_und W(ldr) pc, __vectors_start + 0x1000 W(b) vector_pabt W(b) vector_dabt W(b) vector_addrexcptn W(b) vector_irq W(b) vector_fiq
MIPS实现过程
将异常处理函数放在中断异常的地址入口(except_vec3_generic放到BASE+0x180位置)
填充中断向量表(填充exception_handlers,exception_handlers[0] = 中断入口)
当发生中断的时候,进入中断异常地址入口
做保护现场等动作
跳转进入except_vec3_generic中断异常处理函数
向量表第一项即会调入平台相关的中断中
如下代码所示即为mips对于trap_init函数的实现(只保留关键代码)。其主要作用是判断寄存器或者系统相关变量的值来设置中断向量表,而中断向量表是一个全局变量,其在genex.S汇编中进行了引用。而在汇编中,通过指令mfc0把控制寄存器CP0_CAUSE值拷贝到通用寄存器k1。CP0_CAUSE保存了这次中断发生的原因。用andi指令把k1的值加上0x7c,然后用指令ld加载具体一个中断处理程序的入口地址exception_handlers(k1)。exception_handlers就是我们记录的中断处理程序,根据k1的值找到当前系统需要执行哪个中断处理程序。然后使用指令jr跳转到这个中断处理程序。
k0和k1是为MIPS系统所保留,专门保留给系统发生中断时程序使用的寄存器。这里的”k”代表keep。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 unsigned long exception_handlers[32 ];void __init *set_except_vector (int n, void *addr) { ... old_handler = xchg(&exception_handlers[n], handler); ... } void __init trap_init (void ) { ... set_handler(0x180 , &except_vec3_generic, 0x80 ); ... set_except_vector(0 , using_rollback_handler() ? rollback_handle_int: handle_int); set_except_vector(1 , handle_tlbm); set_except_vector(2 , handle_tlbl); set_except_vector(3 , handle_tlbs); set_except_vector(4 , handle_adel); set_except_vector(5 , handle_ades); set_except_vector(6 , handle_ibe); set_except_vector(7 , handle_dbe); ... }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 NESTED(except_vec3_generic, 0 , sp) .set push .set noat #if R5432_CP0_INTERRUPT_WAR mfc0 k0, CP0_INDEX #endif mfc0 k1, CP0_CAUSE andi k1, k1, 0x7c #ifdef CONFIG_64BIT dsll k1, k1, 1 #endif ld k0, exception_handlers(k1) jr k0 .set pop END (except_vec3_generic) BUILD_ROLLBACK_PROLOGUE handle_int NESTED (handle_int, PT_SIZE, sp) #ifdef CONFIG_TRACE_IRQFLAGS .set push .set noat ... # Check if already on IRQ stack PTR_LI t1, ~(_THREAD_SIZE-1 ) and t1, t1, sp beq t0, t1, 2f li t1, _IRQ_STACK_START PTR_ADD sp, t0, t1 LONG_S s1, 0(sp) 2: jal plat_irq_dispatch move sp, s1 j ret_from_irq #ifdef CONFIG_CPU_MICROMIPS nop #endif END (handle_int)
如上代码所示,最终跳转到plat_irq_dispatch函数,很明显这个函数是与平台相关的,具体代码不做分析。主要完成的功能是:根据中断状态寄存器判断中断类型和中断号等等,随后通过一系列跳转最终调到 desc->handle_irq(desc)
这样一个语句,即注册的中断服务程序中。
early_irq_init early_irq_init属于与硬件和平台无关的通用逻辑层,为irq_desc[]中个元素的某些成员填充默认值。
完成后调用体系相关的arch_early_irq_init函数完成进一步的初始化工作,不过ARM / MIPS体系没有实现该函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 struct irq_desc irq_desc [NR_IRQS ] __cacheline_aligned_in_smp = { [0 ... NR_IRQS-1 ] = { .handle_irq = handle_bad_irq, .depth = 1 , .lock = __RAW_SPIN_LOCK_UNLOCKED(irq_desc->lock), } }; int __init early_irq_init (void ) { int count, i, node = first_online_node; struct irq_desc *desc ; init_irq_default_affinity(); printk(KERN_INFO "NR_IRQS:%d\n" , NR_IRQS); desc = irq_desc; count = ARRAY_SIZE(irq_desc); for (i = 0 ; i < count; i++) { desc[i].kstat_irqs = alloc_percpu(unsigned int ); alloc_masks(&desc[i], GFP_KERNEL, node); raw_spin_lock_init(&desc[i].lock); lockdep_set_class(&desc[i].lock, &irq_desc_lock_class); desc_set_defaults(i, &desc[i], node, NULL ); } return arch_early_irq_init(); }
init_IRQ 初始化不同SOC的中断处理器等操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 void __init init_IRQ (void ) { int i; for (i = 0 ; i < NR_IRQS; i++) irq_set_noprobe(i); arch_init_irq(); for_each_possible_cpu(i) { int irq_pages = IRQ_STACK_SIZE / PAGE_SIZE; void *s = (void *)__get_free_pages(GFP_KERNEL, irq_pages); irq_stack[i] = s; pr_debug("CPU%d IRQ stack at 0x%p - 0x%p\n" , i, irq_stack[i], irq_stack[i] + IRQ_STACK_SIZE); } }
参考资料