中断分类
Linux 系统中, 中断分为:
硬中断: 由外部设备或者执行异常产生的需要快速处理的中断如缺页中断定时器硬件中断
根据内部产生还是外部产生, 分为:
异常: 异常是内部产生的中断, 不可屏蔽
外部中断: 外部中断是由外部设备产生的, 可以屏蔽
软中断:
软中断是 Linux 系统中断处理的底半处理部分, 是 Linux 模拟的中断为了加快硬件中断的处理, 防止数据的丢失, Linux 对中断处理分为顶半处理和底半处理两部分, 顶半处理程序快速处理硬件事件, 把不是那么紧急的逻辑放到底半处理程序中, 可以简单的认为硬终端处理程序为顶半处理程序, 软中断处理程序为底半处理程序软中断一般在硬中断处理程序执行后才会执行但是当硬中断嵌套的时候, 软中断会在所有的硬中断处理完毕后才会处理, 当软中断太多, 会放到 ksoftirqd 线程中处理
内核初始化 - 中断
intel 处理器有 256 个硬中断号其中前 32 个中断号为异常使用, 在内核初始化的时候进行初始化内核初始化的代码流程如下:
可以看到首先初始化异常处理, 再初始化部分外部中断, 再初始化一部分软中断处理
- asmlinkage void __init start_kernel(void)
- {
- lock_kernel();
- ...
- // 初始化调度模块
- sched_init();
- ...
- sort_main_extable();
- // 初始化异常处理
- trap_init();
- ...
- // 初始化外部中断
- init_IRQ();
- ...
- // 初始化定时器模块, 同时, 会注册定时器的软中断处理函数
- init_timers();
- // 初始化软中断)
- softirq_init();
- time_init();
- ...
- // 初始化
- acpi_early_init();
- }
异常中断初始化
异常中断在内核中称为 trap, 异常中断初始化代码为
- // 门初始化初始化中断向量表系统有固定的 256 个硬件中断向量
- void __init trap_init(void)
- {
- set_intr_gate(0,÷_error);
- set_intr_gate_ist(1,&debug,DEBUG_STACK);
- set_intr_gate_ist(2,&nmi,NMI_STACK);
- set_intr_gate(3,&int3);
- set_system_gate(4,&overflow); /* int4-5 can be called from all */
- set_system_gate(5,&bounds);
- set_intr_gate(6,&invalid_op);
- set_intr_gate(7,&device_not_available);
- set_intr_gate_ist(8,&double_fault, DOUBLEFAULT_STACK);
- set_intr_gate(9,&coprocessor_segment_overrun);
- set_intr_gate(10,&invalid_TSS);
- set_intr_gate(11,&segment_not_present);
- set_intr_gate_ist(12,&stack_segment,STACKFAULT_STACK);
- set_intr_gate(13,&general_protection);
- set_intr_gate(14,&page_fault);
- set_intr_gate(15,&spurious_interrupt_bug);
- set_intr_gate(16,&coprocessor_error);
- set_intr_gate(17,&alignment_check);
- #ifdef CONFIG_X86_MCE
- set_intr_gate_ist(18,&machine_check, MCE_STACK);
- #endif
- set_intr_gate(19,&simd_coprocessor_error);
- #ifdef CONFIG_IA32_EMULATION
- set_system_gate(IA32_SYSCALL_VECTOR, ia32_syscall);
- #endif
- set_intr_gate(KDB_VECTOR, call_debug);
- /*
- * Should be a barrier for any external CPU state.
- */
- cpu_init();
- }
总结如下:
中断向量号 | 异常事件 | Linux 的处理程序 |
---|---|---|
0 | 除法错误 | Divide_error |
1 | 调试异常 | Debug |
2 | NMI 中断 | Nmi |
3 | 单字节,int 3 | Int3 |
4 | 溢出 | Overflow |
5 | 边界监测中断 | Bounds |
6 | 无效操作码 | Invalid_op |
7 | 设备不可用 | Device_not_available |
8 | 双重故障 | Double_fault |
9 | 协处理器段溢出 | Coprocessor_segment_overrun |
10 | 无效 TSS | Incalid_tss |
11 | 缺段中断 | Segment_not_present |
12 | 堆栈异常 | Stack_segment |
13 | 一般保护异常 | General_protection |
14 | 页异常 | Page_fault |
15 | Spurious_interrupt_bug | |
16 | 协处理器出错 | Coprocessor_error |
17 | 对齐检查中断 | Alignment_check |
0x80 | 系统调用 | ia32_syscall |
0xf9 | 内核调试 | call_debug |
上述中断处理函数都是汇编语言编写一部分汇编直接处理完毕, 一部分通过调用 C 函数帮助处理
汇编代码在
linux/arch/x86_64/entry.S
中, 大部分都是调用 C 函数
do_中断处理函数名
处理
整理如下:
中断向量号 | 异常事件 | Linux 汇编 | 调用 c 函数 | 处理结果 |
---|---|---|---|---|
0 | 除法错误 | Divide_error | do_divide_error | 发送 SIGFPE 信号 |
1 | 调试异常 | Debug | do_debug | 发送 SIGTRAP 信号 |
2 | NMI 中断 | Nmi | do_nmi | |
3 | 单字节,int 3 | Int3 | do_int3 | 发送 SIGTRAP 信号 |
4 | 溢出 | Overflow | do_overflow | 发送 SIGSEGV 信号 |
5 | 边界监测中断 | Bounds | do_bounds | 发送 SIGSEGV 信号 |
6 | 无效操作码 | Invalid_op | do_invalid_op | 发送 SIGILL 信号 |
7 | 设备不可用 | Device_not_available | math_state_restore | 发送 SIGSEGV 信号 |
8 | 双重故障 | Double_fault | do_double_fault | |
9 | 协处理器段溢出 | Coprocessor_segment_overrun | do_coprocessor_segment_overrun | 发送 SIGFPE 信号 |
10 | 无效 TSS | Invalid_tss | do_invalid_TSS | 发送 SIGSEGV 信号 |
11 | 缺段中断 | Segment_not_present | do_segment_not_present | 发送 SIGBUS 信号 |
12 | 堆栈异常 | Stack_segment | do_stack_segment | |
13 | 一般保护异常 | General_protection | do_general_protection | |
14 | 页异常 | Page_fault | do_page_fault | 处理缺页中断 |
15 | Spurious_interrupt_bug | do_spurious_interrupt_bug | ||
16 | 协处理器出错 | Coprocessor_error | do_coprocessor_error | 发送 SIGFPE 信号 |
17 | 对齐检查中断 | Alignment_check | do_alignment_check | 发送 SIGBUS 信号 |
0x80 | 系统调用 | ia32_syscall | ||
0xf9 | 内核调试 | call_debug | do_call_debug |
外部中断初始化
中断控制器硬件 APIC 分为两种: 本地 APIC 和全局 APIC 本地 APIC 集成在 CPU 内部, 每个 CPU 都有一个, 用于处理本地中断请求, CPU 可以通过 APIC 向其他 CPU 发送中断, 现在主要用于 CPU 之间的通信 (IPI) 全局 APIC 主要是连接外部设备, 用于外部设备的中断在内核中断初始化的时候, 会初始化三个与 IPI 相关中断
- void __init init_IRQ(void)
- {
- int i;
- /**
- * 该函数主要是初始化硬件
- * 1. 初始化本地 APIC 控制芯片
- * 2. 初始化 8259A 芯片
- /
- init_ISA_irqs();
- /*
- * 清空 32 以后的中断向量表(除了系统调用和内核调试用的中断号)
- */
- for (i = 0; i <(NR_VECTORS - FIRST_EXTERNAL_VECTOR); i++) {
- int vector = FIRST_EXTERNAL_VECTOR + i;
- if (i>= NR_IRQS)
- break;
- if (vector != IA32_SYSCALL_VECTOR && vector != KDB_VECTOR) {
- set_intr_gate(vector, interrupt[i]);
- }
- }
- // 多处理器通信中断
- #ifdef CONFIG_SMP
- set_intr_gate(FIRST_DEVICE_VECTOR, interrupt[0]);
- set_intr_gate(RESCHEDULE_VECTOR, reschedule_interrupt);
- set_intr_gate(INVALIDATE_TLB_VECTOR, invalidate_interrupt);
- set_intr_gate(CALL_FUNCTION_VECTOR, call_function_interrupt);
- #endif
- // 本地 APIC 中断
- #ifdef CONFIG_X86_LOCAL_APIC
- /* self generated IPI for local APIC timer */
- set_intr_gate(LOCAL_TIMER_VECTOR, apic_timer_interrupt);
- set_intr_gate(SPURIOUS_APIC_VECTOR, spurious_interrupt);
- set_intr_gate(ERROR_APIC_VECTOR, error_interrupt);
- #endif
- setup_timer();
- if (!acpi_ioapic)
- setup_irq(2, &irq2);
- }
总结如下:
中断向量号 | 中断名 | 异常事件 | 中断处理函数 | 调用 c 函数 | 处理结果 |
---|---|---|---|---|---|
0xfc | RESCHEDULE_VECTOR | 处理器间中断, 用于 cpu 之间同学,其他 cpu 要求重新调度 | reschedule_interrupt | smp_reschedule_interrupt | 将线程调度标志置为需要重新调度。之后内核检查标志的时候会重新调度线程 |
0xfd | INVALIDATE_TLB_VECTOR | 处理器间中断, 用于 cpu 之间通信,其他 cpu 要求 TLB 缓存失效 | invalidate_interrupt | smp_invalidate_interrupt | cpu 刷新 TLB |
0xfa | CALL_FUNCTION_VECTOR | 处理器间中断, 用于 cpu 之间通信,让另外的 cpu 调用某个函数 | call_function_interrupt | smp_call_function_interrupt | 函数数据通过 call_data_struct 传送,cpu 会调用该函数 |
0xef | LOCAL_TIMER_VECTOR | APIC 定期器中断 | apic_timer_interrupt | smp_apic_timer_interrupt | 触发定时器的软中断 |
0xff | SPURIOUS_APIC_VECTOR | 伪中断 | spurious_interrupt | smp_spurious_interrupt | 忽略 |
0xfe | ERROR_APIC_VECTOR | APIC 错误 | error_interrupt | smp_error_interrupt | 打印错误 |
0xfa 中断说明:
当 cpu 需要另一个 cpu 执行某个函数时, 只需要初始化
- struct call_data_struct {
- void (*func) (void *info);
- void *info;
- atomic_t started;
- atomic_t finished;
- int wait;
- };
的结构体, 然后发出一个 0xfa 中断即可
软中断初始化
软中断初始化分为两部分:
初始化定时器时, 会打开 TIMER_SOFTIRQ 的软中断, 并设置中断处理函数为 run_timer_softirq
softirq_init 函数执行, 会打开 TASKLET_SOFTIRQ 和 HI_SOFTIRQ, 处理函数分别为 tasklet_action 和 tasklet_hi_action
软中断的线程处理机制就不说了
来源: https://www.cnblogs.com/stonehat/p/8681639.html