• Linux中断概述


    linux中断流程

    本文简单概述学习一下Linux(Linux3.10,x86)的中断注册处理相关的流程,并讨论一下再中断情况下的调度机制。

    中断向量表的注册

    在调用了start_kernel启动内核之后,会调用trap_init函数来注册。

    void __init trap_init(void)
    {
    	int i;
    
    #ifdef CONFIG_EISA
    	void __iomem *p = early_ioremap(0x0FFFD9, 4);
    
    	if (readl(p) == 'E' + ('I'<<8) + ('S'<<16) + ('A'<<24))
    		EISA_bus = 1;
    	early_iounmap(p, 4);
    #endif
    
    	set_intr_gate(X86_TRAP_DE, &divide_error);
    	set_intr_gate_ist(X86_TRAP_NMI, &nmi, NMI_STACK);
    	/* int4 can be called from all */
    	set_system_intr_gate(X86_TRAP_OF, &overflow);
    	set_intr_gate(X86_TRAP_BR, &bounds);
    	set_intr_gate(X86_TRAP_UD, &invalid_op);
    	set_intr_gate(X86_TRAP_NM, &device_not_available);
    #ifdef CONFIG_X86_32
    	set_task_gate(X86_TRAP_DF, GDT_ENTRY_DOUBLEFAULT_TSS);
    #else
    	set_intr_gate_ist(X86_TRAP_DF, &double_fault, DOUBLEFAULT_STACK);
    #endif
    	set_intr_gate(X86_TRAP_OLD_MF, &coprocessor_segment_overrun);
    	set_intr_gate(X86_TRAP_TS, &invalid_TSS);
    	set_intr_gate(X86_TRAP_NP, &segment_not_present);
    	set_intr_gate_ist(X86_TRAP_SS, &stack_segment, STACKFAULT_STACK);
    	set_intr_gate(X86_TRAP_GP, &general_protection);
    	set_intr_gate(X86_TRAP_SPURIOUS, &spurious_interrupt_bug);
    	set_intr_gate(X86_TRAP_MF, &coprocessor_error);
    	set_intr_gate(X86_TRAP_AC, &alignment_check);
    #ifdef CONFIG_X86_MCE
    	set_intr_gate_ist(X86_TRAP_MC, &machine_check, MCE_STACK);
    #endif
    	set_intr_gate(X86_TRAP_XF, &simd_coprocessor_error);
    
    	/* Reserve all the builtin and the syscall vector: */
    	for (i = 0; i < FIRST_EXTERNAL_VECTOR; i++)
    		set_bit(i, used_vectors);
    
    #ifdef CONFIG_IA32_EMULATION
    	set_system_intr_gate(IA32_SYSCALL_VECTOR, ia32_syscall);
    	set_bit(IA32_SYSCALL_VECTOR, used_vectors);
    #endif
    
    #ifdef CONFIG_X86_32
    	set_system_trap_gate(SYSCALL_VECTOR, &system_call);
    	set_bit(SYSCALL_VECTOR, used_vectors);
    #endif
    
    	/*
    	 * Set the IDT descriptor to a fixed read-only location, so that the
    	 * "sidt" instruction will not leak the location of the kernel, and
    	 * to defend the IDT against arbitrary memory write vulnerabilities.
    	 * It will be reloaded in cpu_init() */
    	__set_fixmap(FIX_RO_IDT, __pa_symbol(idt_table), PAGE_KERNEL_RO);
    	idt_descr.address = fix_to_virt(FIX_RO_IDT);
    
    	/*
    	 * Should be a barrier for any external CPU state:
    	 */
    	cpu_init();
    
    	x86_init.irqs.trap_init();
    
    #ifdef CONFIG_X86_64
    	memcpy(&nmi_idt_table, &idt_table, IDT_ENTRIES * 16);
    	set_nmi_gate(X86_TRAP_DB, &debug);
    	set_nmi_gate(X86_TRAP_BP, &int3);
    #endif
    }
    

    通过调用set_intr_gate函数来写入idt_entry表,将初始的信号和处理的事件写入,通过写入中断处理号和中断处理函数来进行中断源的注册。

    初始化中断

    在上一步将中断和中断源进行注册之后,就将中断进行初始化,即init_IRQ()函数。

    void __init init_IRQ(void)
    {
    	int i;
    
    	/*
    	 * We probably need a better place for this, but it works for
    	 * now ...
    	 */
    	x86_add_irq_domains();
    
    	/*
    	 * On cpu 0, Assign IRQ0_VECTOR..IRQ15_VECTOR's to IRQ 0..15.
    	 * If these IRQ's are handled by legacy interrupt-controllers like PIC,
    	 * then this configuration will likely be static after the boot. If
    	 * these IRQ's are handled by more mordern controllers like IO-APIC,
    	 * then this vector space can be freed and re-used dynamically as the
    	 * irq's migrate etc.
    	 */
    	for (i = 0; i < legacy_pic->nr_legacy_irqs; i++)
    		per_cpu(vector_irq, 0)[IRQ0_VECTOR + i] = i; // 将cpu0的中断号全部注册进去,当前只注册了cpu0的相关中断
    
    	x86_init.irqs.intr_init();   // 初始中断
    }
    

    X86_init.irqs初始化的函数就是native_init_IRQ。

    void __init native_init_IRQ(void)
    {
    	int i;
    
    	/* Execute any quirks before the call gates are initialised: */
    	x86_init.irqs.pre_vector_init();
    
    	apic_intr_init();  // 注册apic相关的中断函数
    
    	/*
    	 * Cover the whole vector space, no vector can escape
    	 * us. (some of these will be overridden and become
    	 * 'special' SMP interrupts)
    	 */
    	i = FIRST_EXTERNAL_VECTOR;
    	for_each_clear_bit_from(i, used_vectors, NR_VECTORS) {
    		/* IA32_SYSCALL_VECTOR could be used in trap_init already. */
    		set_intr_gate(i, interrupt[i - FIRST_EXTERNAL_VECTOR]);
    	}
    
    	if (!acpi_ioapic && !of_ioapic)
    		setup_irq(2, &irq2);
    
    #ifdef CONFIG_X86_32
    	irq_ctx_init(smp_processor_id());
    #endif
    }
    

    设置的每个中断的处理函数都是指向了interrupt这个数组的位置。在x86/kernel/entry_64.S文件中。

    	.section .init.rodata,"a"
    ENTRY(interrupt)     //指向中断
    	.section .entry.text
    	.p2align 5
    	.p2align CONFIG_X86_L1_CACHE_SHIFT
    ENTRY(irq_entries_start)
    	INTR_FRAME
    vector=FIRST_EXTERNAL_VECTOR
    .rept (NR_VECTORS-FIRST_EXTERNAL_VECTOR+6)/7   // 获取数据的参数
    	.balign 32
      .rept	7
        .if vector < NR_VECTORS
          .if vector <> FIRST_EXTERNAL_VECTOR
    	CFI_ADJUST_CFA_OFFSET -8
          .endif
    1:	pushq_cfi $(~vector+0x80)	/* Note: always in signed byte range */
          .if ((vector-FIRST_EXTERNAL_VECTOR)%7) <> 6
    	jmp 2f
          .endif
          .previous
    	.quad 1b
          .section .entry.text
    vector=vector+1
        .endif
      .endr
    2:	jmp common_interrupt  // 调到common_interrput处理
    .endr
    	CFI_ENDPROC
    END(irq_entries_start)
    

    通过跳到common_interrupt来进行中断的处理。

    common_interrupt:
    	XCPT_FRAME
    	ASM_CLAC
    	addq $-0x80,(%rsp)		/* Adjust vector to [-256,-1] range */
    	interrupt do_IRQ      // 调用do_IRQ()处理中断
    	/* 0(%rsp): old_rsp-ARGOFFSET */
    ret_from_intr:
    	DISABLE_INTERRUPTS(CLBR_NONE)
    	TRACE_IRQS_OFF
    	decl PER_CPU_VAR(irq_count)
    
    	/* Restore saved previous stack */
    	popq %rsi
    	CFI_DEF_CFA rsi,SS+8-RBP	/* reg/off reset after def_cfa_expr */
    	leaq ARGOFFSET-RBP(%rsi), %rsp
    	CFI_DEF_CFA_REGISTER	rsp
    	CFI_ADJUST_CFA_OFFSET	RBP-ARGOFFSET
    
    exit_intr:
    	GET_THREAD_INFO(%rcx)
    	testl $3,CS-ARGOFFSET(%rsp)
    	je retint_kernel
    
    	/* Interrupt came from user space */
    	/*
    	 * Has a correct top of stack, but a partial stack frame
    	 * %rcx: thread info. Interrupts off.
    	 */
    retint_with_reschedule:
    	movl $_TIF_WORK_MASK,%edi
    retint_check:
    	LOCKDEP_SYS_EXIT_IRQ
    	movl TI_flags(%rcx),%edx
    	andl %edi,%edx
    	CFI_REMEMBER_STATE
    	jnz  retint_careful
    
    retint_swapgs:		/* return to user-space */
    	/*
    	 * The iretq could re-enable interrupts:
    	 */
    	DISABLE_INTERRUPTS(CLBR_ANY)
    	TRACE_IRQS_IRETQ
    	SWAPGS
    	jmp restore_args
    
    retint_restore_args:	/* return to kernel space */
    	DISABLE_INTERRUPTS(CLBR_ANY)
    	/*
    	 * The iretq could re-enable interrupts:
    	 */
    	TRACE_IRQS_IRETQ
    restore_args:
    	RESTORE_ARGS 1,8,1
    
    irq_return:
    	INTERRUPT_RETURN
    	_ASM_EXTABLE(irq_return, bad_iret)
    

    主要的处理函数位于do_IRQ函数,在中断处理完成之后,无论是返回用户空间还是内核态都会检查一下是否需要进程需求调度。

    unsigned int __irq_entry do_IRQ(struct pt_regs *regs)
    {
    	struct pt_regs *old_regs = set_irq_regs(regs);  // 获取旧的中断
    
    	/* high bit used in ret_from_ code  */
    	unsigned vector = ~regs->orig_ax;
    	unsigned irq;
    
    	irq_enter();   // irq进入中断 禁止中断等
    	exit_idle();
    
    	irq = __this_cpu_read(vector_irq[vector]);  // 读取中断的irq
    
    	if (!handle_irq(irq, regs)) {   // 处理中断
    		ack_APIC_irq();    // 中断回复 
    
    		if (printk_ratelimit())
    			pr_emerg("%s: %d.%d No irq handler for vector (irq %d)\n",
    				__func__, smp_processor_id(), vector, irq);
    	}
    
    	irq_exit();    // irq离开
    
    	set_irq_regs(old_regs);   // 恢复旧的现场
    	return 1;
    }
    

    Handle_irq处理流程如下。

    bool handle_irq(unsigned irq, struct pt_regs *regs)
    {
    	struct irq_desc *desc;
    
    	stack_overflow_check(regs);
    
    	desc = irq_to_desc(irq);
    	if (unlikely(!desc))
    		return false;
    
    	generic_handle_irq_desc(irq, desc);
    	return true;
    }
    
    ...
      
    static inline void generic_handle_irq_desc(unsigned int irq, struct irq_desc *desc)
    {
    	desc->handle_irq(irq, desc);  // 调用注册的handle去处理
    }
    

    至此,中断的处理流程就是这样调用handle处理。

    总结

    本文只是简单的查看了硬件中断的处理与注册流程,大致框架流程就是如上所示。由于本人才疏学浅,如有错误请批评指正。

  • 相关阅读:
    PHP指定时间戳/日期加一天,一年,一周,一月
    redis 不同部署方式性能测试
    数据资产如何入表?有哪些步骤
    Linux 使用用户级别的 systemd 服务
    C-数据结构-树状存储基本概念
    【sqlite3 如何避免插入重复数据】
    解决方法:Ubuntu 22.04网络无法连接,没有网络图标
    案例分享:某汽车企业通过龙智拓展Jira功能,实现高效项目管理
    蓝博图《乡村振兴战略下传统村落文化旅游设计》许少辉八一新著 ​​​
    微信升级后不再使用x5内核,debugx5.qq.com打不开,如何开启微信调试?
  • 原文地址:https://blog.csdn.net/qq_33339479/article/details/127040530