Linux中断管理 (1)Linux中断管理机制


目录:

《Linux中断管理》

《Linux中断管理 (1)Linux中断管理机制》

《Linux中断管理 (2)软中断和tasklet》

《Linux中断管理 (3)workqueue工作队列》

关键词:GIC、IAR、EOI、SGI/PPI/SPI、中断映射、中断异常向量、中断上下文、内核中断线程、中断注册

由于篇幅较大,简单梳理一下内容。

本章主要可以分为三大部分:

讲解硬件背景的1. ARM中断控制器。

系统初始化的静态过程:GIC初始化和各中断的中断号映射2. 硬件中断号和Linux中断号的映射;每个中断的注册5. 注册中断。

一个中断从产生到执行完毕的动态过程:ARM底层通用部分如何处理3. ARM底层中断处理;GIC部分的处理流程以及上层通用处理部分4. 高层中断处理。

这里的高层处理,没有包括下半部。下半部在Linux中断管理 (2)软中断和tasklet和Linux中断管理 (3)workqueue工作队列中进行介绍。

读取GICC_IAR

  T64时刻:在中断N被Linux相应3个时钟内,CPU Interface模块完成对nFIQCPU[n]信号的deasserts,即拉高nFIQCPU[n]信号。

  T126时刻:外设也deassert了该中断N。

  T128时刻:仲裁单元移出了中断N的pending状态。

  T131时刻:Linux服务程序把中断N的硬件ID号写入GICC_EOIR寄存器来完成中断N的全部处理过程。写GICC_EOIR

(8) T146时刻:在向GICC_EOIR寄存器写入中断N中断号后的tph个时钟后,仲裁单元会选择下一个最高优先级中断,即中断M,发送中断请求给CPU Interface模块。CPU Interface会拉低nFIQCPU[n]信号来向CPU报告外设M的中断请求。

(9) T211时刻:Linux中断服务程序读取GICC_IAR寄存器来响应中断,仲裁单元设置中断M的状态为active and pending。

(10) T214时刻:在CPU响应中断后的3个时钟内,CPU Interface模块拉高nFIOCPU[n]信号来完成deassert动作。

那么GICC_IAR和GICC_EOIR分别在Linux什么地方触发的呢?

1.4 Cortex A15 A7实例

arm,cortex-a15-gic", "arm,cortex-a9-gic";------------------此设备的标识符是"arm,cortex-a15-gic" #interrupt-cells = <3>; #address-cells = <0>; interrupt-controller;----------------------------------------------------表示此设备是一个中断控制器 reg = <0 0x2c001000 0 0x1000>, <0 0x2c002000 0 0x1000>, <0 0x2c004000 0 0x2000>, <0 0x2c006000 0 0x2000>; interrupts = <1 9 0xf04>; };

struct irq_domain用于描述一个中断控制器。

GIC中断控制器在初始化时解析DTS信息中定义了几个GIC控制器,每个GIC控制器注册一个struct irq_domain数据结构。

struct irq_domain {
    struct list_head link;-------------------------用于将irq_domain连接到全局链表irq_domain_list中。
    const char *name;------------------------------中断控制器名称
    const struct irq_domain_ops *ops;--------------irq domain映射操作使用的方法集合
    void *host_data;
    unsigned int flags;

    /* Optional data */
    struct device_node *of_node;------------------对应中断控制器的device node
    struct irq_domain_chip_generic *gc;
#ifdef    CONFIG_IRQ_DOMAIN_HIERARCHY
    struct irq_domain *parent;
#endif

    /* reverse map data. The linear map gets appended to the irq_domain */
    irq_hw_number_t hwirq_max;--------------------该irq domain支持中断数量的最大值。
    unsigned int revmap_direct_max_irq;
    unsigned int revmap_size;---------------------线性映射的大小
    struct radix_tree_root revmap_tree;-----------Radix Tree映射的根节点
    unsigned int linear_revmap[];-----------------线性映射用到的lookup table
}

 struct irq_domain_ops定义了irq_domain方法集合,xlate从intspec中解析出硬件中断号和中断类型,intspec[0]和intspec[1]决定中断号,intspec[2]决定中断类型。

 gic_irq_domain_xlate,
    .alloc = gic_irq_domain_alloc,
    .free = irq_domain_free_irqs_top,
};

gic_routable_irq_domain_ops->xlate(d, controller,
                             intspec,
                             intsize,
                             out_hwirq,
                             out_type);

        if (IS_ERR_VALUE(ret))
            return ret;
    }

    *out_type = intspec[2] & IRQ_TYPE_SENSE_MASK;---------------------中断触发类型,包括四种上升沿、下降沿、高电平、低电平。

    return ret;
}

gic_irq_domain_map(domain, virq + i, hwirq + i);---------------执行软硬件的映射,并且根据中断类型设置struct irq_desc->handle_irq处理函数。

    return 0;
}

gic_chip, d->host_data,
                    handle_percpu_devid_irq, NULL, NULL);
        set_irq_flags(irq, IRQF_VALID | IRQF_NOAUTOEN);
    } else {
        irq_domain_set_info(d, irq, hw, &gic_chip, d->host_data,
                    handle_fasteoi_irq, NULL, NULL);
        set_irq_flags(irq, IRQF_VALID | IRQF_PROBE);

        gic_routable_irq_domain_ops->map(d, irq, hw);
    }
    return 0;
}

irq_domain_set_hwirq_and_chip(domain, virq, hwirq, chip, chip_data);
    __irq_set_handler(virq, handler, 0, handler_name);
    irq_set_handler_data(virq, handler_data);
}

gic_of_init);

static int gic_cnt __initdata;

static int __init
gic_init_bases(gic_cnt, -1, dist_base, cpu_base, percpu_offset, node);
    if (!gic_cnt)
        gic_init_physaddr(node);

    if (parent) {
        irq = irq_of_parse_and_map(node, 0);
        gic_cascade_irq(gic_cnt, irq);
    }

    if (IS_ENABLED(CONFIG_ARM_GIC_V2M))
        gicv2m_of_init(node, gic_data[gic_cnt].domain);

    gic_cnt++;
    return 0;
}

 gic_init_bases的gic_nr是GIC控制器的序号,主要调用irq_domain_add_linear()分配并函数注册一个irq_domain。

gic_irq_domain_hierarchy_ops;--------------GICv2的struct irq_domain_ops
...
        gic->domain = irq_domain_add_linear(node, gic_irqs, ops, gic);-----------------注册irq_domain,操作函数使用gic_irq_domain_hierarchy_ops
    } else {        /* Non-DT case */
...
    }

    if (WARN_ON(!gic->domain))
        return;

    if (gic_nr == 0) {
#ifdef CONFIG_SMP
        set_smp_cross_call(gic_raise_softirq);
        register_cpu_notifier(&gic_cpu_notifier);
#endif
        set_handle_irq(gic_handle_irq);-------在irq_handler中调用handle_arch_irq,这里将handle_arch_irq指向gic_handle_irq,实现了平台中断和具体GIC中断的关联。
    }

    gic_chip.flags |= gic_arch_extn.flags;
    gic_dist_init(gic);----------------------GIC Distributer部分初始化
    gic_cpu_init(gic);-----------------------GIC CPU Interface部分初始化
    gic_pm_init(gic);------------------------GIC PM相关初始化
}

 irq_domain_add_linear()->__irq_domain_add()分配并初始化struct irq_domain。

struct irq_domain *__irq_domain_add(struct device_node *of_node, int size,
                    irq_hw_number_t hwirq_max, int direct_max,
                    const struct irq_domain_ops *ops,
                    void *host_data)
{
    struct irq_domain *domain;

    domain = kzalloc_node(sizeof(*domain) + (sizeof(unsigned int) * size),
                  GFP_KERNEL, of_node_to_nid(of_node));-------------domain大小为struct irq_domain加上gic_irqs个unsigned int。
    if (WARN_ON(!domain))
        return NULL;

    /* Fill structure */
    INIT_RADIX_TREE(&domain->revmap_tree, GFP_KERNEL);
    domain->ops = ops;
    domain->host_data = host_data;
    domain->of_node = of_node_get(of_node);
    domain->hwirq_max = hwirq_max;
    domain->revmap_size = size;
    domain->revmap_direct_max_irq = direct_max;
    irq_domain_check_hierarchy(domain);

    mutex_lock(&irq_domain_mutex);
    list_add(&domain->link, &irq_domain_list);----------------------将创建好的struct irq_domain加入全局链表irq_domain_list。
    mutex_unlock(&irq_domain_mutex);

    pr_debug("Added domain %s\n", domain->name);
    return domain;
}

2.3 系统初始化之中断号映射

 上一小节是中断控制器GIC的初始化,下面看看一个硬件中断是如何映射到Linux空间的中断的。

customize_machine()是arch_initcall阶段调用,很靠前。

 customize_machine

  ->of_platform_populate

    ->of_platform_bus_create

      ->of_amba_device_create

        ->of_amba_device_create

下面结合dtsi文件看看来龙去脉,arch/arm/boot/dts/vexpress-v2m.dtsi。

/dts-v1/;

/ {
    model = "V2P-CA9";
    arm,hbi = <0x191>;
    arm,vexpress,site = <0xf>;
    compatible = "arm,vexpress,v2p-ca9", "arm,vexpress";
    interrupt-parent = <&gic>;
    #address-cells = <1>;
    #size-cells = <1>;
...
    gic: interrupt-controller@1e001000 {
        compatible = "arm,cortex-a9-gic";
        #interrupt-cells = <3>;
        #address-cells = <0>;
        interrupt-controller;
        reg = <0x1e001000 0x1000>,
              <0x1e000100 0x100>;
    };
...
    smb {
        compatible = "simple-bus";

        #address-cells = <2>;
        #size-cells = <1>;
        ranges = <0 0 0x40000000 0x04000000>,
             <1 0 0x44000000 0x04000000>,
             <2 0 0x48000000 0x04000000>,
             <3 0 0x4c000000 0x04000000>,
             <7 0 0x10000000 0x00020000>;

        #interrupt-cells = <1>;
        interrupt-map-mask = <0 0 63>;
        interrupt-map = <0 0  0 &gic 0  0 4>,
                <0 0  1 &gic 0  1 4>,
...
/include/ "vexpress-v2m.dtsi"
    };
}

vexpress-v2m.dtsi文件:
motherboard { model
= "V2M-P1"; arm,hbi = <0x190>; arm,vexpress,site = <0>; compatible = "arm,vexpress,v2m-p1", "simple-bus"; #address-cells = <2>; /* SMB chipselect number and offset */ #size-cells = <1>; #interrupt-cells = <1>; ranges; ... iofpga@7,00000000 { compatible = "arm,amba-bus", "simple-bus"; #address-cells = <1>; #size-cells = <1>; ranges = <0 7 0 0x20000>; ... v2m_serial0: uart@09000 { compatible = "arm,pl011", "arm,primecell"; reg = <0x09000 0x1000>; interrupts = <5>; clocks = <&v2m_oscclk2>, <&smbclk>; clock-names = "uartclk", "apb_pclk"; }; ... }; }

这里首先从根目录下查找"simple-bus",从上面可以看出指向smb设备。

smb设备包含vexpress-v2m.dtsi文件,然后在of_platform_bus_create()中遍历所有设备。

const struct of_device_id of_default_bus_match_table[] = {
    { .compatible = "simple-bus", },
#ifdef CONFIG_ARM_AMBA
    { .compatible = "arm,amba-bus", },
#endif /* CONFIG_ARM_AMBA */
    {} /* Empty terminated list */
};


static int __init customize_machine(void)
{
...
        of_platform_populate(NULL, of_default_bus_match_table,-----------------找到匹配"simple-bus"的设备,这里指向smb。
                    NULL, NULL);
...
}


of_platform_bus_create(child, matches, lookup, parent, true);-----这里的root指向根目录,即"/"。
        if (rc)
            break;
    }
...
}


        of_amba_device_create(bus, bus_id, platform_data, parent);
        return 0;
    }

    dev = of_platform_device_create_pdata(bus, bus_id, platform_data, parent);
    if (!dev || !of_match_node(matches, bus))
        return 0;

    for_each_child_of_node(bus, child) {----------------遍历smb下的所有"simple-bus"设备,这里可以嵌套几层。从smb->motherboard->iofpga@7,00000000。
        pr_debug("   create child: %s\n", child->full_name);
        rc = of_platform_bus_create(child, matches, lookup, &dev->dev, strict);
        if (rc) {
            of_node_put(child);
            break;
        }
    }
    of_node_set_flag(bus, OF_POPULATED_BUS);
    return rc;
}

 of_amba_device_create创建ARM AMBA类型设备,其中中断部分交给irq_of_parse_and_map()处理。

 irq_of_parse_and_map(node, i);
...
}

以uart@09000为例,irq_of_parse_and_map中的of_irq_parse_one()解析设备中的"interrupts"、"regs"等参数,参数放入struct of_phandle_args中,oirq->args[1]中存放中断号5,oirq->np存放struct device_node。

irq_create_of_mapping()建立硬件中断号到Linux中断号的映射。

irq_create_of_mapping主要调用如下,主要工作交给__irq_domain_alloc_irqs()进行处理。

irq_create_of_mapping

  ->domain->ops->xlate---------------------------------

  ->irq_find_mapping

  ->irq_domain_alloc_irqs

    ->__irq_domain_alloc_irqs

      ->irq_domain_alloc_descs

      ->irq_domain_alloc_irq_data

      ->irq_domain_alloc_irqs_recursive

        ->gic_irq_domain_alloc

          ->gic_irq_domain_map-----------------------进行硬件中断号和软件中断号的映射

            ->gic_irq_domain_set_info----------------设置重要参数到中断描述符中

      ->irq_domain_insert_irq

irq_create_of_mapping(&oirq);
}

gic_irq_domain_xlate()函数进行硬件中断号到Linux中断号的转换。
                    irq_data->args_count, &hwirq, &type))
            return 0;
    }

    if (irq_domain_is_hierarchy(domain)) {-------------------------可以分层挂载
        /*
         * If we've already configured this interrupt,
         * don't do it again, or hell will break loose.
         */
        virq = irq_find_mapping(domain, hwirq);-------------------从已有的linear_revmap中寻找Linux中断号。
        if (virq)
            return virq;

        virq = irq_domain_alloc_irqs(domain, 1, NUMA_NO_NODE, irq_data);---------如果没有找到,重新分配中断映射。参数1表示每次只分配一个中断。
        if (virq <= 0)
            return 0;
    } else {
...
    }

    /* Set type if specified and different than the current one */
    if (type != IRQ_TYPE_NONE &&
        type != irq_get_trigger_type(virq))
        irq_set_irq_type(virq, type);-----------------------------设置中断触发类型
    return virq;
}

struct irq_desc定义了中断描述符,irq_desc[]数组定义了NR_IRQS个中断描述符,数组下标表示IRQ中断号,通过IRQ中断号可以找到对应中断描述符。

struct irq_desc内置了struct irq_data结构体,struct irq_data的irq和hwirq分别对应软件中断号和硬件中断号。通过这两个成员,可以将硬件中断号和软件中断号映射起来。

struct irq_chip定义了中断控制器底层操作相关的方法集合。

irq_data;
    unsigned int __percpu    *kstat_irqs;
    irq_flow_handler_t    handle_irq;-----------------根据中断号分类,不同类型中断的处理handle。0~31对应handle_percpu_devid_irq;32~对应handle_fasteoi_irq。
#ifdef CONFIG_IRQ_PREFLOW_FASTEOI
    irq_preflow_handler_t    preflow_handler;
#endif
    struct irqaction    *action;    /* IRQ action list */
    unsigned int        status_use_accessors;
__irq_alloc_descs。
        if (virq < 0) {
            pr_debug("cannot allocate IRQ(base %d, count %d)\n",
                 irq_base, nr_irqs);
            return virq;
        }
    }

    if (irq_domain_alloc_irq_data(domain, virq, nr_irqs)) {--------------分配struct irq_data数据结构。
        pr_debug("cannot allocate memory for IRQ%d\n", virq);
        ret = -ENOMEM;
        goto out_free_desc;
    }

    mutex_lock(&irq_domain_mutex);
    ret = irq_domain_alloc_irqs_recursive(domain, virq, nr_irqs, arg);----调用struct irq_domain中的alloc回调函数进行硬件中断号和软件中断号的映射。
    if (ret < 0) {
        mutex_unlock(&irq_domain_mutex);
        goto out_free_irq_data;
    }
    for (i = 0; i < nr_irqs; i++)
        irq_domain_insert_irq(virq + i);
    mutex_unlock(&irq_domain_mutex);

    return virq;
...
}

int __ref
domain->ops->alloc(domain, irq_base, nr_irqs, arg);
    if (ret < 0 && recursive)
        irq_domain_free_irqs_recursive(parent, irq_base, nr_irqs);

    return ret;
}

 至此完成了中断DeviceTree的解析,各数据结构的初始化,以及最主要的硬件中断号到Linux中断号的映射。

early_trap_init(vectors);-------------------------------------------实现异常向量表的复制动作。... /* * Create a mapping for the machine vectors at the high-vectors * location (0xffff0000). If we aren't using high-vectors, also * create a mapping at the low-vectors virtual address. */ map.pfn = __phys_to_pfn(virt_to_phys(vectors));---------------------vectors物理页面号 map.virtual = 0xffff0000;-------------------------------------------待映射到的虚拟地址0xffff_0000~0xffff_0fff map.length = PAGE_SIZE;---------------------------------------------映射区间大小 #ifdef CONFIG_KUSER_HELPERS map.type = MT_HIGH_VECTORS;-----------------------------------------映射到high vector #else map.type = MT_LOW_VECTORS; #endif create_mapping(&map); if (!vectors_high()) { map.virtual = 0; map.length = PAGE_SIZE * 2; map.type = MT_LOW_VECTORS; create_mapping(&map); } /* Now create a kernel read-only mapping */ map.pfn += 1; map.virtual = 0xffff0000 + PAGE_SIZE;------------------------------映射到0xffff_1000~0xffff_1ffff map.length = PAGE_SIZE; map.type = MT_LOW_VECTORS; create_mapping(&map); ... }

early_trap_init分别将__vectors_start和__stubs_start两个页面复制到分配的两个页面中。

vector_stub定义。

vector_irq---------------------------------------------------------------跳转到vector_irq W(b) vector_fiq /* * Interrupt dispatcher */ __irq_svc @ 3 (SVC_26 / SVC_32)----------------------------svc模式数值是0b10011,与上0xf后就是3。 .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



sub lr, lr, #\correction-------------------------------------------------------correction==4解释 .endif @ @ Save r0, lr_ (parent PC) and spsr_ @ (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)---------------------------------修改CPSR寄存器的控制域为SVC模式,为了使中断处理在SVC模式下执行。 msr spsr_cxsf, r0 @ @ the branch table must immediately follow this code @ and lr, lr, #0x0f--------------------------------------------------------------低4位反映了进入中断前CPU的运行模式,9为USR,3为SVC模式。 THUMB( adr r0, 1f ) THUMB( ldr lr, [r0, lr, lsl #2] )-------------------------------------------根据中断发生点所在的模式,给lr寄存器赋值,__irq_usr或者__irq_svc标签处。 mov r0, spk ARM( ldr lr, [pc, lr, lsl #2] )---------------------------------------------得到的lr就是".long __irq_svc" movs pc, lr @ branch to handler in SVC mode-------------------------把lr的值赋给pc指针,跳转到__irq_usr或者__irq_svc。 ENDPROC(vector_\name)

3.3 内核空间中断处理__irq_svc

 __irq_svc处理发生在内核空间的中断,主要svc_entry保护中断现场;irq_handler执行中断处理;如果打开抢占功能,检查是否可以抢占;最后svc_exit执行中断退出处理。

svc_entry
    irq_handler

#ifdef CONFIG_PREEMPT-----------------------------------------------------中断处理结束后,发生抢占的地方?
    get_thread_info tsk
    ldr    r8, [tsk, #TI_PREEMPT]        @ get preempt count--------------获取thread_info->preempt_cpunt变量;preempt_count为0,说明可以抢占进程;preempt_count大于0,表示不能抢占。
    ldr    r0, [tsk, #TI_FLAGS]        @ get flags------------------------获取thread_info->flags变量
    teq    r8, #0                @ if preempt count != 0
    movne    r0, #0                @ force flags to 0
    tst    r0, #_TIF_NEED_RESCHED-----------------------------------------判断是否设置了_TIF_NEED_RESCHED标志位
    blne    svc_preempt
#endif

    svc_exit r5, irq = 1            @ return from exception
 UNWIND(.fnend        )
ENDPROC(__irq_svc)

svc_entry将中断现场保存到内核栈中,主要是struct pt_regs中的寄存器。

irq_handler()

  ->handle_arch_irq()->gic_handle_irq()

    ->handle_domain_irq()->__handle_domain_irq()-------------读取IAR寄存器,响应中断,获取硬件中断号

      ->irq_find_mapping()------------------------------------------------将硬件中断号转变成Linux中断号

      ->generic_handle_irq()---------------------------------------------之后的操作都是Linux中断号

        ->handle_percpu_devid_irq()-----------------------------------SGI/PPI类型中断处理

        ->handle_fasteoi_irq()--------------------------------------------SPI类型中断处理

          ->handle_irq_event()->handle_irq_event_percpu()------执行中断处理核心函数

            ->action->handler-----------------------------------------------执行primary handler。

            ->__irq_wake_thread()----------------------------------------根据需要唤醒中断内核线程

4.1 irq_handler

 irq_handler宏调用handle_arch_irq函数,这个函数set_handle_irq注册,GICv2对应gic_handle_irq。

handle_arch_irq
    mov    r0, sp
    adr    lr, BSYM(9997f)
    ldr    pc, [r1]
#else
    arch_irq_handler_default
#endif
9997:
    .endm

4.2 gic_handle_irq

git_init_bases设置handle_arch_irq为gic_handle_irq。

void __init gic_init_bases(unsigned int gic_nr, int irq_start,
               void __iomem *dist_base, void __iomem *cpu_base,
               u32 percpu_offset, struct device_node *node)
{
...
    if (gic_nr == 0) {
...
        set_handle_irq(gic_handle_irq);
    }
...
}

handle_domain_irq(gic->domain, irqnr, regs);
            continue;
        }
        if (irqnr < 16) {---------------------------------------SGI类型的中断是CPU核间通信所用,只有定义了CONFIG_SMP才有意义。
            writel_relaxed(irqstat, cpu_base + GIC_CPU_EOI);----直接写EOI寄存器,表示结束中断。
#ifdef CONFIG_SMP
            handle_IPI(irqnr, regs);----------------------------irqnr表示SGI中断类型
#endif
            continue;
        }
        break;
    } while (1);
}

handle_domain_irq调用__handle_domain_irq,其中lookup置为true。

irq_enter显式告诉Linux内核现在要进入中断上下文了,在处理完中断后调用irq_exit告诉Linux已经完成中断处理过程。

irq_enter();-----------------------------------------------通过显式增加hardirq域计数,通知Linux进入中断上下文

#ifdef CONFIG_IRQ_DOMAIN
    if (lookup)
        irq = irq_find_mapping(domain, hwirq);-----------------根据硬件中断号找到对应的软件中断号
#endif

    /*
     * Some hardware gives randomly wrong interrupts.  Rather
     * than crashing, do something sensible.
     */
    if (unlikely(!irq || irq >= nr_irqs)) {
        ack_bad_irq(irq);
        ret = -EINVAL;
    } else {
        generic_handle_irq(irq);--------------------------------开始具体某一个中断的处理,此处irq已经是Linux中断号。
    }

    irq_exit();-------------------------------------------------退出中断上下文
    set_irq_regs(old_regs);
    return ret;
}

irq_find_mapping在struct irq_domain中根据hwirq找到Linux环境的irq。

gic_irq_domain_map的时候根据hw号决定handle,hw硬件中断号小于32指向handle_percpu_devid_irq,其他情况指向handle_fasteoi_irq。

void
__irq_set_handler(unsigned int irq, irq_flow_handler_t handle, int is_chained,
          const char *name)
{
...
    desc->handle_irq = handle;
    desc->name = name;
...
}

 handle_percpu_devid_irq处理0~31的SGI/PPI类型中断,首先响应IAR,然后执行handler,最后发送EOI。

chip->irq_eoi(&desc->irq_data);-------------------调用gic_eoi_irq()函数
}

irq_enter和irq_exit显式地处理hardirq域计数,两者之间的部分属于中断上下文。

/*
 * Enter an interrupt context.
 */
in_interrupt() && local_softirq_pending())--------------当前不处于中断上下文,且有pending的softirq,进行softirq处理。
        invoke_softirq();

    tick_irq_exit();
    rcu_irq_exit();
    trace_hardirq_exit(); /* must be last! */
}

4.2.1 中断上下文

判断当前进程是处于中断上下文,还是进程上下文依赖于preempt_count,这个变量在struct thread_info中。

preempt_count计数共32bit,从低到高依次是:

#define PREEMPT_BITS	8
#define SOFTIRQ_BITS	8
#define HARDIRQ_BITS	4
#define NMI_BITS	1
#define hardirq_count()    (preempt_count() & HARDIRQ_MASK)-----------------硬件中断计数
#define softirq_count()    (preempt_count() & SOFTIRQ_MASK)-----------------软中断计数
#define irq_count()    (preempt_count() & (HARDIRQ_MASK | SOFTIRQ_MASK \----包括NMI、硬中断、软中断三者计数
                 | NMI_MASK))

/*
 * Are we doing bottom half or hardware interrupt processing?
 *
 * in_irq()       - We're in (hard) IRQ context
 * in_softirq()   - We have BH disabled, or are processing softirqs
 * in_interrupt() - We're in NMI,IRQ,SoftIRQ context or have BH disabled
 * in_serving_softirq() - We're in softirq context
 * in_nmi()       - We're in NMI context
 * in_task()      - We're in task context
 *
 * Note: due to the BH disabled confusion: in_softirq(),in_interrupt() really
 *       should not be used in new code.
 */
#define in_irq()        (hardirq_count())----------------------------判断是否正在硬件中断上下文
#define in_softirq()        (softirq_count())------------------------判断是否正在处理软中断或者禁止BH。
handle_irq_event(desc);

    cond_unmask_eoi_irq(desc, chip);----------------------------------------根据不同条件执行unmask_irq()解除中断屏蔽,或者执行irq_chip->irq_eoi发送EOI信号,通知GIC中断处理完毕。

    raw_spin_unlock(&desc->lock);
    return;
out:
    if (!(chip->flags & IRQCHIP_EOI_IF_HANDLED))
        chip->irq_eoi(&desc->irq_data);
    raw_spin_unlock(&desc->lock);
}

handle_irq_event调用handle_irq_event_percpu,执行action->handler(),如有需要唤醒内核中断线程执行action->thread_fn。

 handle_irq_event_percpu(desc, action);

    raw_spin_lock(&desc->lock);
    irqd_clear(&desc->irq_data, IRQD_IRQ_INPROGRESS);-------清除IRQD_IRQ_INPROGRESS标志位,表示中断处理结束。
    return ret;
}

irqreturn_t
__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;
}

4.3.1 唤醒中断内核线程

__irq_wake_thread唤醒对应中断的内核线程。

irq_thread, new, "irq/%d-%s", irq,
                   new->name);--------------------------------------------------创建线程名为irq/xxx-xxx的内核线程,线程执行函数是irq_thread。
...
        sched_setscheduler_nocheck(t, SCHED_FIFO, &param);----------------------设置进程调度策略为SCHED_FIFO。

        /*
         * We keep the reference to the task struct even if
         * the thread dies to avoid that the interrupt code
         * references an already freed task_struct.
         */
        get_task_struct(t);
        new->thread = t;-------------------------------------------------------将当前线程和irq_action关联起来

        set_bit(IRQTF_AFFINITY, &new->thread_flags);--------------------------对中断线程设置CPU亲和性
    }
...
}

4.3.3 内核中断线程执行

irq_thread是中断线程的执行函数,在irq_wait_for_interrupt()中等待。

irq_wait_for_interrupt()中判断IRQTF_RUNTHREAD标志位,没有置位则schedule()换出CPU,进行睡眠。

直到__irq_wake_thread()置位了IRQTF_RUNTHREAD,并且wake_up_process()后,irq_wait_for_interrupt()返回0。

 irq_thread_fn;

    init_task_work(&on_exit_work, irq_thread_dtor);
    task_work_add(current, &on_exit_work, false);

    irq_thread_check_affinity(desc, action);

    while (!irq_wait_for_interrupt(action)) {
        irqreturn_t action_ret;

        irq_thread_check_affinity(desc, action);

        action_ret = handler_fn(desc, action);-----------执行中断内核线程函数
        if (action_ret == IRQ_HANDLED)
            atomic_inc(&desc->threads_handled);----------增加threads_handled计数

        wake_threads_waitq(desc);------------------------唤醒wait_for_threads等待队列
    }

    /*
     * This is the regular exit path. __free_irq() is stopping the
     * thread via kthread_stop() after calling
     * synchronize_irq(). So neither IRQTF_RUNTHREAD nor the
     * oneshot mask bit can be set. We cannot verify that as we
     * cannot touch the oneshot mask at this point anymore as
     * __setup_irq() might have given out currents thread_mask
     * again.
     */
    task_work_cancel(current, irq_thread_dtor);
    return 0;
}


static int irq_wait_for_interrupt(struct irqaction *action)
{
    set_current_state(TASK_INTERRUPTIBLE);

    while (!kthread_should_stop()) {

        if (test_and_clear_bit(IRQTF_RUNTHREAD,
                       &action->thread_flags)) {------------判断thread_flags是否设置IRQTF_RUNTHREAD标志位,如果设置则设置当前状态TASK_RUNNING并返回0。此处和__irq_wake_thread中设置IRQTF_RUNTHREAD对应。
            __set_current_state(TASK_RUNNING);
            return 0;
        }
        schedule();-----------------------------------------换出CPU,在此等待睡眠
        set_current_state(TASK_INTERRUPTIBLE);
    }
    __set_current_state(TASK_RUNNING);
    return -1;
}

request_threaded_irq注册中断参数thread_fn。
    irq_finalize_oneshot(desc, action);---------------------针对oneshot类型中断收尾处理,主要是去屏蔽中断。
    return ret;
}

irq_finalize_oneshot()对ontshot类型的中断进行收尾操作。

static void irq_finalize_oneshot(struct irq_desc *desc,
                 struct irqaction *action)
{
    if (!(desc->istate & IRQS_ONESHOT) ||
        action->handler == irq_forced_secondary_handler)
        return;
again:
    chip_bus_lock(desc);
    raw_spin_lock_irq(&desc->lock);

    /*
     * Implausible though it may be we need to protect us against
     * the following scenario:
     *
     * The thread is faster done than the hard interrupt handler
     * on the other CPU. If we unmask the irq line then the
     * interrupt can come in again and masks the line, leaves due
     * to IRQS_INPROGRESS and the irq line is masked forever.
     *
     * This also serializes the state of shared oneshot handlers
     * versus "desc->threads_onehsot |= action->thread_mask;" in
     * irq_wake_thread(). See the comment there which explains the
     * serialization.
     */
    if (unlikely(irqd_irq_inprogress(&desc->irq_data))) {-----------必须等待硬件中断处理程序清除IRQD_IRQ_INPROGRESS标志位,见handle_irq_event()。因为该标志位表示硬件中断处理程序正在处理硬件中断,直到硬件中断处理完毕才会清除该标志。
        raw_spin_unlock_irq(&desc->lock);
        chip_bus_sync_unlock(desc);
        cpu_relax();
        goto again;
    }

    /*
     * Now check again, whether the thread should run. Otherwise
     * we would clear the threads_oneshot bit of this thread which
     * was just set.
     */
    if (test_bit(IRQTF_RUNTHREAD, &action->thread_flags))
        goto out_unlock;

    desc->threads_oneshot &= ~action->thread_mask;

    if (!desc->threads_oneshot && !irqd_irq_disabled(&desc->irq_data) &&
        irqd_irq_masked(&desc->irq_data))
        unmask_threaded_irq(desc);----------------------------------执行EOI或者去中断屏蔽。

out_unlock:
    raw_spin_unlock_irq(&desc->lock);
    chip_bus_sync_unlock(desc);
}

至此一个中断的执行完毕。 

4.4 如何保证IRQS_ONESHOT不嵌套?

request_threaded_irq()申请中断时描述该中断的特性。

IRQS_*的中断标志位是位于struct irq_desc数据结构的istate成员,也即core_internal_state__do_not_mess_with_it

IRQD_*是struct irq_data数据结构中的state_use_accessors成员一组中断标志位,通常用于描述底层中断状态。

关于IRQF_ONESHOT特别解释:必须在硬件中断处理结束之后才能重新使能中断;线程化中断处理过程中保持中断线处于关闭状态,直到该中断线上所有thread_fn执行完毕。

#define IRQF_TRIGGER_NONE    0x00000000
#define IRQF_TRIGGER_RISING    0x00000001---------------------------上升沿触发
#define IRQF_TRIGGER_FALLING    0x00000002--------------------------下降沿触发
#define IRQF_TRIGGER_HIGH    0x00000004-----------------------------高电平触发
#define IRQF_TRIGGER_LOW    0x00000008------------------------------地电平触发
#define IRQF_TRIGGER_MASK    (IRQF_TRIGGER_HIGH | IRQF_TRIGGER_LOW | \
                 IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING)--------四种触发类型
#define IRQF_TRIGGER_PROBE    0x00000010

#define IRQF_SHARED        0x00000080-------------------------------多个设备共享一个中断号
#define IRQF_PROBE_SHARED    0x00000100-----------------------------中断处理程序允许sharing mismatch发生
#define __IRQF_TIMER        0x00000200------------------------------标记一个时钟中断
#define IRQF_PERCPU        0x00000400-------------------------------属于某个特定CPU的中断
#define IRQF_NOBALANCING    0x00000800------------------------------禁止在多CPU之间做中断均衡
#define IRQF_IRQPOLL        0x00001000------------------------------中断被用作轮询
#define IRQF_ONESHOT        0x00002000------------------------------一次性触发中断,不允许嵌套。
#define IRQF_NO_SUSPEND        0x00004000---------------------------在系统睡眠过程中不要关闭该中断
#define IRQF_FORCE_RESUME    0x00008000-----------------------------在系统唤醒过程中必须抢孩子打开该中断
#define IRQF_NO_THREAD        0x00010000----------------------------表示该中断不会给线程化
#define IRQF_EARLY_RESUME    0x00020000
#define IRQF_COND_SUSPEND    0x00040000

#define IRQF_TIMER        (__IRQF_TIMER | IRQF_NO_SUSPEND | IRQF_NO_THREAD)


enum {
    IRQS_AUTODETECT        = 0x00000001,-------------------处于自动侦测状态
    IRQS_SPURIOUS_DISABLED    = 0x00000002,----------------被视为“伪中断”并被禁用
    IRQS_POLL_INPROGRESS    = 0x00000008,------------------正处于轮询调用action
    IRQS_ONESHOT        = 0x00000020,----------------------表示只执行一次,由IRQF_ONESHOT转换而来,在中断线程化执行完成后需要小心对待,见irq_finalize_oneshot()。
    IRQS_REPLAY        = 0x00000040,-----------------------重新发送一次中断
    IRQS_WAITING        = 0x00000080,----------------------处于等待状态
    IRQS_PENDING        = 0x00000200,----------------------该中断被挂起
    IRQS_SUSPENDED        = 0x00000800,--------------------该中断被暂停
};


enum {
    IRQD_TRIGGER_MASK        = 0xf,-------------------------该中断触发类型
    IRQD_SETAFFINITY_PENDING    = (1 <<  8),
    IRQD_NO_BALANCING        = (1 << 10),
    IRQD_PER_CPU            = (1 << 11),
    IRQD_AFFINITY_SET        = (1 << 12),
    IRQD_LEVEL            = (1 << 13),
    IRQD_WAKEUP_STATE        = (1 << 14),
    IRQD_MOVE_PCNTXT        = (1 << 15),
    IRQD_IRQ_DISABLED        = (1 << 16),--------------------该中断处于关闭状态
    IRQD_IRQ_MASKED            = (1 << 17),------------------该中断被屏蔽中
    IRQD_IRQ_INPROGRESS        = (1 << 18),------------------该中断正在被处理中
    IRQD_WAKEUP_ARMED        = (1 << 19),
    IRQD_FORWARDED_TO_VCPU        = (1 << 20),
};

struct irqaction是每个中断的irqaction描述符。

struct irqaction {
    irq_handler_t        handler;-----------primary handler函数指针
    void            *dev_id;----------------传递给中断处理程序的参数
    void __percpu        *percpu_dev_id;
    struct irqaction    *next;
    irq_handler_t        thread_fn;---------中断线程处理程序的函数指针
    struct task_struct    *thread;----------中断线程的task_struct数据结构
    unsigned int        irq;----------------Linux软件中断号
    unsigned int        flags;--------------注册中断时用的中断标志位,IRQF_*。
    unsigned long        thread_flags;------中断线程相关标志位
    unsigned long        thread_mask;-------在共享中断中,每一个action有一个比特位来表示。
    const char        *name;----------------中断线程名称
    struct proc_dir_entry    *dir;
} ____cacheline_internodealigned_in_smp;

request_irq调用request_threaded_irq进行中断注册,只是少了一个thread_fn参数。这也是两则的区别所在,request_irq不能注册线程化中断。

irq:Linux软件中断号,不是硬件中断号。

handler:指primary handler,也即request_irq的中断处理函数handler。

thread_fn:中断线程化的处理函数。

irqflags:中断标志位,见IRQF_*解释。

devname:中断名称。

dev_id:传递给中断处理程序的参数。

handler和thread_fn分别被赋给action->handler和action->thread_fn,组合如下:

  handler thread_fn  
1 先执行handler,然后条件执行thread_fn。
2 × 等同于request_irq()
3 × handler=irq_default_primary_handler
4 × × 返回-EINVAL

很多request_threaded_irq()使用第3种组合,irq_default_primary_handler()返回IRQ_WAKE_THREAD,将工作交给thread_fn进行处理。

第2种组合相当于request_irq()。

第4种组合不被允许,因为中断得不到任何处理。

第1种组合较复杂,在handler根据实际情况返回IRQ_WAKE_THREAD(唤醒内核中断线程)或者IRQ_HANDLED(中断已经处理完毕,不需要唤醒中断内核线程)。

request_threaded_irq()对参数进行检查之后,分配struct irqaction并填充,然后将注册工作交给__setup_irq()。

static inline int __must_check
 request_threaded_irq(irq, handler, NULL, flags, name, dev);
}

 __setup_irq(irq, desc, action);
    chip_bus_sync_unlock(desc);

    if (retval)
        kfree(action);
...
    return retval;
}

 

 

 

5.3 __setup_irq

 

一张图

 __setup_irq()首先做参数检查,然后根据需要创建中断内核线程,这期间处理中断嵌套、oneshot、中断共享等问题。

还设置了中断触发类型设置,中断使能等工作。最后根据需要唤醒中断内核线程,并创建此中断相关sysfs节点。

/*
 * Internal function to register an irqaction - typically used to
 * allocate special interrupts that are part of the architecture.
 */
static int
gic_chipreturn -ENOSYS;
    if (!try_module_get(desc->owner))
        return -ENODEV;

    /*
     * Check whether the interrupt nests into another interrupt
     * thread.
     */
    nested = irq_settings_is_nested_thread(desc);-----------------对于设置了_IRQ_NESTED_THREAD嵌套类型的中断描述符,必须指定thread_fn。
    if (nested) {
        if (!new->thread_fn) {
            ret = -EINVAL;
            goto out_mput;
        }
        /*
         * Replace the primary handler which was provided from
         * the driver for non nested interrupt handling by the
         * dummy function which warns when called.
         */
        new->handler = irq_nested_primary_handler;
    } else {
        if (irq_settings_can_thread(desc))-----------------------判断该中断是否可以被线程化,如果没有设置_IRQ_NOTHREAD表示可以被强制线程化。
            irq_setup_forced_threading(new);
    }

    /*
     * Create a handler thread when a thread function is supplied
     * and the interrupt does not nest into another interrupt
     * thread.
     */
    if (new->thread_fn && !nested) {-----------------------------对不支持嵌套的线程化中断创建一个内核线程,实时SCHED_FIFO,优先级为50的实时线程。
        struct task_struct *t;
        static const struct sched_param param = {
            .sched_priority = MAX_USER_RT_PRIO/2,
        };

        t = kthread_create(irq_thread, new, "irq/%d-%s", irq,
                   new->name);-----------------------------------由irq、中断号、中断名组成的中断线程名,处理函数是irq_thread()。
        if (IS_ERR(t)) {
            ret = PTR_ERR(t);
            goto out_mput;
        }

        sched_setscheduler_nocheck(t, SCHED_FIFO, &param);

        get_task_struct(t);
        new->thread = t;

        set_bit(IRQTF_AFFINITY, &new->thread_flags);
    }

    if (!alloc_cpumask_var(&mask, GFP_KERNEL)) {
        ret = -ENOMEM;
        goto out_thread;
    }

    /*
     * Drivers are often written to work w/o knowledge about the
     * underlying irq chip implementation, so a request for a
     * threaded irq without a primary hard irq context handler
     * requires the ONESHOT flag to be set. Some irq chips like
     * MSI based interrupts are per se one shot safe. Check the
     * chip flags, so we can avoid the unmask dance at the end of
     * the threaded handler for those.
     */
    if (desc->irq_data.chip->flags & IRQCHIP_ONESHOT_SAFE)----------表示该中断控制器不支持中断嵌套,所以flags去掉IRQF_ONESHOT。
        new->flags &= ~IRQF_ONESHOT;

    raw_spin_lock_irqsave(&desc->lock, flags);
    old_ptr = &desc->action;
    old = *old_ptr;
    if (old) {-----------------------------------------------------old指向desc->action指向的链表,old不为空说明已经有中断添加到中断描述符irq_desc中,说明这是一个共享中断。shared=1。
...
        /* add new interrupt at end of irq queue */
        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);
        shared = 1;
    }

    /*
     * Setup the thread mask for this irqaction for ONESHOT. For
     * !ONESHOT irqs the thread mask is 0 so we can avoid a
     * conditional in irq_wake_thread().
     */
    if (new->flags & IRQF_ONESHOT) {
        /*
         * Unlikely to have 32 resp 64 irqs sharing one line,
         * but who knows.
         */
        if (thread_mask == ~0UL) {
            ret = -EBUSY;
            goto out_mask;
        }

        new->thread_mask = 1 << ffz(thread_mask);

    } else if (new->handler == irq_default_primary_handler &&---------非IRQF_ONESHOT类型中断,且handler使用默认irq_default_primary_handler(),如果中断触发类型是LEVEL,如果中断出发后不清中断容易引发中断风暴。提醒驱动开发者,没有primary handler且中断控制器不支持硬件oneshot,必须显式指定IRQF_ONESHOT表示位。
           !(desc->irq_data.chip->flags & IRQCHIP_ONESHOT_SAFE)) {

        pr_err("Threaded irq requested with handler=NULL and !ONESHOT for irq %d\n",
               irq);
        ret = -EINVAL;
        goto out_mask;
    }

    if (!shared) {-------------------------------------------------非共享中断情况
        ret = irq_request_resources(desc);
        if (ret) {
            pr_err("Failed to request resources for %s (irq %d) on irqchip %s\n",
                   new->name, irq, desc->irq_data.chip->name);
            goto out_mask;
        }

        init_waitqueue_head(&desc->wait_for_threads);

        /* Setup the type (level, edge polarity) if configured: */
        if (new->flags & IRQF_TRIGGER_MASK) {
            ret = __irq_set_trigger(desc, irq,-------------------调用gic_chip->irq_set_type设置中断触发类型。
                    new->flags & IRQF_TRIGGER_MASK);

            if (ret)
                goto out_mask;
        }

        desc->istate &= ~(IRQS_AUTODETECT | IRQS_SPURIOUS_DISABLED | \
                  IRQS_ONESHOT | IRQS_WAITING);
        irqd_clear(&desc->irq_data, IRQD_IRQ_INPROGRESS);---------清IRQD_IRQ_INPROGRESS标志位

        if (new->flags & IRQF_PERCPU) {
            irqd_set(&desc->irq_data, IRQD_PER_CPU);
            irq_settings_set_per_cpu(desc);
        }

        if (new->flags & IRQF_ONESHOT)
            desc->istate |= IRQS_ONESHOT;

        if (irq_settings_can_autoenable(desc))
            irq_startup(desc, true);
        else
            /* Undo nested disables: */
            desc->depth = 1;

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

        /* Set default affinity mask once everything is setup */
        setup_affinity(irq, desc, mask);

    } else if (new->flags & IRQF_TRIGGER_MASK) {
..
    }

    new->irq = irq;
    *old_ptr = new;

    irq_pm_install_action(desc, new);

    /* Reset broken irq detection when installing new handler */
    desc->irq_count = 0;
    desc->irqs_unhandled = 0;

    /*
     * Check whether we disabled the irq via the spurious handler
     * before. Reenable it and give it another chance.
     */
    if (shared && (desc->istate & IRQS_SPURIOUS_DISABLED)) {
        desc->istate &= ~IRQS_SPURIOUS_DISABLED;
        __enable_irq(desc, irq);
    }

    raw_spin_unlock_irqrestore(&desc->lock, flags);

    /*
     * Strictly no need to wake it up, but hung_task complains
     * when no hard interrupt wakes the thread up.
     */
    if (new->thread)
        wake_up_process(new->thread);------------------------------如果该中断被线程化,那么就唤醒该内核线程。这里每个中断对应一个线程。

    register_irq_proc(irq, desc);----------------------------------创建/proc/irq/xxx/目录及其节点。
    new->dir = NULL;
    register_handler_proc(irq, new);-------------------------------以action->name创建目录
    free_cpumask_var(mask);

    return 0;
...
}

 

irq_setup_forced_threading()判断是否强制当前中断线程化,然后对thread_flags置位IRQTF_FORCED_THREAD表示此中断被强制线程化。

将原来的primary handler弄到中断线程中去执行,原来的primary handler换成irq_default_primary_handler。

并设置secondary的primary handler指向irq_forced_secondary_handler(),原来的thread_fn移到secondary的中线程中执行。

static int irq_setup_forced_threading(struct irqaction *new)
{
    if (!force_irqthreads)---------------------------------------------如果内核启动参数包含threadirqs,则支持强制线程化。或者CONFIG_PREEMPT_RT_BASE实时补丁打开,这里也强制线程化。
        return 0;
    if (new->flags & (IRQF_NO_THREAD | IRQF_PERCPU | IRQF_ONESHOT))----和线程化矛盾的标志位。
        return 0;

    new->flags |= IRQF_ONESHOT;----------------------------------------强制线程化的中断都置位IRQF_ONESHOT。

    if (new->handler != irq_default_primary_handler && new->thread_fn) {
        /* Allocate the secondary action */
        new->secondary = kzalloc(sizeof(struct irqaction), GFP_KERNEL);
        if (!new->secondary)
            return -ENOMEM;
        new->secondary->handler = irq_forced_secondary_handler;
        new->secondary->thread_fn = new->thread_fn;
        new->secondary->dev_id = new->dev_id;
        new->secondary->irq = new->irq;
        new->secondary->name = new->name;
    }
    /* Deal with the primary handler */
    set_bit(IRQTF_FORCED_THREAD, &new->thread_flags);
    new->thread_fn = new->handler;
    new->handler = irq_default_primary_handler;
    return 0;
}

 

setup_irq()、request_threaded_irq()、request_irq()都是对__setup_irq()的包裹。

request_irq()调用request_threaded_irq(),只是少了thread_fn。

request_thraded_irq()和setup_irq()的区别在于,setup_irq()入参是struct irqaction ,而request_threaded_irq()在内部组装struct irqaction。

 

6. 一个中断的生命

经过上面的分析可以看出一个中断从产生、执行,到最终结束的流程。这里我们用树形代码路径来简要分析一下一个中断的生命周期。

vector_irq()->vector_irq()->__irq_svc()

  ->svc_entry()--------------------------------------------------------------------------保护中断现场

  ->irq_handler()->gic_handle_irq()------------------------------------------------具体到GIC中断控制器对应的就是gic_handle_irq(),此处从架构相关进入了GIC相关处理。

    ->GIC_CPU_INTACK--------------------------------------------------------------读取IAR寄存器,响应中断。

    ->handle_domain_irq()

      ->irq_enter()------------------------------------------------------------------------进入硬中断上下文

      ->generic_handle_irq()

        ->generic_handle_irq_desc()->handle_fasteoi_irq()--------------------根据中断号分辨不同类型的中断,对应不同处理函数,这里中断号取大于等于32。

          ->handle_irq_event()->handle_irq_event_percpu()

            ->action->handler()-----------------------------------------------------------对应到特定中断的处理函数,即上半部

              ->__irq_wake_thread()-----------------------------------------------------如果中断函数处理返回IRQ_WAKE_THREAD,则唤醒中断线程进行处理,但不是立即执行中断线程。

      ->irq_exit()---------------------------------------------------------------------------退出硬中断上下文。视情况处理软中断。

        ->invoke_softirq()-----------------------------------------------------------------处理软中断,超出一定条件任务就会交给软中断线程处理。

    ->GIC_CPU_EOI--------------------------------------------------------------------写EOI寄存器,表示结束中断。至此GIC才会接收新的硬件中断,此前一直是屏蔽硬件中断的。

  ->svc_exit-------------------------------------------------------------------------------恢复中断现场

 从上面的分析可以看出:

  • 中断上半部的处理是关硬件中断的,这里的关硬件中断是GIC就不接收中断处理。直到写EOI之后,GIC仲裁单元才会重新选择中断进行处理。
  • 软中断运行于软中断上下文中,但是仍然是关硬件中断的,这里需要特别注意,软中断需要快速处理并且不能睡眠。
  • 不是所有软中断都运行于软中断上下文中,部分软中断任务可能会交给ksoftirqd线程处理。
  • 包括IRQ_WAKE_THREAD、ksoftirqd、woker等唤醒线程的情况,都不会在中断上下文中进行处理。中断上下文中所做的处理只是唤醒,执行时机交给系统调度。
  • 如果要提高Linux实时性,有两个要点:一是将上半部线程化;另一个是将软中断都交给ksoftirqd线程处理。

相关