内核中断初始化过程

《中断与异常(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 and SYM_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相关推荐

  1. linux那些事之page fault(AMD64架构)(user space)(2)

    do_user_addr_fault 用户空间地址处理是page fault主要处理流程,x86 64位系统主要是do_user_addr_fault()函数 该处理部分是x86架构特有部分 即与架构 ...

  2. linux那些事之中断与异常(AMD64架构)_1

    中断与异常 中断及异常是学习操作系统必备知识,通常CPU时将其设计作为打破当前执行程序流程的一种手段,同时也是cpu与外部硬件交互的一种手段. 一般而言中断可以分为可以屏蔽(maskable)和不可屏 ...

  3. linux那些事之page fault(AMD64架构)(1)

    应用程序或者内核都是运行在虚拟内存空间之中,kernel 启动完成之后如果一个虚拟地址要访问物理内存需要通过CPU MMU硬件进行地址转换,整个虚拟地址访问物理内存逻辑过程如下: kernel 启动完 ...

  4. Linux内核深入理解中断和异常(6):IRQs的非早期初始化

    Linux内核深入理解中断和异常(6):IRQs的非早期初始化 rtoax 2021年3月 0x00-0x1f architecture-defined exceptions and interrup ...

  5. Linux内核深入理解中断和异常(1)

    Linux内核深入理解中断和异常(1) rtoax 2021年3月 1. 中断介绍 内核中第一个子系统是中断(interrupts). 1.1. 什么是中断? 我们已经在这本书的很多地方听到过 中断( ...

  6. Linux内核深入理解中断和异常(8):串口驱动程序

    Linux内核深入理解中断和异常(8):串口驱动程序 rtoax 2021年3月 /*** start_kernel()->setup_arch()->idt_setup_early_tr ...

  7. Linux内核深入理解中断和异常(7):中断下半部:Softirq, Tasklets and Workqueues

    Linux内核深入理解中断和异常(7):中断下半部:Softirq, Tasklets and Workqueues rtoax 2021年3月 0x00-0x1f architecture-defi ...

  8. Linux内核深入理解中断和异常(5):外部中断

    Linux内核深入理解中断和异常(5):外部中断 rtoax 2021年3月 1. 外部中断简介 外部中断包括:键盘,鼠标,打印机等. 外部中断包括: I/O interrupts; IO中断 Tim ...

  9. Linux内核深入理解中断和异常(3):异常处理的实现(X86_TRAP_xx)

    Linux内核深入理解中断和异常(3):异常处理的实现(X86_TRAP_xx) rtoax 2021年3月 /*** start_kernel()->setup_arch()->idt_ ...

最新文章

  1. 三角形最小路径和—leetcode120
  2. 采访田飞师兄有感 ——by 李皈颖
  3. 输入学号查询课程c语言,广工c语言课程设计
  4. 实战MongoDB-Replication之Master-Slave
  5. 【React Native 安卓开发】----侧边栏的实现DrawerLayoutAndroid以及第三方框架react-native-side-menu的使用【第六篇】
  6. Arcgis重采样或者裁剪的问题
  7. RPC规范接口实现模块Flask-JSONRPC
  8. plot 串口助手,DataScope软件的数据,导入matlab绘图。温度曲线
  9. 【typecho插件】typecho邮箱插件LoveXiaozhou是一款Typecho邮件通知类插件、小周
  10. 笔记:Python Data Science Toolbox (Part 1)
  11. 第48节 C语言课程总结与展望
  12. R语言如何释放运行之后的内存?
  13. 计算机生产管理系统培训,{生产管理培训}生产企业审核系统讲义.pdf
  14. 学php应该怎么学习数学,数学难学,数学到底该怎么学?
  15. java 去掉空行_java 去掉空行
  16. gcc ------ 编译与链接选项及CFLAGS、LDFLAGS、LIBS
  17. html页面用excel打印,excel怎么打印不能全部显示出来
  18. Vue项目中操作svg文件
  19. 同步与异步区别之我见(一)
  20. android led弹幕,LED弹幕手持字幕

热门文章

  1. JavaEE基础(03):Http请求详解,握手挥手流程简介
  2. hadoop--集群崩溃处理方法
  3. linux下查看硬盘信息、硬盘分区、格式化、挂载、及swap分区
  4. android中wifi输入的密码保存的路径
  5. 加速进军自动驾驶领域,福特计划推出自动驾驶出租车服务
  6. Percona XtraBackup热备份实践
  7. php中函数前加符号的作用分解
  8. twisted 网络通信的简单例子
  9. 读写XML文档时,去掉新增加节点的“空命名空间”(xmlns=””)
  10. 自动根据键盘位置调整UITextView的高度