【计算子系统】CPU中断处理

Linux之中断interrupt机制 Linux内核之interrup机制分析 阅读详情

从计算机系统内部看,中断无时无刻不在,这篇博文就和大家一起探讨中断的原理,并以x86_64平台上的linux 3.10内核为例来分析底层实现细节。

什么是中断?

中断是一个系统过程,是计算系统中外部设备向CPU(或CPU之间)通知事件发生的一种机制。这种说法也许有些偏底层,可以从上层更直观的角度来理解:想象你正面对你的个人电脑,当你按下一个键盘按键时,你就触发了一个中断,随后屏幕上会出现你期望的字母;或者当你移动鼠标时,你也会触会一个中断,随后光标会随鼠标的移动而移动。严格意义上说,中断只是一个系统过程(系统调用过程如果抛开其核心处理函数的执行,也是一个系统过程),是系统的一个部分,而不是一个完整系统,因为它不具备完整系统所应有的功能、性能、可靠性、可扩展性、安全性、兼容性、可维护性等各方面的属性。比如,通过按动键盘,你以中断的方式向计算系统发送命令,但真正执行命令并返回结果的是计算机系统而不是按键动作本身。

为什么需要中断?

计算机是个“死脑筋”,从打开电源键开始,就算你不向她发送任何指令,她也会按设定的程序开始忙禄,大多时候都在执行一个叫做IDEL的无聊程序。如何让她听命于你呢?两种方法,要么让她随时可被打断,去做你想让她做的事,随后再去干原来被打断的事;要么让她不停地一直问你想让她做什么,其它的事啥也不干,这样你一发话,她可以立刻响应你的命令。第一种方式,便是中断(interrupt),第二种方式叫轮询(polling)。在大多数场景下,中断都是一种更为高效的(从完成任务数来看)通知方式,因为计算机干了更多有意义的事,而不是一直在“傻问”;轮询时,如果你想让计算机干得活不是很多,那么计算机大多数问询得到结果都是“谢谢,我不需要你做什么”,这就白白浪费了她的宝贵精力,但是每次你想让计算机做事时,她总是先主动地询问你,之后便立刻进入工作状态了,这比下达命令之后才慢吞吞开始行动的中断方式更为高效(从任务响应时间来看)。因此中断和轮询各有优点,各有各的适用场景:中断适用大多数设备通知场景,而在处理时延敏感场景下(如高性能网络转发),轮询表现得更好。

如何实现中断?

下面我们深入系统内部,更细致地理解中断过程。前期的博文介绍过intel i440fx体系的基本组成,这里我们做些简化,只看和中断过程相关的几个部分,并按中断发生后的时序对中断过程作一个概括性的描述:
在这里插入图片描述

  • 首先,我们可以看到在南桥芯片上集成了一个称为IOAPIC的部件,它共有24根中断引脚可以接收来自外部设备(如键盘、鼠标)的中断请求;当外设触发中断请求后,IOAPIC芯片会根据设备驱动初始化时设定的内容(一个内存地址Address和一个数据Data)向总线发送中断信息(将Data值写入Address地址代表的内存),如图中绿色线条所示,直观地理解,这个Address指明了接收本次中断的具体CPU,而Data代表中断向量号(粗略地讲,可以认为这是不同中断相互区别的一个整数值,0~255)。
  • 其次,对于PCI设备而言,有两种中断触发方式:一种是通过intx中断引脚触发(最终通过IOAPIC发送中断信号,如图中虚线所示);另一种是MSI/MSI-X方式(Message Signal Interrupt),PCI设备通过驱动初始化时设定的内容直接向总线发送中断,如图中蓝线所示,其发送原理类似IOAPIC,由于外部设备中断过程并非本文讨论的重点,有兴趣的同学可以查阅PCI规范中相关内容来获得更深入的理解
  • 此外,CPU之间可以通过写ICR寄存器发送IPI(Inter Processors Interrupt)中断来进行核间通信,如图中粉色线条所示。
  • 最后,每个CPU逻辑核都有一个称为LAPIC的子部件,它负责接收总线上的中断信息,当确认是发送给本地逻辑核时,便会引发本地CPU的中断过程:CPU会对保存当前正在执行任务的状态信息,之后会根据中断信息找到具体的中断处理逻辑函数;在完成中断处理函数后,CPU便会恢复先前保存的任务状态,继续处理原先的作务。

CPU如何处理中断?

1. 硬件自动完成动作

x86_64 CPU在中断发生时会执行一系列硬件动作,完成执行上下文的切换,这些都是硬件自动完成的,不受软件控制,如下图所示:
在这里插入图片描述
图中上半部分表示中断发生前寄存器状态(这里以用户态上下文状态举例):
● CS(代码段寄存器)和rip(指令指针寄存器)指向了当前正在执行的用户态指令的位置(绿色线条所示);
● SS(堆栈段寄存器)和rsp(栈指针寄存器)指向了当前用户态堆栈的栈项位置;
● rflags(标志寄存器)中的IF位为1,表示允许中断发生;
● TR(Task Register)任务寄存器指向当前任务的任务状态段TSS(Task State Segment),其中的rsp0域指向了内核态栈顶位置(内核特权级为0);
● IDTR(Interrupt Descriptor Table Register)指向了全局中断描述符表(Interrupt Descriptor Table),表中共有256个中断描述符(Interrupt Descriptor),每个描述符指向一个中断处理函数入口,中断描述符在表中的索引(下标)称为中断向量(Interrupt Vector)。
中断发生后(只能发生在指令边界,不能打断单条指令的执行),寄存器状态将发生变化,如上图下半部分所示
● 对于运行在用户态的程序,中断发生后需要切换到内核态执行中断处理函数,出于安全的考虑,堆栈也需要切换到内核态(注意,每个进程在内核态都有一个独立的栈空间,3.10内核中有16K大小,栈项指针保存在TSS);
● 切换到内核态栈后,CPU自动将用户态SS、rsp、rflags、CS、rip压入栈中(从上到下,栈顶在下,栈底在上);
● CPU根据中断向量,取出中断描述符表中对应的中断描述符,将CS:rip指向中断描述符中的函数入口地址;
● 对于类型为Interrupt Gate的中断描述符,rflags中的IF标置位将被清零,表示CPU此时开始不响应外部中断。

2、中断描述符表

arch/x86/kernel/irqinit.c:

void __init native_init_IRQ(void)
{
    int i;
    
    ...

    /*
     * 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]);
    }

    ...

}

FIRST_EXTERNAL_VECTOR为32,NR_VECTOR为256,开头的这段注释的意思是说这里会给从32到256的所有中断向量注册处理函数,从下面的代码看出处理函数在全局数组interrupt中。那么就有两个问题:为什么从32开始?为什么一开始就能把中断处理函数全部注册好,此时驱动程序都没初始化,具体的中断处理逻辑难道不是在驱动代码中实现的吗?第一个问题比较好回答,其实0~31的向量是intel预留给异常使用的,这是CPU用来处理内部问题的一种方式,如除零、缺页等等。第二个问题目前确实比较难回答,我们就带着这个问题看看interrupt数组的定义吧:

arch/x86/kernel/entry_64.S:

/*
 * Build the entry stubs and pointer table with some assembler magic.
 * We pack 7 stubs into a single 32-byte chunk, which will fit in a
 * single cache line on all modern x86 implementations.
 */
    .section .init.rodata,"a"
ENTRY(interrupt)
    .section .entry.text
    .p2align 5
    .p2align CONFIG_X86_L1_CACHE_SHIFT
ENTRY(irq_entries_start)
vector=FIRST_EXTERNAL_VECTOR
.rept (NR_VECTORS-FIRST_EXTERNAL_VECTOR+6)/7
    .balign 32
    .rept	7
    .if vector < NR_VECTORS
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
.endr
END(irq_entries_start)

.previous
END(interrupt)
.previous

这段汇编代码确实比较晦涩,它把32到256的中断按7个一组划成一个个大组,每个大组的内存占用空间大小在32个字节内,这样这些组块可以被CPU缓存到内部缓存中,以加速对这些内存的访问,显然这是一个性能优化手段。每个大组内包含了7个中断的桩(stub)函数和每个中断的处理函数入口地址,其内存结构如下图所示:
在这里插入图片描述

每个中断处理函数的入口地址以XXX_表示,桩函数包含两条指令,一条push指令和一条jmp指令。前6个中断桩函数的jmp指令都会跳转到最后一个桩函数的jmp指令位置,而该指令最终跳转到common_interrupt位置处继续执行。在每个桩函数的最后(组内的最后一个桩函数是在jmp指令前)放置了当前处理函数的入口地址,最终这些地址会组成全局interrupt数组。

3、公共入口函数common_interrupt
从上节的介绍中可以看出,中断发生后,CPU会执行中断描述符表所指向的各个中断的桩函数(如上图中XXX_32表示32号向量所对应的中断处理函数入口),而所有桩函数在将中断向量压入栈后(会做符号化处理),最终会跳转到common_interrupt函数,这个函数就成了所有中断的公共入口:

arch/x86/kernel/entry_64.S:

/*
 * Interrupt entry/exit.
 *
 * Interrupt entry points save only callee clobbered registers in fast path.
 *
 * Entry runs with interrupts off.
 */

/* 0(%rsp): ~(interrupt number) */
.macro interrupt func
    /* reserve pt_regs for scratch regs and rbp */
    subq $ORIG_RAX-RBP, %rsp
    SAVE_ARGS_IRQ
    call \func
.endm

/*
 * Interrupt entry/exit should be protected against kprobes
 */
.pushsection .kprobes.text, "ax"
/*
 * The interrupt stubs push (~vector+0x80) onto the stack and
 * then jump to common_interrupt.
 */
.p2align CONFIG_X86_L1_CACHE_SHIFT
common_interrupt:
    addq $-0x80,(%rsp)		/* Adjust vector to [-256,-1] range */
    interrupt do_IRQ
    /* 0(%rsp): old_rsp-ARGOFFSET */
ret_from_intr:
    ...

这里的interrupt代表一个宏,而不是之前讨论的interrupt全局数组。common_interrupt的工作过程就是将栈顶的向量号转化成负数(-256,-1),然后通过SAVE_ARGS_IRQ宏保存必要的寄要器,最后调用C语言函数do_IRQ来处理中断。SAVE_ARGS_IRQ宏定义如下:

arch/x86/kernel/entry_64.S:

/* save partial stack frame */
.macro SAVE_ARGS_IRQ
    cld
    /* start from rbp in pt_regs and jump over */
    movq_cfi rdi, (RDI-RBP)
    movq_cfi rsi, (RSI-RBP)
    movq_cfi rdx, (RDX-RBP)
    movq_cfi rcx, (RCX-RBP)
    movq_cfi rax, (RAX-RBP)
    movq_cfi  r8,  (R8-RBP)
    movq_cfi  r9,  (R9-RBP)
    movq_cfi r10, (R10-RBP)
    movq_cfi r11, (R11-RBP)

    /* Save rbp so that we can unwind from get_irq_regs() */
    movq_cfi rbp, 0

    /* Save previous stack value */
    movq %rsp, %rsi

    leaq -RBP(%rsp),%rdi	/* arg1 for handler */
    testl $3, CS-RBP(%rsi)
    je 1f
    SWAPGS
    /*
     * irq_count is used to check if a CPU is already on an interrupt stack
     * or not. While this is essentially redundant with preempt_count it is
     * a little cheaper to use a separate counter in the PDA (short of
     * moving irq_enter into assembly, which would be too much work)
     */
1:	incl PER_CPU_VAR(irq_count)
    cmovzq PER_CPU_VAR(irq_stack_ptr),%rsp

    /* Store previous stack value */
    pushq %rsi
    TRACE_IRQS_OFF
.endm
arch/x86/include/asm/calling.h:

/*
 * 64-bit system call stack frame layout defines and helpers,
 * for assembly code:
 */

#define R15		  0
#define R14		  8
#define R13		 16
#define R12		 24
#define RBP		 32
#define RBX		 40

/* arguments: interrupts/non tracing syscalls only save up to here: */
#define R11		 48
#define R10		 56
#define R9		 64
#define R8		 72
#define RAX		 80
#define RCX		 88
#define RDX		 96
#define RSI		104
#define RDI		112
#define ORIG_RAX	120       /* + error_code */
/* end of arguments */

/* cpu exception frame or undefined in case of fast syscall: */
#define RIP		128
#define CS		136
#define EFLAGS		144
#define RSP		152
#define SS		160

为什么只保存rdir11寄存器?这就涉及gcc编译方面的知识了,对于一个C函数来说,调用者如果在rdir11寄存器中保存了有用的信息,那调用者就需要在执行该C函数的调用前保存这些寄存器,因为C函数执行的过程中有可能会修改这些寄存器且不对这些寄存器做保存;而对于rbx,rbp,r12-r15这些寄存器,调用者如果在其中保存了有用的信息,在C函数调用返回后,这些寄存器的值不会发生改变,因为如果C函数内部会使用这些寄存器,它会保存旧的值并在函数返回前恢复这些寄存器旧有的值。

终于来到了C语言函数do_IRQ:

arch/x86/kernel/irq.c:

/*
 * do_IRQ handles all normal device IRQ's (the special
 * SMP cross-CPU interrupts have their own specific
 * handlers).
 */
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();
    exit_idle();

    irq = __this_cpu_read(vector_irq[vector]);

    if (!handle_irq(irq, regs)) {
        ...
    }

    irq_exit();

    set_irq_regs(old_regs);
    return 1;
}

上述函数的参数regs对应寄存器rdi(可以回顾下x86_64寄存器传参规则),它是在SAVE_ARGS_IRQ宏中赋值的,指向了栈顶保存的r15寄存器。我理解此时栈顶有可能并没有保存r15寄存器的值,就看do_IRQ函数汇编后需不需要使用r15,但是其实do_IRQ只需要通过regs找到偏移为orig_ax的值(保存了向量号)就行,并不会去访问regs->r15,所以并不影响程序的正确性。
● do_IRQ函数先保存了旧的栈帧结构指针,并在函数返回前恢复了旧的栈帧结构指针(目前还不是太理解在x86中的作用);
● 通过regs中的orig_ax取出中断向量号,这里会将负数再次转成正数;
● 执行irq_enter表明正式进入中断上下文,如将当前进程的preempt_count计数增加;exit_idle表明CPU将退出空闲状态,这里均不作展开;
● 通过percpu变量将中断向量转换成irq号,并根据irq号处理中断;
● 执行riq_exit表明退出中断上下文,并恢复旧的栈帖结构指针;

这里最让人困惑的是irq号,它和中断向量是什么关系?转化关系为什么又是percpu类型的?为了回答这些问题,我们的思路暂时切出中断发生后的过程,来了解一些中断管理类的概念和初始化动作。

smp系统出现之前,系统中不同的外部中断完全可以用中断向量来区分,但在smp系统中,CPU核数增加导致中断处理也变得复杂,每个CPU都可以处理不同的中断,如果还用全局性的中断向量来区分中断,所能表示的中断数目太少。那是否可以给每个CPU都设立独立的中断描述符表?不行,这样会大大增加内核实现的复杂性,它采用了一种变通的方式:所有外部中断通过irq号来区分,不同的中断(即不同的irq)可以使用相同的中断向量,只要这些中断被分配到不同的核上,例如在我的系统中查看中断信息得到如下结果:
在这里插入图片描述
第一列中的数字即代表中断irq号,如0号irq代表ISA总线上的全局PIT时钟中断;通常来说0-15号irq对应传统ISA中断;16—39号开始分配给IOAPIC(i440fx中只有一个IOAPIC,占用24个irq);再往后的irq分配给MSI/MSI-X(i440fx中从16+24=40号开始)。上图中我们看不到系统给每个中断分配的中断向量,假设系统初始化时给irq 0分配了0号核的32号向量,给irq 1分配了1号核的32号向量,那么0号核的percpu数组vector_irq的32号元素就指向irq 0,而1号核的percpu数组vecotr_irq的32号元素指向irq 1,如此一来,虽然0号核和1号核收到的中断向量都是32,但是do_IRQ可以通过percpu的vector_irq找到不同的irq,并通handle_irq执行真正的中断处理逻辑。这就是percpu的vectro_irq的神奇作用,也回答了前篇所提出的为什么在驱动初始化前就可以给所有中断向量注册处理函数:中断描述符表中所指的函数只是一个伪入口(即桩函数),而非实际的处理函数;实际的处理函数是在驱动初始化时,在为设备申请了irq号之后,通过request_irq(irq, function…)注册给不同的irq的。

我们再切回中断的处理过程,在理解irq、中断向量、CPU核之间的关系后,可以看到handle_irq即是对每个中断进行实质性处理的核心函数,最终会调用request_irq函数注册的中断处理逻辑。下面我们就来分析一下handle_irq的实现逻辑。

4、中断处理逻辑handle_irq

该函数整体逻辑比较简单:先将irq转换成struct irq_desc结构,然后调用的generic_handle_irq_desc函数。

arch/x86/kernel/irq_64.c:

bool handle_irq(unsigned irq, struct pt_regs *regs)
{
    struct irq_desc *desc;

    ...

    desc = irq_to_desc(irq);
    if (unlikely(!desc))
        return false;

    generic_handle_irq_desc(irq, desc);
    return true;
}

irq_desc结构体将包含与中断相关的所有关键信息,内核中将所有中断的irq_desc结构组织成一棵树的结构:

include/linux/irqdesc.h:

/**
 * struct irq_desc - interrupt descriptor
 * @irq_data:		per irq and chip data passed down to chip functions
 * @kstat_irqs:		irq stats per cpu
 * @handle_irq:		highlevel irq-events handler
 * @preflow_handler:	handler called before the flow handler (currently used by sparc)
 * @action:		the irq action chain
 * @status:		status information
 * @core_internal_state__do_not_mess_with_it: core internal status information
 * @depth:		disable-depth, for nested irq_disable() calls
 * @wake_depth:		enable depth, for multiple irq_set_irq_wake() callers
 * @irq_count:		stats field to detect stalled irqs
 * @last_unhandled:	aging timer for unhandled count
 * @irqs_unhandled:	stats field for spurious unhandled interrupts
 * @threads_handled:	stats field for deferred spurious detection of threaded handlers
 * @threads_handled_last: comparator field for deferred spurious detection of theraded handlers
 * @lock:		locking for SMP
 * @affinity_hint:	hint to user space for preferred irq affinity
 * @affinity_notify:	context for notification of affinity changes
 * @pending_mask:	pending rebalanced interrupts
 * @threads_oneshot:	bitfield to handle shared oneshot threads
 * @threads_active:	number of irqaction threads currently running
 * @wait_for_threads:	wait queue for sync_irq to wait for threaded handlers
 * @dir:		/proc/irq/ procfs entry
 * @name:		flow handler name for /proc/interrupts output
 */
struct irq_desc {
    struct irq_data		irq_data;
    unsigned int __percpu	*kstat_irqs;
    irq_flow_handler_t	handle_irq;

    struct irqaction	*action;	/* IRQ action list */
    unsigned int		status_use_accessors;
    unsigned int		core_internal_state__do_not_mess_with_it;
    unsigned int		depth;		/* nested irq disables */
    unsigned int		wake_depth;	/* nested wake enables */
    unsigned int		irq_count;	/* For detecting broken IRQs */
    unsigned long		last_unhandled;	/* Aging timer for unhandled count */
    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_PROC_FS
    struct proc_dir_entry	*dir;
#endif
    int			parent_irq;
    struct module		*owner;
    const char		*name;
} ____cacheline_internodealigned_in_smp;

...

/*
 * Architectures call this to let the generic IRQ layer
 * handle an interrupt. If the descriptor is attached to an
 * irqchip-style controller then we call the ->handle_irq() handler,
 * and it calls __do_IRQ() if it's attached to an irqtype-style controller.
 */
static inline void generic_handle_irq_desc(unsigned int irq, struct irq_desc *desc)
{
    desc->handle_irq(irq, desc);
}

对于上述代码片断,我不再多作解释,大家可以对照代码注释仔细理解。这里的generic_handle_irq_desc函数通过内联的方式会调用每个中断对应的handle_irq函数。可能很多同学会把这里的handle_irq理解成就是用户(驱动程序)通过request_irq注册的中断处理函数。其实不然,这里的handle_irq仍然是一段通用的中断处理逻辑,用来实现对不同中断模式的处理和中断流控功能。这些通用的处理函数主要有三类:handle_level_irq、handle_edge_irq、handle_fasteoi_irq。

kernel/irq/chip.c:

/**
 *	handle_level_irq - Level type irq handler
 *	@irq:	the interrupt number
 *	@desc:	the interrupt description structure for this irq
 *
 *	Level type interrupts are active as long as the hardware line has
 *	the active level. This may require to mask the interrupt and unmask
 *	it after the associated handler has acknowledged the device, so the
 *	interrupt line is back to inactive.
 */
void
handle_level_irq(unsigned int irq, struct irq_desc *desc)
{
    ...
}

/**
 *	handle_fasteoi_irq - irq handler for transparent controllers
 *	@irq:	the interrupt number
 *	@desc:	the interrupt description structure for this irq
 *
 *	Only a single callback will be issued to the chip: an ->eoi()
 *	call when the interrupt has been serviced. This enables support
 *	for modern forms of interrupt handlers, which handle the flow
 *	details in hardware, transparently.
 */
void
handle_fasteoi_irq(unsigned int irq, struct irq_desc *desc)
{
    ...
}

/**
 *	handle_edge_irq - edge type IRQ handler
 *	@irq:	the interrupt number
 *	@desc:	the interrupt description structure for this irq
 *
 *	Interrupt occures on the falling and/or rising edge of a hardware
 *	signal. The occurrence is latched into the irq controller hardware
 *	and must be acked in order to be reenabled. After the ack another
 *	interrupt can happen on the same source even before the first one
 *	is handled by the associated event handler. If this happens it
 *	might be necessary to disable (mask) the interrupt depending on the
 *	controller hardware. This requires to reenable the interrupt inside
 *	of the loop which handles the interrupts which have arrived while
 *	the handler was running. If all pending interrupts are handled, the
 *	loop is left.
 */
void
handle_edge_irq(unsigned int irq, struct irq_desc *desc)
{
    ...
}

这三类函数主要是针对不同物理电气特性的中断和中断控制器(如IO-APIC、支持MSI的PCI设备等)做不同的处理。有兴趣的同学可以结合intel IO-APIC说明和PCI规范来仔细理解里面的实现过程。
  最后,这几类函数都会调用handle_irq_event,它会调用irq_desc中action的handler,这个函数指针,才是用户通过request_irq注册的中断处理函数。到这一步,才真正调用到实际的中断处理逻辑。

kernel/irq/handler.c:

irqreturn_t
handle_irq_event_percpu(struct irq_desc *desc, struct irqaction *action)
{
    irqreturn_t retval = IRQ_NONE;
    unsigned int flags = 0, irq = desc->irq_data.irq;

    do {
        irqreturn_t res;

        res = action->handler(irq, action->dev_id);

        ...

        switch (res) {
        case IRQ_WAKE_THREAD:
            /*
             * Catch drivers which return WAKE_THREAD but
             * did not set up a thread function
             */
            if (unlikely(!action->thread_fn)) {
                warn_no_thread(irq, action);
                break;
            }

            irq_wake_thread(desc, action);

            /* Fall through to add to randomness */
        case IRQ_HANDLED:
            flags |= action->flags;
            break;

        default:
            break;
        }

        retval |= res;
        action = action->next;
    } while (action);

    add_interrupt_randomness(irq, flags);

    if (!noirqdebug)
        note_interrupt(irq, desc, retval);
    return retval;
}

irqreturn_t handle_irq_event(struct irq_desc *desc)
{
    struct irqaction *action = desc->action;
    irqreturn_t ret;

    desc->istate &= ~IRQS_PENDING;
    irqd_set(&desc->irq_data, IRQD_IRQ_INPROGRESS);
    raw_spin_unlock(&desc->lock);

    ret = handle_irq_event_percpu(desc, action);

    raw_spin_lock(&desc->lock);
    irqd_clear(&desc->irq_data, IRQD_IRQ_INPROGRESS);
    return ret;
}

5、中断返回ret_from_intr

当irq_desc的action处理完毕之后,中断处理过程将逐步返回到ret_from_intr:

arch/x86/kernel/entry_64.S:

ret_from_intr:
    DISABLE_INTERRUPTS(CLBR_NONE)
    TRACE_IRQS_OFF
    decl PER_CPU_VAR(irq_count)

    /* Restore saved previous stack */
    popq %rsi
    leaq ARGOFFSET-RBP(%rsi), %rsp

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
    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

上述过程首先判断中断发生时是在用户态还是在内核态,

● 如果是在内核态,就跳转到retint_kernel执行,这里会根据内核是否打开抢占进行不同的处理:如果内核不可抢占,那就恢复寄存器后返回到被中断的上下文继续执行;如果是可抢占的,那就可以进行调度。
● 如果是在用户态,就进行调度及信号相关的判断和处理;处理完成并恢复寄存器后,便通过iretq指令返回被中断的上下文继续执行。

至此,CPU上中断处理的整个系统过程完美结束,这真是一个漫长的旅途:->
  
本文转-》自吴斌个人主页-》【计算子系统】CPU中断处理

28335GPIO及外部中断配置介绍 弄了两周终于把28335的启动流程、寄存器及中断向量表的映射方法、内存的划分等有了一个全面的了解,今天看到久违的LED灯的闪烁,顿扫阴霾。特在此总结下28335GPIO及外部中断配置介绍。其实对于一个微控制器,只要能够独立实现这两个功能,也算是入门了。 一、GPIO口介绍 外界二进制信息(数字量)要被CPU处理,要给存储器存放,就需要外界信息源与两者之间的交换接口,这样的交换接口若用来 阅读详情

相关推荐

Simulink嵌入式自动代码生成DSP 28335/28035/28x系列 (1)——官方例程(1)讲解 {ADC-PWM同步中断}

现如今可以 使用MATLAB中的simulink模块 对DSP TI 28x系列芯片做详细的配置, 即使 使用者在 无代码编写经验仍能直接导出工程 和代码 本文可能适用于 使用Simulink 简化管理代码应用层,配置底层的 DSP工程师 入门电源行业的 应届大学生 无 C 语言基础的 相关领域的工程师

欢迎莅临o(* ̄▽ ̄*)ブ 1万+

CPU“中断”的本质是什么?

本文我们从中断的概念开始讲起,一路上分析了中断的作用、中断的产生、中断的处理。其中内容最多的是硬件中断的处理,方法很多很繁杂。从6.1节CPU运行模型中,我们可以看到中断对于推动整个系统运行的重要性。所以说中断机制是计算机系统的神经和脉搏,一点都不为过。想要学会Linux内核,弄明白中断机制是其中必不可少的一环。最后我们再来看一下中断机制的图:​参考文献:《Interrupt in Linux (硬件篇)》深入理解Linux中断机制_城中之城的博客-优快云博客。

资料qun832218493 7879

计算机硬件架构与操作系统核心机制深度解析

计算机硬件架构是理解现代计算系统的基础,其核心组件包括CPU、内存控制器和I/O控制器等,通过系统总线协同工作。CPU作为计算核心,采用多级流水线和寄存器组设计,显著提升指令执行效率。内存子系统通过分层缓存和虚拟内存管理,优化数据访问性能。操作系统在此基础上构建进程调度、内存管理等核心机制,如Linux的CFS调度器和伙伴系统。这些技术的结合不仅支撑了从嵌入式设备到数据中心的各种应用场景,更为性能调优和安全增强提供了基础框架。通过分析CPU性能热点和内存泄漏检测等实践,开发者可以深入掌握硬件与软件的协同优化

weixin_33585889的博客 258

CPU中断

CPU共256个中断类型型号: 0~31:对应于异常和非屏蔽中断,不使用中断控制器; 32~47:分配给可屏蔽中断,由I/O设备引起的中断,这其中就包含可由两个8259A级联响应的15个; 48~255:用来标识软中断; CPU中断的机制概论 cpu的数学模型是图灵机,图灵机是一个线性运行指令的机器,它的执行序列是完全确定的,中断机制是使得cpu具有异步处理...

jadeshu的博客 1万+

1、计算机编程基础与实践:从硬件到软件的全面指南

本文全面介绍了计算机编程的基础知识,从硬件到软件的各个方面,包括计算机子系统、数据存储格式、计算机算术、布尔代数、逻辑门、组合与时序逻辑电路、内存、中央处理器(CPU)、汇编语言编程、控制流构造、子函数、位运算、数据结构、面向对象编程、分数数表示、输入/输出(I/O)、中断和异常等。同时,还提供了编程环境搭建、调试工具使用、实践案例分析以及常见问题解答等内容,帮助读者深入理解计算机编程的底层原理并提升实践能力。

dream的博客 73

Ubuntu下Charm-Crypto安装

经过一下午的努力终于是把Charm-Crypto给安上了,经过查询资料,大致有三种方案(两坑一可行)。 适用于我的电脑的是Ubuntu 18.04版本。 第一种方案是根据连接https://pypi.org/project/charm-crypto/的教程进行安装,但是该方案的优势在于下载快速,缺陷在于Ubuntu系统仅仅更新到13+版本。 第二种就是网络博客上的介绍,内容与第一种较类似,但是有16+,18+成功的例子,缺点就是bug太多,我大部分时间都耗在了这上边,而且还没有成功。 第三中是github上

Mr.Yi的博客 2284

计算机组成原理

计算机组成原理——电子科技大学计算机科学与工程学院复试必看!学无止境!

0 1036

stackelberg博弈_博弈论、多智能体、强化学习(随便记点东西)

多智能体应用场景:已实现:游戏AI。正在努力:UAV,物资运输,快递分发,交通系统。未来:社会学,预测群体行为,城市区域合作,国际关系。博弈论基本定义:玩家集合: 策略集合: 收益函数: 囚徒困境:两智能体矩阵博弈坦白抵赖坦白1,13,0抵赖0,32,2占优策略与最佳应对:上表中两者均具有占优策略,即无论如何都应该选坦白。那么构造下表。坦白抵赖坦白1,13,0抵赖0,12,2对于玩家一来说,坦白...

weixin_39611722的博客 2995

计算机组成原理(复试)

计算机系统包括硬件和软件。从计算机系统的层次结构来看,它通常可以分为五个以上的层次,在每一层上都能进行程序设计。由下至上可排序为:第一级微程序机器级,微指令硬件直接执行;第二级传统机器级,用微程序解释机器指令;第三级操作系统级,一般用及其语言程序解释作业控制语句;第四级汇编语言级,这一级由汇编语言支持和执行;第五级高级语言级,采用高级语言,由各种高级语言编译程序支持和执行。还可以有第六级,应用语言机器级,采用各种面向问题的应用语言。冯诺依曼结构计算机是一种典型的计算机组织结构,将计算机硬件分为运算器,存储器

qq_40625979的博客 4100

FFT去除频率分量的完整流程与数学分析

重叠与窗口:确保块间无缝过渡,避免幅度跳变;实时缓冲:处理速度需快于数据输入速度,避免断流;DAC的平滑角色:通过数模转换和滤波,将离散样点还原为连续波形。通过这种分段处理+重叠拼接的方法,即使FFT本身处理的是离散数据块,最终DAC输出仍能保持信号的时间连续性。以下是完整的数学分析过程和代码逻辑,演示如何通过FFT去除特定频率成分,并通过去窗技术恢复信号。信号加窗。

林愿留的博客 1607

NEORV32实战指南:三步构建你的开源RISC-V嵌入式系统

在嵌入式开发领域,你是否曾面临这样的困境:需要一个轻量级、可定制的处理器核心,但又不想被商业IP的许可费用和限制所束缚?或者想要深入学习RISC-V架构,却找不到一个完整、易上手的开源实现?这正是NEORV32项目要解决的核心问题。 NEORV32是一个基于平台无关VHDL语言开发的小型、可定制且高度可扩展的32位RISC-V软核CPU和微控制器类SoC。它不仅仅是一个处理器核心,更是一个完整的

gitblog_00153的博客 245

DeepSeek与豆包无缝对接:5分钟搞定智能周报自动化(附完整代码)

本文详细介绍了如何利用DeepSeek与豆包API,快速搭建一个智能周报自动化系统。通过整合豆包平台的工作流数据,并调用DeepSeek大模型进行深度分析与内容生成,该系统能在5分钟内自动抓取、分析多源信息,并生成结构清晰、富有洞察力的团队周报,显著提升工作效率。

weixin_29029995的博客 490

Python 模拟 TOY 计算机:200行代码实现冯·诺伊曼体系 5 大核心部件

本文通过Python代码实现了一个完整的冯·诺伊曼体系计算机模拟器,仅用200行代码便构建了运算器、控制器、存储器等五大核心组件。该项目不仅深入解析了冯·诺伊曼体系结构的工作原理,还提供了可运行的教学工具,帮助读者从理论到实践掌握计算机底层原理。

weixin_33860528的博客 432

别再死磕GAN了!用PyTorch从零实现DDPM图像生成(附CIFAR-10完整代码)

本文详细介绍了如何使用PyTorch从零实现DDPM(Denoising Diffusion Probabilistic Models)图像生成模型,并在CIFAR-10数据集上进行训练和测试。通过对比GAN,DDPM展现出更稳定的训练过程和更高质量的生成效果。文章包含完整的代码实现、训练技巧和优化建议,帮助开发者快速掌握这一前沿的生成模型技术。

weixin_33720078的博客 630

大模型——Dify:知识库与外部知识库

摘要 Dify的知识库功能为开发者提供了动态知识管理解决方案,超越了大模型静态预训练数据的限制。知识库支持多种文档格式(如txt、pdf、docx等),通过文档分段、索引设置和检索优化机制提升LLM的响应准确性。系统提供高质量和经济两种索引模式,前者采用向量化检索技术,后者使用关键词匹配以降低成本。知识库支持元数据管理,包括自动生成的内置元数据和用户自定义元数据,便于内容分类和检索。对于高级需求,Dify还支持连接外部知识库,通过API实现跨平台数据共享和检索优化,满足企业级知识管理需求。

726

从零开始:微型计算机系统核心组件与工作流程深度解析

本文深度解析微型计算机系统的核心组件与工作流程,重点探讨CPU如何指挥全局、存储器系统的分级设计、总线架构的演进以及I/O设备的转换过程。通过实际案例和性能测试,揭示硬件原理对系统优化的重要性,帮助读者全面理解微机系统的工作原理与性能调优方法。

weixin_30268921的博客 404

手把手教你完成基于matlab的路面裂缝检测系统设计

裂缝是路面病害最早的形态之一,只有在裂缝出现的早期进行维护,才能有效地提高公路的使用寿命。因此,路面裂缝的检测识别系统对于提升公路质量有着重大的意义。首先,对路面裂缝图像进行相应的预处理,目的是减小环境因素的影响,消除无用的噪点。然后提取目标图像特征,判断路面裂缝类型,标记出路面裂缝的位置。最后通过设计的 GUI 界面来集成各个关键步骤,显示所得到的试验结果。仿真结果表明,设计的路面裂缝检测识别系统是高效可行的。

在职AI算法工程师,擅长计算机视觉,YOLO目标检测、分割等,擅长web、pyqt界面可视化,好内容持续更新中,来这里跟大家一起学习,共同进步 1216

操作系统核心机制与硬件协同优化实践

操作系统作为计算机系统的核心软件,通过硬件抽象、资源调度和服务提供三大核心机制管理底层硬件资源。从技术原理看,现代操作系统依赖CPU多级调度体系、MMU内存管理单元和DMA设备访问机制实现高效资源分配,其中分页机制、TLB缓存和中断处理等硬件特性直接影响系统性能。在工程实践中,操作系统的价值体现在云计算调度优化、高频交易延迟降低等场景,例如通过Linux CFS调度器调优可解决金融系统交易卡顿问题,而大页内存技术能将订单处理延迟降低62.5%。随着NVMe存储、持久化内存等新兴硬件发展,操作系统需要持续适配

weixin_30846347的博客 239

visio安装包

Microsoft Office Visio 是一款专业的制图软件。利用经过更新的形状、协作工具和数据链接图表,创建专业级图表以简化复杂的信息。

jdk-8u144-linux-x64.tar.gz.

该资源为搭建Linux系统Java环境用,资源的后缀为tar.gz格式的压缩包

操作系统/OS笔记01】课程概要、什么是操作系统

【操作系统/OS笔记01】课程概要、什么是操作系统 本次笔记内容: 1.1 课程概述 1.2 什么是操作系统 1.3 为什么学习操作系统 文章目录【操作系统/OS笔记01】课程概要、什么是操作系统课程概要纲要实验工具预备知识操作系统什么是操作系统操作系统包含什么OS Kernel的特征:学习操作系统的意义 课程概要 资源站点: Bilibili 【清华大学】操作系统 陈渝 全113讲 av33394011 教师信息: 陈渝 清华大学 纲要 本课程学习什么? 基本概念及原理 操作系统介绍 中断及系统调用

夜空中最亮的星 259

Camera2Demo

在Camera2 的基础上进行封装,使得Camera2一堆初始化逻辑与Activity分离,降低耦合度,完成预览拍照并保存的操作

linux内核相关

按下开机键,CPU 将 PC 寄存器的值强制初始化为 0xffff0,这个位置是 BIOS 程序的入口地址(一跳)执行了一些硬件检测工作后,最后一步将启动区内容加载到内存 0x7c00,并跳转到这里(三跳)该入口地址处是一个跳转指令,跳转到 0xfe05b 位置,开始执行(二跳)启动区代码主要是加载操作系统内核,并跳转到加载处(四跳)

73

IEM模型,地表粗糙度模拟

利用微波反演土壤水分时候,利用IEM模型可以实现对随机地表的模拟

AI系统-23AI芯片CPU子系统介绍

本文介绍了AI SoC中CPU子系统的架构设计,重点以ARM Cortex-A76AE为例进行分析。CPU子系统作为SoC的核心,包含处理器核心、中断控制器、缓存系统、内存控制器等关键组件。文章详细讲解了ARM Cortex-A76AE的微架构特点,包括其4发射超标量乱序执行、6个整数执行单元等设计,并强调了其在汽车电子领域的分裂锁定(Split-Lock)功能。同时介绍了DynamIQ共享单元(DSU)的作用,包括管理多核集群、共享L3缓存以及支持异构计算等特性。这些设计共同构成了现代高性能、低功耗且具备

483

interrupt子系统详谈

a. 中断的处理可以分为上半部,下半部b. 中断上半部,用来处理紧急的事,它是在关中断的状态下执行的c. 中断下半部,用来处理耗时的、不那么紧急的事,它是在开中断的状态下执行的d. 中断下半部执行时,有可能会被多次打断,有可能会再次发生同一个中断e. 中断上半部执行完后,触发中断下半部的处理f. 中断上半部、下半部的执行过程中,不能休眠:中断休眠的话,以后谁来调度进程啊?

m0_73726682的博客 2150

59IO子系统层次结构

IO子系统的层次结构 IO软件涉及的面非常广,往下与硬件有密切关系,晚上又与用户直接交互,它与进程管理、存储器管理、文件管理等都存在一定的联系,即他们都可能需要IO软件来实现IO操作。 为了使复杂的IO软件具有清晰的结构,良好的可移植性和适应性,在IO软件中普遍采用了层次式结构,将系统输入\输出功能组织成一系列的层次,每一层都利用其下层提供的服务,完成输入\输出功能中的某些子功能。 一个比较合理的层次划分如下图所示。整个IO系统可以看成具有四个层次的系统结构,各层次及其功能如下: (1) 用户层I/O软件

weixin_41883890的博客 3370

Linux中断(interrupt)子系统

中断系统基本原理这个中断系列文章主要针对移动设备中的Linux进行讨论,文中的例子基本都是基于ARM这一体系架构,其他架构的原理其实也差不多,区别只是其中的硬件抽象层。内核版本基于3.3。虽然内核的版本不断地提升,不过自从上一次变更到当前的通用中断子系统后,大的框架性的东西并没有太大的改变。1. 设备、中断控制器和CPU一个完整的设备中,与中断相关的硬件可以划分为3类,它们分别是:设备、中断控制器

hong 5422

Linux内核中那些子系统

好的,这是一个非常棒的问题,也是Linux内核开发者或高级系统工程师面试时的经典问题。• 对于核心子系统(进程、内存、VFS、驱动模型):不仅要理解其基本概念和工作原理,还要能阅读相关代码,并使用工具(如ftrace, perf)进行分析和调试。◦ 功能:提供一种统一的模型来表示设备、驱动和总线,以及它们之间的关系。◦ 功能:限制、记录和隔离进程组的资源使用(CPU、内存、磁盘I/O、网络等)。◦ 功能:负责进程和线程的创建、销毁、调度、执行以及它们之间的通信(IPC)。

yiyu20180729的博客 704
上一篇: 达内教育Django笔记 ORM_01
下一篇: Django 中使用 脚本将 json 数据导入ORM数据表
刀不锋马太瘦
博客等级 码龄10年 2粉丝 4原创
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值