linux那些事之中断与异常(AMD64架构)_2
内核中断初始化过程
《中断与异常(AMD64架构)_1》,主要从硬件角度分析amd64 x86 cpu 中的一些基本概念,以及如何配置x86 cpu的中断向量表等。由于中断需要在内核启动较早的时候进行配置,而此时由于一些资源还未初始化完成,因此早期使用的中断处理函数和最终使用的中断处理函数会有差别,根据启动的不同阶段其设置的中断函数也稍微不同。整个内核中断初始化过程也稍微有点复杂,需要梳理内核启动过程才能更加清楚中断初始化为什么这么做:
中断向向量表在初始化过程如图上述5个过程,分别安装不同的中断的门描述到中断向量表中:
- 系统上电,经由BIOS初始化并引导进行内核启动函数x86_64_start_kernel函数进入内核启动程序,该函数首先做一系列初始化函数包括对早期中断向量表安装函数idt_setup_early_handler,安装必要的中断对应的处理函数。
- 经过一段初始化后,继续安装一些trap类型的中断idt_setup_early_traps, 命令early的意思就是由于早期初始化过程一些资源还未初始化好,但是又不得不使用该中断,所以只能使用特定的一些中断及对应的中断处理函数,等所需要资源初始化完成之后再安装正常的中断处理函数。
- 专门安装page fault中断处理函数idt_setup_early_pf。
- trap_init() 由于此时资源已经准备完毕,可以安装完整的trap 类型中断。
- idt_setup_apic_and_irq_gates: 安装apic 相关中断以及对本地cpu的中断处理函数。
1:early_idt_handler_array
early_idt_handler_array是内核启动之后,安装的第一个中断相关处理函数,其安装函数为:
idt_setup_early_handler()
idt_setup_early_handler主要kernel 初期所安装使用的中断向量表,由于太过于早期,需要安装特殊的中断处理函数,所支持的中断处理函数也比较少:
void __init idt_setup_early_handler(void)
{int i;for (i = 0; i < NUM_EXCEPTION_VECTORS; i++)set_intr_gate(i, early_idt_handler_array[i]);
#ifdef CONFIG_X86_32for ( ; i < NR_VECTORS; i++)set_intr_gate(i, early_ignore_irq);
#endifload_idt(&idt_descr);
}
- 使用set_intr_gate,设置中断到中断表idt_table中,NUM_EXCEPTION_VECTORS宏为32,意思是只安装硬件保留的32个中断对应的中断函数
- 如果是32为 x86系统则需要安装irq 早期中断
- load_idt(&idt_descr),在《中断与异常(AMD64架构)_1》已经说明 主要是将中断向量表idt_table地址和大小设置到中断中断向量寄存器中。
early_idt_handler_array变量
early_idt_handler_array变量是定义早期中断相关处理函数数组,early_idt_handler_array声明如下:
extern const char early_idt_handler_array[NUM_EXCEPTION_VECTORS][EARLY_IDT_HANDLER_SIZE];
- early_idt_handler_array为一个二维数组,NUM_EXCEPTION_VECTORS* EARLY_IDT_HANDLER_SIZE其中NUM_EXCEPTION_VECTORS为32表示为所有预留的32个向量,每个向量对应大小为EARLY_IDT_HANDLER_SIZE 9个字节,其格式为2个字节的备用指令用于向栈中压入默认错误码(如果异常本身没有提供错误码),2个字节的指令用于向栈中压入向量号,剩余5个字节用于跳转到异常中断处理程序。
定义问题,由于early_idt_handler_array数组需要在早于kernel 启动之前定义并初始化,而在调用kernel之前都是一堆汇编启动,因此只能通过汇编语将该数组定义并初始化(位于arch\x86\kernel\head_64.s文件中):
SYM_CODE_START(early_idt_handler_array)i = 0.rept NUM_EXCEPTION_VECTORS.if ((EXCEPTION_ERRCODE_MASK >> i) & 1) == 0UNWIND_HINT_IRET_REGSpushq $0 # Dummy error code, to make stack frame uniform.elseUNWIND_HINT_IRET_REGS offset=8.endifpushq $i # 72(%rsp) Vector numberjmp early_idt_handler_commonUNWIND_HINT_IRET_REGSi = i + 1.fill early_idt_handler_array + i*EARLY_IDT_HANDLER_SIZE - ., 1, 0xcc.endrUNWIND_HINT_IRET_REGS offset=16
SYM_CODE_END(early_idt_handler_array)
- SYM_CODE_START 和 SYM_CODE_END为内核使用语法表示一段代码起始和结束位置,意思是明确表明知道这段代码段是做什么用的,类似与已经弃用的ENTRY 和END:
SYM_CODE_START
andSYM_CODE_START_LOCAL
should be used only in special cases – if you know what you are doing. This is used exclusively for interrupt handlers and similar where the calling convention is not the C one._NOALIGN
variants exist too
- rept指令为binutils汇编指令,其格式为.rept count, 其中count为重复后面endr之前的代码次数,例如:
.rept 3 .long 0 .endr
上述用例是.rept 3重复执行endr之前代码long 0三次,执行效果相等于:
.long 0 .long 0 .long 0
在SYM_CODE_START(early_idt_handler_array) 代码中:
.rept NUM_EXCEPTION_VECTORS... ... .endr
NUM_EXCEPTION_VECTORS 为32,意思为重复执行endr之前代码32次,循环处理处理每个中断处理函数。
- pushq $0 和 pushq $i 分别将返回值错误码和中断向量号进入到栈中
- jmp early_idt_handler_common 跳入到中断通用中断处理中。
- .fill 指令填充数个 0xcc,.fill 指令格式如下:
.fill repeat , size , value
early_idt_handler_common
early_idt_handler_common代码段主要处理中断:
SYM_CODE_START_LOCAL(early_idt_handler_common)/** The stack is the hardware frame, an error code or zero, and the* vector number.*/cldincl early_recursion_flag(%rip)/* The vector number is currently in the pt_regs->di slot. */pushq %rsi /* pt_regs->si */movq 8(%rsp), %rsi /* RSI = vector number */movq %rdi, 8(%rsp) /* pt_regs->di = RDI */pushq %rdx /* pt_regs->dx */pushq %rcx /* pt_regs->cx */pushq %rax /* pt_regs->ax */pushq %r8 /* pt_regs->r8 */pushq %r9 /* pt_regs->r9 */pushq %r10 /* pt_regs->r10 */pushq %r11 /* pt_regs->r11 */pushq %rbx /* pt_regs->bx */pushq %rbp /* pt_regs->bp */pushq %r12 /* pt_regs->r12 */pushq %r13 /* pt_regs->r13 */pushq %r14 /* pt_regs->r14 */pushq %r15 /* pt_regs->r15 */UNWIND_HINT_REGScmpq $14,%rsi /* Page fault? */jnz 10fGET_CR2_INTO(%rdi) /* can clobber %rax if pv */call early_make_pgtableandl %eax,%eaxjz 20f /* All good */10:movq %rsp,%rdi /* RDI = pt_regs; RSI is already trapnr */call early_fixup_exception20:decl early_recursion_flag(%rip)jmp restore_regs_and_return_to_kernel
SYM_CODE_END(early_idt_handler_common)
- pushq %rsi 将rsi寄存器的值存放到栈中,后续用于存放向量号
- movq 8(%rsp), %rsi:将中断向量号存放到rsi中
- movq %rdi, 8(%rsp):将rdi 寄存器中的内容存放到rsp寄存器中
- pushq %rdx ... pushq %r15 将一系列后续要用到的寄存器保存入栈,用于后续返回现场使用
- cmpq $14,%rsi: 单独处理14号中断(page fault):
- 如果是缺页中断,则从CR2寄存器中获取到GET_CR2_INTO(%rdi) 缺页地址到rdi寄存器中(rdi寄存器x86系统下用于传参)
- page fault中断调用early_make_pgtable,用于处理page fault中断,由于在系统早期很多资源buddy,以及内核先吃等还未启动,因此page fault此时只能在早期单独进行管理,管理结构early_top_pgt
- 如果不是page fault中断,则通过jnz 10f跳转到early_fixup_exception对其他中断统一处理,从main_extable_sort_needed 中查找是否有对应的中断处理。
- %eax寄存器用来放early_make_pgtable函数的返回值,andl用于检查返回值通过求与,如果为0则调restore_regs_and_return_to_kernel,对返回值进行处理。
可以看到早期early_idt_handler_array中断处理表中主要是对page fault异常进行专门处理。
2:idt_setup_early_trap()
idt_setup_early_traps()函数主要用于安装早期的一些特殊trap类型中断,函数如下:
void __init idt_setup_early_traps(void)
{idt_setup_from_table(idt_table, early_idts, ARRAY_SIZE(early_idts),true);load_idt(&idt_descr);
}
所安装的中断全部位于early_idts表中,同时调用load_idt()刷新中断向量表寄存器。
early_idts
early_idts中支持的中断如下:
static const __initconst struct idt_data early_idts[] = {INTG(X86_TRAP_DB, asm_exc_debug),SYSG(X86_TRAP_BP, asm_exc_int3),#ifdef CONFIG_X86_32/** Not possible on 64-bit. See idt_setup_early_pf() for details.*/INTG(X86_TRAP_PF, asm_exc_page_fault),
#endif
};
所支持的中断为X86_TRAP_DB 和X86_TRAP_BP, 如果是x86 32位系统则还支持X86_TRAP_PF,x86 64位系统后面通过idt_setup_early_pf刷新。
3:idt_setup_early_pf
idt_setup_early_pf()函数,主要是x86 64位系统 更新page fault:
void __init idt_setup_early_pf(void)
{idt_setup_from_table(idt_table, early_pf_idts,ARRAY_SIZE(early_pf_idts), true);
}
early_pf_idts
early_pf_idts支持的中断如下:
/** Early traps running on the DEFAULT_STACK because the other interrupt* stacks work only after cpu_init().*/
static const __initconst struct idt_data early_pf_idts[] = {INTG(X86_TRAP_PF, asm_exc_page_fault),
};
4:Trap中断更新
经过初始化之后,之前的early trap已经不在实用,需要重新更新trap相关中断
idt_setup_traps
/*** idt_setup_traps - Initialize the idt table with default traps*/
void __init idt_setup_traps(void)
{idt_setup_from_table(idt_table, def_idts, ARRAY_SIZE(def_idts), true);
}
从def_idts 中更新相关支持的trap类型中断。
def_idts
def_idts支持的中断如下:
/** The default IDT entries which are set up in trap_init() before* cpu_init() is invoked. Interrupt stacks cannot be used at that point and* the traps which use them are reinitialized with IST after cpu_init() has* set up TSS.*/
static const __initconst struct idt_data def_idts[] = {INTG(X86_TRAP_DE, asm_exc_divide_error),INTG(X86_TRAP_NMI, asm_exc_nmi),INTG(X86_TRAP_BR, asm_exc_bounds),INTG(X86_TRAP_UD, asm_exc_invalid_op),INTG(X86_TRAP_NM, asm_exc_device_not_available),INTG(X86_TRAP_OLD_MF, asm_exc_coproc_segment_overrun),INTG(X86_TRAP_TS, asm_exc_invalid_tss),INTG(X86_TRAP_NP, asm_exc_segment_not_present),INTG(X86_TRAP_SS, asm_exc_stack_segment),INTG(X86_TRAP_GP, asm_exc_general_protection),INTG(X86_TRAP_SPURIOUS, asm_exc_spurious_interrupt_bug),INTG(X86_TRAP_MF, asm_exc_coprocessor_error),INTG(X86_TRAP_AC, asm_exc_alignment_check),INTG(X86_TRAP_XF, asm_exc_simd_coprocessor_error),#ifdef CONFIG_X86_32TSKG(X86_TRAP_DF, GDT_ENTRY_DOUBLEFAULT_TSS),
#elseINTG(X86_TRAP_DF, asm_exc_double_fault),
#endifINTG(X86_TRAP_DB, asm_exc_debug),#ifdef CONFIG_X86_MCEINTG(X86_TRAP_MC, asm_exc_machine_check),
#endifSYSG(X86_TRAP_OF, asm_exc_overflow),
#if defined(CONFIG_IA32_EMULATION)SYSG(IA32_SYSCALL_VECTOR, entry_INT80_compat),
#elif defined(CONFIG_X86_32)SYSG(IA32_SYSCALL_VECTOR, entry_INT80_32),
#endif
};
idt_setup_ist_traps
继续更新trap相关中断:
/*** idt_setup_traps - Initialize the idt table with default traps*/
void __init idt_setup_traps(void)
{idt_setup_from_table(idt_table, def_idts, ARRAY_SIZE(def_idts), true);
}
可以看到本质上还是更新def_idts。
5:更新ACPI和IRQ相关中断
最好更新ACPI和IRQ相关中断
idt_setup_apic_and_irq_gates
/*** idt_setup_apic_and_irq_gates - Setup APIC/SMP and normal interrupt gates*/
void __init idt_setup_apic_and_irq_gates(void)
{int i = FIRST_EXTERNAL_VECTOR;void *entry;idt_setup_from_table(idt_table, apic_idts, ARRAY_SIZE(apic_idts), true);for_each_clear_bit_from(i, system_vectors, FIRST_SYSTEM_VECTOR) {entry = irq_entries_start + 8 * (i - FIRST_EXTERNAL_VECTOR);set_intr_gate(i, entry);}#ifdef CONFIG_X86_LOCAL_APICfor_each_clear_bit_from(i, system_vectors, NR_VECTORS) {/** Don't set the non assigned system vectors in the* system_vectors bitmap. Otherwise they show up in* /proc/interrupts.*/entry = spurious_entries_start + 8 * (i - FIRST_SYSTEM_VECTOR);set_intr_gate(i, entry);}
#endif/* Map IDT into CPU entry area and reload it. */idt_map_in_cea();load_idt(&idt_descr);/* Make the IDT table read only */set_memory_ro((unsigned long)&idt_table, 1);idt_setup_done = true;
}
- ACPI中断来源与apic_idts表中
- CONFIG_X86_LOCAL_APIC 如果本地cpu有特殊的中断,则进行更新
apic_idts
apic_idts相关支持的中断如下:
/** The APIC and SMP idt entries*/
static const __initconst struct idt_data apic_idts[] = {
#ifdef CONFIG_SMPINTG(RESCHEDULE_VECTOR, asm_sysvec_reschedule_ipi),INTG(CALL_FUNCTION_VECTOR, asm_sysvec_call_function),INTG(CALL_FUNCTION_SINGLE_VECTOR, asm_sysvec_call_function_single),INTG(IRQ_MOVE_CLEANUP_VECTOR, asm_sysvec_irq_move_cleanup),INTG(REBOOT_VECTOR, asm_sysvec_reboot),
#endif#ifdef CONFIG_X86_THERMAL_VECTORINTG(THERMAL_APIC_VECTOR, asm_sysvec_thermal),
#endif#ifdef CONFIG_X86_MCE_THRESHOLDINTG(THRESHOLD_APIC_VECTOR, asm_sysvec_threshold),
#endif#ifdef CONFIG_X86_MCE_AMDINTG(DEFERRED_ERROR_VECTOR, asm_sysvec_deferred_error),
#endif#ifdef CONFIG_X86_LOCAL_APICINTG(LOCAL_TIMER_VECTOR, asm_sysvec_apic_timer_interrupt),INTG(X86_PLATFORM_IPI_VECTOR, asm_sysvec_x86_platform_ipi),
# ifdef CONFIG_HAVE_KVMINTG(POSTED_INTR_VECTOR, asm_sysvec_kvm_posted_intr_ipi),INTG(POSTED_INTR_WAKEUP_VECTOR, asm_sysvec_kvm_posted_intr_wakeup_ipi),INTG(POSTED_INTR_NESTED_VECTOR, asm_sysvec_kvm_posted_intr_nested_ipi),
# endif
# ifdef CONFIG_IRQ_WORKINTG(IRQ_WORK_VECTOR, asm_sysvec_irq_work),
# endif
# ifdef CONFIG_X86_UVINTG(UV_BAU_MESSAGE, asm_sysvec_uv_bau_message),
# endifINTG(SPURIOUS_APIC_VECTOR, asm_sysvec_spurious_apic_interrupt),INTG(ERROR_APIC_VECTOR, asm_sysvec_error_interrupt),
#endif
};
参考资料
https://www.kernel.org/doc/html/latest/asm-annotations.html
Rept (Using as)
早期的中断和异常控制 · Linux Insides中文
linux那些事之中断与异常(AMD64架构)_2相关推荐
- linux那些事之page fault(AMD64架构)(user space)(2)
do_user_addr_fault 用户空间地址处理是page fault主要处理流程,x86 64位系统主要是do_user_addr_fault()函数 该处理部分是x86架构特有部分 即与架构 ...
- linux那些事之中断与异常(AMD64架构)_1
中断与异常 中断及异常是学习操作系统必备知识,通常CPU时将其设计作为打破当前执行程序流程的一种手段,同时也是cpu与外部硬件交互的一种手段. 一般而言中断可以分为可以屏蔽(maskable)和不可屏 ...
- linux那些事之page fault(AMD64架构)(1)
应用程序或者内核都是运行在虚拟内存空间之中,kernel 启动完成之后如果一个虚拟地址要访问物理内存需要通过CPU MMU硬件进行地址转换,整个虚拟地址访问物理内存逻辑过程如下: kernel 启动完 ...
- Linux内核深入理解中断和异常(6):IRQs的非早期初始化
Linux内核深入理解中断和异常(6):IRQs的非早期初始化 rtoax 2021年3月 0x00-0x1f architecture-defined exceptions and interrup ...
- Linux内核深入理解中断和异常(1)
Linux内核深入理解中断和异常(1) rtoax 2021年3月 1. 中断介绍 内核中第一个子系统是中断(interrupts). 1.1. 什么是中断? 我们已经在这本书的很多地方听到过 中断( ...
- Linux内核深入理解中断和异常(8):串口驱动程序
Linux内核深入理解中断和异常(8):串口驱动程序 rtoax 2021年3月 /*** start_kernel()->setup_arch()->idt_setup_early_tr ...
- Linux内核深入理解中断和异常(7):中断下半部:Softirq, Tasklets and Workqueues
Linux内核深入理解中断和异常(7):中断下半部:Softirq, Tasklets and Workqueues rtoax 2021年3月 0x00-0x1f architecture-defi ...
- Linux内核深入理解中断和异常(5):外部中断
Linux内核深入理解中断和异常(5):外部中断 rtoax 2021年3月 1. 外部中断简介 外部中断包括:键盘,鼠标,打印机等. 外部中断包括: I/O interrupts; IO中断 Tim ...
- Linux内核深入理解中断和异常(3):异常处理的实现(X86_TRAP_xx)
Linux内核深入理解中断和异常(3):异常处理的实现(X86_TRAP_xx) rtoax 2021年3月 /*** start_kernel()->setup_arch()->idt_ ...
最新文章
- 三角形最小路径和—leetcode120
- 采访田飞师兄有感 ——by 李皈颖
- 输入学号查询课程c语言,广工c语言课程设计
- 实战MongoDB-Replication之Master-Slave
- 【React Native 安卓开发】----侧边栏的实现DrawerLayoutAndroid以及第三方框架react-native-side-menu的使用【第六篇】
- Arcgis重采样或者裁剪的问题
- RPC规范接口实现模块Flask-JSONRPC
- plot 串口助手,DataScope软件的数据,导入matlab绘图。温度曲线
- 【typecho插件】typecho邮箱插件LoveXiaozhou是一款Typecho邮件通知类插件、小周
- 笔记:Python Data Science Toolbox (Part 1)
- 第48节 C语言课程总结与展望
- R语言如何释放运行之后的内存?
- 计算机生产管理系统培训,{生产管理培训}生产企业审核系统讲义.pdf
- 学php应该怎么学习数学,数学难学,数学到底该怎么学?
- java 去掉空行_java 去掉空行
- gcc ------ 编译与链接选项及CFLAGS、LDFLAGS、LIBS
- html页面用excel打印,excel怎么打印不能全部显示出来
- Vue项目中操作svg文件
- 同步与异步区别之我见(一)
- android led弹幕,LED弹幕手持字幕