Linux教程

linux arm32中断子系统学习总结(三)--- 软件子系统

本文主要是介绍linux arm32中断子系统学习总结(三)--- 软件子系统,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

 

三、arm32中断处理软件子系统

 

  中断软件子系统负责cpu检测到中断以后的处理,总体来看,可以分为三个部分:中断向量函数、中断控制器驱动部分以及用户接口部分;

  中断向量函数放在中断向量表里面,每一种中断对应一个中断向量函数,软件在初始化时需要创建一个中断向量表,放在内存中并通过协处理器cp15告诉cpu中断向量表的位置;cpu检测到中断后,会自动将处理器模式切换到对应的中断模式,然后将pc指向对应的中断向量函数。中断向量函数中会将处理器模式切换到svc模式,将被中断前处理器的硬件上下文保存起来,然后跳转到中断控制器驱动注册的处理函数,至此,中断传递到中断控制器驱动;

  中断控制器驱动负责管理所有类型的中断,包括外设中断、SMP核间中断以及cpu热插拔等中断;向下提供中断控制器操作接口,向上提供用户处理中断的各种接口;

最后是上层用户接口,上层通过中断控制器提供的接口注册回调函数处理中断事件;

3.1 中断向量表

  由于arm处理器处理中断的方式:cpu执行完当前指令,检测到中断后,会自动将工作模式切换到对应的中断模式,然后将pc指向中断向量表中对应中断的中断向量。

  因此,软件需要创建一个中断向量表,并通过设置协处理器cp15告诉cpu中断向量表的位置。

  中断向量表定义在entry-armv.S文件中:

.L__vectors_start:
    W(b)    vector_rst
    W(b)    vector_und
    W(ldr)    pc, .L__vectors_start + 0x1000
    W(b)    vector_pabt
    W(b)    vector_dabt
    W(b)    vector_addrexcptn
    W(b)    vector_irq
    W(b)    vector_fiq

  __vectors_start的地址定义在vmlinux.lds.S文件中,这个是默认地址,内核初始化过程中会重新设置

__vectors_start = .;
    .vectors 0xffff0000 : AT(__vectors_start) {
        *(.vectors)
    }
    . = __vectors_start + SIZEOF(.vectors);
    __vectors_end = .;

    __stubs_start = .;
    .stubs ADDR(.vectors) + 0x1000 : AT(__stubs_start) {
        *(.stubs)
    }
    . = __stubs_start + SIZEOF(.stubs);
    __stubs_end = .;

  devicemaps_init函数中会申请内存,并将中断向量表拷贝到内存,然后,根据协处理器cp15的寄存器C1的bit[13]是否被置1,将中断向量表的物理地址映射到0xffff0000或者0x0,详细函数调用流程如下图所示:

3.2 中断向量函数的实现

  中断向量表中的跳转函数都通过.macro   vector_stub, name, mode, correction=0宏进行定义,该宏具体如下:

/*
 * Vector stubs.
 *
 * This code is copied to 0xffff1000 so we can use branches in the
 * vectors, rather than ldr's.  Note that this code must not exceed
 * a page size.
 *
 * Common stub entry macro:
 *   Enter in IRQ mode, spsr = SVC/USR CPSR, lr = SVC/USR PC
 *
 * SP points to a minimal amount of processor-private memory, the address
 * of which is copied into r0 for the mode specific abort handler.
 */
    .macro    vector_stub, name, mode, correction=0
    .align    5

vector_\name:
    .if \correction
    sub    lr, lr, #\correction
    .endif

    @
    @ Save r0, lr_<exception> (parent PC) and spsr_<exception>
    @ (parent CPSR)
    @
    stmia    sp, {r0, lr}        @ save r0, lr
    mrs    lr, spsr
    str    lr, [sp, #8]        @ save spsr

    @
    @ Prepare for SVC32 mode.  IRQs remain disabled.
    @
    mrs    r0, cpsr
    eor    r0, r0, #(\mode ^ SVC_MODE | PSR_ISETSTATE)
    msr    spsr_cxsf, r0

    @
    @ the branch table must immediately follow this code
    @
    and    lr, lr, #0x0f
 THUMB(    adr    r0, 1f            )
 THUMB(    ldr    lr, [r0, lr, lsl #2]    )
    mov    r0, sp
 ARM(    ldr    lr, [pc, lr, lsl #2]    )
    movs    pc, lr            @ branch to handler in SVC mode
ENDPROC(vector_\name)

  以IRQ中断向量vector_irq为例,通过vector_stub irq, IRQ_MODE, 4宏定义,如下:

/*
 * Interrupt dispatcher
 */
    vector_stub    irq, IRQ_MODE, 4

    .long    __irq_usr            @  0  (USR_26 / USR_32)
    .long    __irq_invalid            @  1  (FIQ_26 / FIQ_32)
    .long    __irq_invalid            @  2  (IRQ_26 / IRQ_32)
    .long    __irq_svc            @  3  (SVC_26 / SVC_32)
    .long    __irq_invalid            @  4
    .long    __irq_invalid            @  5
    .long    __irq_invalid            @  6
    .long    __irq_invalid            @  7
    .long    __irq_invalid            @  8
    .long    __irq_invalid            @  9
    .long    __irq_invalid            @  a
    .long    __irq_invalid            @  b
    .long    __irq_invalid            @  c
    .long    __irq_invalid            @  d
    .long    __irq_invalid            @  e
    .long    __irq_invalid            @  f

  于是,IRQ中断产生后就跳转到vector_stub   irq, IRQ_MODE, 4。

  以程序工作在用户模式时被中断为例,在vector_irq函数中,会将处理器的运行模式切换到SVC模式,然后,进入__irq_svc,保存处理器被中断前的硬件上下文,最后,跳转到中断控制器驱动初始化时绑定的中断入口函数handle_arch_irq(进入这个函数时,cpu本地中断处于屏蔽状态),具体内容如下表:

/* 将r0、lr寄存器(lr就是被中断的下一条指令)以及spsr(用户模式的cpsr)保存到irq模式的栈 */
        stmia sp, {r0, lr}
        mrs lr, spsr --- /* 此时lr保存的是spsr,也就是用户模式的cpsr,也就是被中断前处理器所处的模式 */
        str lr, [sp, #8]
    /* 将spsr寄存器修改为svc模式,为切换到管理模式做准备 */
        mrs r0, cpsr  --- /* 此时中断模式的CPSR,中断还是屏蔽状态 */
        eor r0, r0, #(\mode ^ SVC_MODE | PSR_ISETSTATE)
        msr spsr_cxsf, r0
    /* 获取被中断前处理器所处的模式 */
        and lr, lr, #0x0f
        THUMB(adr r0, 1f)
        THUMB(ldr lr, [r0, lr, lsl #2])
    /* 让r0寄存器指向中断模式下堆栈的基地址 */
        mov r0, sp
    /* 将lr设置为__irq_usr(此时的lr只是作为临时寄存器使用),然后跳转到__irq_usr */
        ARM(ldr lr, [pc, lr, lsl #2])
        movs pc, lr  --- /* movs指令会同时将spsr_irq赋值给cpsr,从而实现向svc模式切换 */
__irq_usr:
/* usr_entry进行irq_handler前,硬件上下文的保存*/
        sub sp, sp, #S_FRAME_SIZE --- /* 分配svc模式的栈 */
        /* r0-r12是所有模式公共的,保存到栈 */
        ARM(stmib sp, {r1 - r12})
        THUMB(stmia sp, {r0 - r12})
        /* 将之前保存在中断模式堆栈中的r0_usr,lr,spsr分别存储到r3-r5中 --- 当前r0是中断模式堆栈的基地址 */
        ldmia r0, {r3 - r5}
        add r0, sp, #S_PC
        mov r6, #-1
        /* 保存用户模式下的sp_usr,lr_usr */
        stmia r0, {r4 - r6}
        ARM(stmdb r0, {sp, lr}^)
        THUMB(store_user_sp_lr r0, r1, S_SP - S_PC)
    /* 进入irq_handler */
        irq_handler
            /*
             * Interrupt handling.
            */
                .macro    irq_handler
            #ifdef CONFIG_MULTI_IRQ_HANDLER
                ldr    r1, =handle_arch_irq  --- /* 然后,进入这个函数执行 */
                mov    r0, sp
                badr    lr, 9997f
                ldr    pc, [r1]
            #else
                arch_irq_handler_default
            #endif
            9997:
                .endm
    /* 恢复用户模式 */
        b ret_to_user_from_irq

3.3 中断控制器驱动

3.3.1 中断控制器驱动初始化

  

  如上图所示,中断控制器驱动主要围绕struct gic_chip_data结构体进行;该结构体中包含两个主要的结构体struct irq_chip和struct irq_domain,其中,struct irq_chip用来表示中断控制器芯片,一个系统中可能使用多片中断控制器,一片中断控制器用一个struct irq_chip结构体表示,这个结构体里面填充了中断控制器的操作函数,比如irq_enable和irq_disable;struct irq_domain用于硬件中断号和虚拟中断号的管理。

  中断控制器驱动,主要完成如下3部分功能:

  1. 将上层的中断处理函数绑定给中断向量里面的回调函数,比如为IRQ中断向量中的回调函数handle_arch_irq绑定为gic_handle_irq,还有SMP核间交互的回调函数gic_raise_softirq以及cpu热插拔的回调函数gic_starting_cpu;

  2. 提供中断控制器芯片的操作接口给上层模块调用;

  3. 中断控制器中每个中断以实际的硬件中断号标识,linux内核对每个中断以虚拟中断号标识,因此中断控制器驱动还要管理硬件中断号和虚拟中断号之间的映射。

  中断控制器的初始化流程如下图所示:

  中断控制器驱动初始化的入口函数是gic_of_init,这个函数放在IRQCHIP_DECLARE宏里面,内核对每一种中断控制器都声明一个IRQCHIP_DECLARE宏,如下,宏里面包含的compatible字段和中断控制器初始化入口函数,of_irq_init(__irqchip_of_table)函数中,将设备树中中断控制器节点的compatible字段和这些宏的compatible比较,找到该中断控制器对应的IRQCHIP_DECLARE宏,然后调用里面的回调函数gic_of_init进行中断控制器驱动的初始化;itop4412中断控制器的设备树节点如下,compatible字段是"arm,cortex-a9-gic",于是和IRQCHIP_DECLARE(cortex_a9_gic, "arm,cortex-a9-gic", gic_of_init)匹配。

IRQCHIP_DECLARE(gic_400, "arm,gic-400", gic_of_init);
IRQCHIP_DECLARE(arm11mp_gic, "arm,arm11mp-gic", gic_of_init);
IRQCHIP_DECLARE(arm1176jzf_dc_gic, "arm,arm1176jzf-devchip-gic", gic_of_init);
IRQCHIP_DECLARE(cortex_a15_gic, "arm,cortex-a15-gic", gic_of_init);
IRQCHIP_DECLARE(cortex_a9_gic, "arm,cortex-a9-gic", gic_of_init);
IRQCHIP_DECLARE(cortex_a7_gic, "arm,cortex-a7-gic", gic_of_init);
IRQCHIP_DECLARE(msm_8660_qgic, "qcom,msm-8660-qgic", gic_of_init);
IRQCHIP_DECLARE(msm_qgic2, "qcom,msm-qgic2", gic_of_init);
IRQCHIP_DECLARE(pl390, "arm,pl390", gic_of_init);

  itop4412中断控制器设备树节点:

gic: interrupt-controller@10490000 {
                compatible = "arm,cortex-a9-gic";
                #interrupt-cells = <3>;
                interrupt-controller;
                reg = <0x10490000 0x10000>, <0x10480000 0x10000>;
};

3.3.2 中断管理

  每个硬件中断在中断控制器中有个固定的硬件中断号,在linux内核中对应一个虚拟中断号,多个外设可以共享一条硬件中断线,那么一个虚拟中断号就可以挂接多个中断处理函数以对应多个外设。

  每个硬件中断用中断描述符struct irq_desc描述,这个结构体里面主要包含如下几个内容:

  1. struct irq_domain结构体,负责硬件中断号和虚拟中断号的映射管理;

  2. struct irq_chip结构体,中断控制器芯片的操作函数;

  3. irq_flow_handler_t handle_irq函数,中断产生后,中断向量的回调函数中最终会调用到这个函数;

  4. struct irqaction结构体,用户注册中断时,注册接口会创建一个irqaction结构体,将用户注册的中断处理函数绑定到这个结构体,然后放到中断描述符irq_desc的struct irqaction链表里面,当中断产生时,处理函数irq_flow_handler就会遍历这个链表,逐个处理用户注册的中断处理。

  用户通过request_irq/request_threaded_irq函数注册中断处理函数,在这个函数里面会根据虚拟中断号获取中断描述符,分配action结构体,并填充,包括中断回调函数、线程回调函数等,进行线程化处理逻辑,如果是非共享中断,进行开中断以及cpu亲和性等处理,然后将action结构体放到中断描述符的链表尾部,最后还会在proc文件系统中添加相关信息,具体过程如下:

/*
 * 获取中断描述符
 * 分配action结构体,并填充,包括中断回调函数、线程回调函数等
 * 创建内核线程
 * 如果是非共享中断,进行开中断以及cpu亲和性等处理
 * 将action结构体放到中断描述符的链表尾部
 * 在proc文件系统中添加相关信息
*/
request_irq
    request_threaded_irq
        /*
         * 虚拟中断号获取中断描述符
        */
        desc = irq_to_desc(irq);
        /*
         * 分配结构体struct irqaction,并填充
        */
        action = kzalloc(sizeof(struct irqaction), GFP_KERNEL);
        action->handler = handler;
        action->thread_fn = thread_fn;
        action->flags = irqflags;
        action->name = devname;
        action->dev_id = dev_id;
        /*
         * __setup_irq(unsigned int irq, struct irq_desc *desc, struct irqaction *new)
         * 中断注册主要处理函数
         * irq -> 虚拟中断号
         * desc -> 中断描述符
         * action -> 前面kzalloc分配的结构体
        */
        __setup_irq(irq, desc, action)
            new->irq = irq;
            nested = irq_settings_is_nested_thread(desc);
            if (nested)
                /* 如果中断绑定在其他中断线程中,需要特别处理 */
                new->handler = irq_nested_primary_handler;
            else
                /* 判断是否能线程化,进行强制线程化处理 */
                if (irq_settings_can_thread(desc))
                    /*
                     * 强制线程化处理
                     * 填充前面分配的action结构体
                     * 创建secondary action
                    */
                    irq_setup_forced_threading(new)
                        new->flags |= IRQF_ONESHOT;
                        new->secondary = kzalloc(sizeof(struct irqaction), GFP_KERNEL);
                        set_bit(IRQTF_FORCED_THREAD, &new->thread_flags);
                        /*
                         * 1、将线程回调函数设置成中断处理函数new->handler
                         * 2、将中断处理函数设置成默认的处理函数irq_default_primary_handler
                         * ??? 为什么要这样设置???
                        */
                        new->thread_fn = new->handler;
                        new->handler = irq_default_primary_handler;
            /*
             * 创建一个内核线程
            */
            if (new->thread_fn && !nested)
                setup_irq_thread(new, irq, false)
                    t = kthread_create(irq_thread, new, "irq/%d-%s", irq, new->name);
                    /* 设置线程的调度策略 */
                    sched_setscheduler_nocheck(t, SCHED_FIFO, &param);
            /*
             * 中断描述符的action链表为空,也就是第一次挂接action,需要分配资源
            */
            if (!desc->action)
                irq_request_resources(desc)
                    struct irq_data *d = &desc->irq_data;
                    struct irq_chip *c = d->chip;
                    /* 调用gic驱动的回调函数申请资源 */
                    c->irq_request_resources ? c->irq_request_resources(d) : 0;
            /*
             * 共享中断
             * 在已经挂接action的情况下,将当前action挂接到链表中
            */
            old_ptr = &desc->action;
            old = *old_ptr;
            if (old)
                do {
                    /*
                     * Or all existing action->thread_mask bits,
                     * so we can find the next zero bit for this
                     * new action.
                     */
                    thread_mask |= old->thread_mask;
                    old_ptr = &old->next;
                    old = *old_ptr;
                } while (old);  --- /* 循环结束后,old指向链表尾部,指针内容为NULL */
                shared = 1;
            /*
             * 非共享中断要特殊处理
            */
            if (!shared)
                init_waitqueue_head(&desc->wait_for_threads);
                __irq_set_trigger(desc, new->flags & IRQF_TRIGGER_MASK);
                irqd_clear(&desc->irq_data, IRQD_IRQ_INPROGRESS);
                if (new->flags & IRQF_PERCPU)
                    irqd_set(&desc->irq_data, IRQD_PER_CPU);
                    irq_settings_set_per_cpu(desc);
                /* Exclude IRQ from balancing if requested */
                if (new->flags & IRQF_NOBALANCING)
                    irq_settings_set_no_balancing(desc);
                    irqd_set(&desc->irq_data, IRQD_NO_BALANCING);
                if (irq_settings_can_autoenable(desc))
                    /*
                     * 开启中断,并进行亲和性设置
                    */
                    irq_startup(desc, IRQ_RESEND, IRQ_START_COND)
                        if (irqd_is_started(d)) {
                            irq_enable(desc);
                        } else {
                            switch (__irq_startup_managed(desc, aff, force)) {
                            case IRQ_STARTUP_NORMAL:
                                ret = __irq_startup(desc);
                                irq_setup_affinity(desc);
                                break;
                            case IRQ_STARTUP_MANAGED:
                                irq_do_set_affinity(d, aff, false);
                                ret = __irq_startup(desc);
                                break;
                            case IRQ_STARTUP_ABORT:
                                return 0;
                            }
                        }
            /* 将action结构体放到中断描述符链表的尾部 */
            *old_ptr = new;
            /*
             * 将内核线程设置为可执行状态
            */
            wake_up_process(new->thread);
            /* 在proc文件系统中添加相关信息 */
            register_irq_proc(irq, desc);
            irq_add_debugfs_entry(irq, desc);
            new->dir = NULL;
            register_handler_proc(irq, new);

3.4 完整的中断处理流程

  处理器检测到中断后,会将工作模式切换到对应的中断模式,然后将pc指向中断向量,从而跳转到中断向量执行;在中断向量中,软件会将工作模式切换到SVC模式,保存硬件上下文,然后跳转到控制器驱动初始化时注册的回调函数;在这个回调函数里面会通过硬件中断号找到对应的虚拟中断号,从而找到该中断对应的中断描述符irq_desc;最后依次调用中断描述符irq_desc中用户注册的中断处理函数。

  以处理器工作在用户模式被IRQ中断为例,具体流程如下:

1. 处理器切换到IRQ中断模式,跳转到中断向量vector_irq
    W(b)    vector_irq
    Vector_irq展开后为宏定义vector_stub    irq, IRQ_MODE, 4
2. 在vector_irq中,将处理器模式切换到SVC模式(需要指出的时,此时的IRQ中断通过CPSR寄存器被屏蔽掉了),跳转到__irq_usr
    将lr设置为__irq_usr(此时的lr只是作为临时寄存器使用),然后跳转到__irq_usr
        ARM(ldr lr, [pc, lr, lsl #2])
        movs pc, lr  --- movs指令会同时将spsr_irq赋值给cpsr,从而实现向svc模式切换
3. 在__irq_usr中,调用irq_handler,然后,在irq_handler里面跳转到handle_arch_irq,handle_arch_irq这个函数指针在中断控制器驱动初始化的时候被赋值为gic_handle_irq
    set_handle_irq(gic_handle_irq)
    void __init set_handle_irq(void (*handle_irq)(struct pt_regs *))
        handle_arch_irq = handle_irq;
4. gic_handle_irq这个函数里面会进行循环处理完所有待处理的中断,
从中断控制器的寄存器读取硬件中断号,
    irqstat = readl_relaxed(cpu_base + GIC_CPU_INTACK);
    irqnr = irqstat & GICC_IAR_INT_ID_MASK;
    当硬件中断号小于16,表示核间IPI中断,调用handle_IPI
        if (irqnr < 16) {
            writel_relaxed(irqstat, cpu_base + GIC_CPU_EOI);
            if (static_key_true(&supports_deactivate))
                writel_relaxed(irqstat, cpu_base + GIC_CPU_DEACTIVATE);
#ifdef CONFIG_SMP
            /*
             * Ensure any shared data written by the CPU sending
             * the IPI is read after we've read the ACK register
             * on the GIC.
             *
             * Pairs with the write barrier in gic_raise_softirq
             */
            smp_rmb();
            handle_IPI(irqnr, regs);
#endif
            continue;
        }
    当硬件中断号大于15小于1020,表示共享和CPU私有中断,调用handle_domain_irq
        if (likely(irqnr > 15 && irqnr < 1020)) {
            if (static_key_true(&supports_deactivate))
                writel_relaxed(irqstat, cpu_base + GIC_CPU_EOI);
            isb();
            handle_domain_irq(gic->domain, irqnr, regs);
            continue;
        }
    当读取的硬件中断号无效,则退出while循环
5. IRQ中断,进入handle_domain_irq函数
6. handle_domain_irq函数中,首先会调用irq_enter函数进入中断上下文
    irq_enter();
7. irq_enter函数,将处理器preempt_count变量的HARDIRQ部分+1表示进入硬件中断上下文;系统会根据preempt_count变量来判断是否可以调度及抢占,只有preempt_count值为0时,才可以调度和抢占;那么handle_domain_irq函数在退出前,系统一直处于不可抢占状态,那么当前中断就一直使用被中断进程的上下文,比如内核栈、current以及preempt_count等都一直是被中断进程的上下文
    #define __irq_enter()                    \
    do {                        \
        account_irq_enter_time(current);    \
        preempt_count_add(HARDIRQ_OFFSET);    \
        trace_hardirq_enter();            \
    } while (0)
8. 取出虚拟中断号
    irq = irq_find_mapping(domain, hwirq)
9. 进一步调用上层的处理函数generic_handle_irq
10. 通过generic_handle_irq_desc调用中断描述符的handle_irq,在建立硬件中断号和虚拟中断号的映射关系时,gic_irq_domain_map函数中给handle_irq绑定了处理函数,
    硬件中断号小于32时,handle_irq被绑定为handle_percpu_devid_irq
        if (hw < 32) {
            irq_set_percpu_devid(irq);
            irq_domain_set_info(d, irq, hw, &gic->chip, d->host_data,
                        handle_percpu_devid_irq, NULL, NULL);
            irq_set_status_flags(irq, IRQ_NOAUTOEN);
        }
    硬件中断号大于等于32时,handle_irq被绑定为handle_fasteoi_irq
        irq_domain_set_info(d, irq, hw, &gic->chip, d->host_data,
                             handle_fasteoi_irq, NULL, NULL);
11. 进入handle_fasteoi_irq函数,使用raw_spin_lock对中断描述符访问加自旋锁
    raw_spin_lock(&desc->lock);
    修改中断状态
        desc->istate &= ~(IRQS_REPLAY | IRQS_WAITING);
12. 如果用户没有注册中断处理函数(即action链表为空),或者该中断处于屏蔽状态,那么将中断状态修改为IRQS_PENDING,调用mask_irq在中断控制器级屏蔽该中断线,然后,退出handle_fasteoi_irq函数
    /*
     * If its disabled or no action available
     * then mask it and get out of here:
     */
    if (unlikely(!desc->action || irqd_irq_disabled(&desc->irq_data))) {
        desc->istate |= IRQS_PENDING;
        mask_irq(desc);
        goto out;
    }
13. 如果是IRQS_ONESHOT类型中断,那么屏蔽该中断
    if (desc->istate & IRQS_ONESHOT)
        mask_irq(desc);
14. 进一步调用handle_irq_event
    handle_irq_event(desc);
15. 进入handle_irq_event函数
    将中断状态修改为IRQD_IRQ_INPROGRESS
        desc->istate &= ~IRQS_PENDING;
        irqd_set(&desc->irq_data, IRQD_IRQ_INPROGRESS);
    进一步调用handle_irq_event_percpu
16. 进入handle_irq_event_percpu函数
    扫描action链表,依次调用用户注册的中断处理函数
        for_each_action_of_desc(desc, action)
            irqreturn_t res;
            /*
             * arm32处理器,此时读出的cpsr寄存器bit[7]被置1,说明cpu本地IRQ中断被禁止了,那就是不会发生IRQ硬件中断嵌套的情况了?
             */
            res = action->handler(irq, action->dev_id);
            根据返回值res判断,是否进行了线程化,如果进行了线程化,则唤醒对应的内核线程
                switch (res)
                    case IRQ_WAKE_THREAD:
                        __irq_wake_thread(desc, action);
            非线程化,则修改flag,然后返回
                case IRQ_HANDLED:
17. generic_handle_irq函数返回有,__handle_domain_irq会调用irq_exit函数退出中断上下文
    irq_exit();
18. 进入irq_exit函数
    如果本地中断没有被屏蔽,则会产生告警
        #ifndef __ARCH_IRQ_EXIT_IRQS_DISABLED
            local_irq_disable();
#else
        WARN_ON_ONCE(!irqs_disabled());
#endif
    统计硬件中断退出的次数
        account_irq_exit_time(current);
    退出硬件中断上下文
        preempt_count_sub(HARDIRQ_OFFSET);
    如果不在硬件上下文,并且有软中断需要处理,那么开始执行软中断
        如果没有设置软中断强制线程化,那么直接调用软中断的回调函数执行,此时中断使用的栈还没有完全释放干净,因此使用的还是硬件堆栈
            __do_softirq();
        如果设置了软中断强制线程化,那么调度软中断内核线程运行,每个cpu都绑定了一个软中断内核线程
            wakeup_softirqd();

 

本文大部分内容参考如下博客,梳理总结只为自己更好地理解,如有侵权,请联系删除

参考资料:

https://www.cnblogs.com/LoyenWang/p/12996812.html

 

这篇关于linux arm32中断子系统学习总结(三)--- 软件子系统的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!