Linux中断处理流程

1. 中断处理流程

  当中断发生时,Linux系统会跳转到asm_do_IRQ()函数(所有中断程序的总入口函数),并且把终端号irq传进来。根据中断号,找到irq_desc结构(一个中断的描述结构),然后调用irq_desc中的handle_irq函数,即子中断入口函数。我们编写中断的驱动,即填充并注册irq_desc结构。

2. 中断处理数据结构:irq_desc

  Linux内核将所有的中断统一编号,使用一个irq_desc[NR_IRQS]的结构体数组来描述这些中断:每个数组项对应着一个中断源(也可能是一组中断源),记录中断入口函数、中断标记,并提供了中断的底层硬件访问函数(中断清除、屏蔽、使能)。另外通过这个结构体数组项中的action,能够找到用户注册的中断处理函数。

struct irq_desc {
    unsigned int        irq;
    irq_flow_handler_t    handle_irq;
    struct irq_chip        *chip;
    struct msi_desc        *msi_desc;
    void            *handler_data;
    void            *chip_data;
    struct irqaction    *action;    /* IRQ action list */
    unsigned int        status;        /* IRQ status */

    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;
    spinlock_t        lock;
    const char        *name;
} ____cacheline_internodealigned_in_smp;

(1)handle_irq:中断的入口函数

(2)chip:包含这个中断的清除、屏蔽、使能等底层函数

struct irq_chip {
    const char    *name;
    unsigned int    (*startup)(unsigned int irq);
    void        (*shutdown)(unsigned int irq);
    void        (*enable)(unsigned int irq);
    void        (*disable)(unsigned int irq);

    void        (*ack)(unsigned int irq);
    void        (*mask)(unsigned int irq);
    void        (*mask_ack)(unsigned int irq);
    void        (*unmask)(unsigned int irq);
    void        (*eoi)(unsigned int irq);

    void        (*end)(unsigned int irq);
    void        (*set_affinity)(unsigned int irq,
                    const struct cpumask *dest);
    int        (*retrigger)(unsigned int irq);
    int        (*set_type)(unsigned int irq, unsigned int flow_type);
    int        (*set_wake)(unsigned int irq, unsigned int on);

    /* Currently used only by UML, might disappear one day.*/
#ifdef CONFIG_IRQ_RELEASE_METHOD
    void        (*release)(unsigned int irq, void *dev_id);
#endif
    /*
     * For compatibility, ->typename is copied into ->name.
     * Will disappear.
     */
    const char    *typename;
};

(3)action:记录用户注册的中断处理函数、中断标志等内容

struct irqaction {
    irq_handler_t handler;
    unsigned long flags;
    cpumask_t mask;
    const char *name;
    void *dev_id;
    struct irqaction *next;
    int irq;
    struct proc_dir_entry *dir;
};

3. 中断处理流程总结

(1) 发生中断后,CPU执行异常向量vector_irq的代码;

(2)在vector_irq里面,最终会调用中断处理C程序总入口函数asm_do_IRQ();

(3)asm_do_IRQ()根据中断号调用irq_des[NR_IRQS]数组中的对应数组项中的handle_irq();

(4)handle_irq()会使用chip的成员函数来设置硬件,例如清除中断,禁止中断,重新开启中断等;

(5)handle_irq逐个调用用户在action链表中注册的处理函数。

  可见,中断体系结构的初始化,就是构造irq_desc[NR_IRQS]这个数据结构;用户注册中断就是构造action链表;用户卸载中断就是从action链表中去除对应的项。

4. Linux操作系统中断初始化

(1)init_IRQ()函数用来初始化中断体系结构,代码位于arch/arm/kernel/irq.c

void __init init_IRQ(void)
{
    int irq;

    for (irq = 0; irq < NR_IRQS; irq++)
        irq_desc[irq].status |= IRQ_NOREQUEST | IRQ_NOPROBE;

#ifdef CONFIG_SMP
    bad_irq_desc.affinity = CPU_MASK_ALL;
    bad_irq_desc.cpu = smp_processor_id();
#endif
    init_arch_irq();
}

(2)init_arch_irq()函数,就是用来初始化irq_desc[NR_IRQS]的,与硬件平台紧密相关。init_arch_irq其实是一个函数指针,我们移植Linux内核时,以S3C2440平台为例,把init_arch_irq指向函数s3c24xx_init_irq()。
(3)s3c24xx_init_irq()函数在arch/arm/plat-s3c24xx/irq.c中定义,它为所有的中断设置了芯片相关的数据结构irq_desc[irq].chip,设置了处理函数入口irq_desc[irq].handle_irq。

(4)以外部中断EINT0为例:

for (irqno = IRQ_EINT0; irqno <= IRQ_EINT3; irqno++) {
    irqdbf("registering irq %d (ext int)\n", irqno);
    set_irq_chip(irqno, &s3c_irq_eint0t4);
    set_irq_handler(irqno, handle_edge_irq);
    set_irq_flags(irqno, IRQF_VALID);
}

① set_irq_chip()的作用就是"irq_desc[irqno].chip = &s3c_irq_eint0t4",s3c_irq_eint0t4为系统提供了一套操作EINT0~EINT4的中断底层函数集,内容如下

static struct irq_chip s3c_irq_eint0t4 = {
    .name        = "s3c-ext0",
    .ack        = s3c_irq_ack,
    .mask        = s3c_irq_mask,
    .unmask        = s3c_irq_unmask,
    .set_wake    = s3c_irq_wake,
    .set_type    = s3c_irqext_type,
};

② set_irq_handler()函数的作用就是“irq_desc[irqno].handle_irq = handle_edge_irq”。发生中断后,do_asm_irq()函数会调用中断入口函数handle_edge_irq(),而handle_edge_irq()函数会调用用户注册的处理函数(即irq_desc[irqno].action)。

5. 用户注册中断时带来的中断初始化

(1)用户(驱动程序)通过request_irq()函数向内核注册中断处理函数,request_irq()函数根据中断号找到数组irq_desc[irqno]对应的数组项,然后在它的action链表中添加一个action表项。该函数定义于:kernel/irq/manage.c,内容如下

int request_irq(unsigned int irq, irq_handler_t handler,
        unsigned long irqflags, const char *devname, void *dev_id)
{
    struct irqaction *action;
    struct irq_desc *desc;
    int retval;

    /*
     * handle_IRQ_event() always ignores IRQF_DISABLED except for
     * the _first_ irqaction (sigh).  That can cause oopsing, but
     * the behavior is classified as "will not fix" so we need to
     * start nudging drivers away from using that idiom.
     */
    if ((irqflags & (IRQF_SHARED|IRQF_DISABLED))
            == (IRQF_SHARED|IRQF_DISABLED))
        pr_warning("IRQ %d/%s: IRQF_DISABLED is not "
                "guaranteed on shared IRQs\n",
                irq, devname);

#ifdef CONFIG_LOCKDEP
    /*
     * Lockdep wants atomic interrupt handlers:
     */
    irqflags |= IRQF_DISABLED;
#endif
    /*
     * Sanity-check: shared interrupts must pass in a real dev-ID,
     * otherwise we‘ll have trouble later trying to figure out
     * which interrupt is which (messes up the interrupt freeing
     * logic etc).
     */
    if ((irqflags & IRQF_SHARED) && !dev_id)
        return -EINVAL;

    desc = irq_to_desc(irq);
    if (!desc)
        return -EINVAL;

    if (desc->status & IRQ_NOREQUEST)
        return -EINVAL;
    if (!handler)
        return -EINVAL;

    action = kmalloc(sizeof(struct irqaction), GFP_ATOMIC);
    if (!action)
        return -ENOMEM;

    action->handler = handler;
    action->flags = irqflags;
    cpus_clear(action->mask);
    action->name = devname;
    action->next = NULL;
    action->dev_id = dev_id;

    retval = __setup_irq(irq, desc, action);
    if (retval)
        kfree(action);

#ifdef CONFIG_DEBUG_SHIRQ
    if (irqflags & IRQF_SHARED) {
        /*
         * It‘s a shared IRQ -- the driver ought to be prepared for it
         * to happen immediately, so let‘s make sure....
         * We disable the irq to make sure that a ‘real‘ IRQ doesn‘t
         * run in parallel with our fake.
         */
        unsigned long flags;

        disable_irq(irq);
        local_irq_save(flags);

        handler(irq, dev_id);

        local_irq_restore(flags);
        enable_irq(irq);
    }
#endif
    return retval;
}

(2)  request_irq()函数首先使用4个参数构造一个irqaction结构,然后调用setup_irq()函数将它链入链表中,代码如下:

int setup_irq(unsigned int irq, struct irqaction *new)
{
    /* 判断是否没有注册过,如果已经注册了就判断是否是可共享的中断 */
    p = &desc->action;
    old = *p;
    if (old) {
        if (!((old->flags & new->flags) & IRQF_SHARED) ||
            ((old->flags ^ new->flags) & IRQF_TRIGGER_MASK)) {
            old_name = old->name;
            goto mismatch;
        }

        /* add new interrupt at end of irq queue */
        do {
            p = &old->next;
            old = *p;
        } while (old);
        shared = 1;
    }

    /* 链入新表项 */
    *p = new;

    /* 如果在链入之前不是空链,那么之前的共享中断已经设置了中断触发方式,没有必要重复设置 */
    /* 如果链入之前是空链,那么就需要设置中断触发方式 */
    if (!shared) {
        irq_chip_set_defaults(desc->chip);

        /* Setup the type (level, edge polarity) if configured: */
        if (new->flags & IRQF_TRIGGER_MASK) {
            if (desc->chip && desc->chip->set_type)
                desc->chip->set_type(irq,
                        new->flags & IRQF_TRIGGER_MASK);
            else
                printk(KERN_WARNING "No IRQF_TRIGGER set_type "
                       "function for IRQ %d (%s)\n", irq,
                       desc->chip ? desc->chip->name :
                       "unknown");
        } else
            compat_irq_chip_set_default_handler(desc);

        desc->status &= ~(IRQ_AUTODETECT | IRQ_WAITING |
                  IRQ_INPROGRESS);

        if (!(desc->status & IRQ_NOAUTOEN)) {
            desc->depth = 0;
            desc->status &= ~IRQ_DISABLED;
            /* 启动中断 */
            if (desc->chip->startup)
                desc->chip->startup(irq);
            else
                desc->chip->enable(irq);
        } else
            /* Undo nested disables: */
            desc->depth = 1;
    }
    /* Reset broken irq detection when installing new handler */
    desc->irq_count = 0;
    desc->irqs_unhandled = 0;

    new->irq = irq;
    register_irq_proc(irq);
    new->dir = NULL;
    register_handler_proc(irq, new);
}

(3) setup_irq()函数主要完成功能如下

① 将新建的irqaciton结构链入irq_desc[irq]结构体的action链表中

  * 如果action链表为空,则直接链入

  * 如果非空,则要判断新建的irqaciton结构和链表中的irqaciton结构所表示的中断类型是否一致:即是都声明为“可共享的”,是否都是用相同的触发方式,如果一致,则将新建的irqaciton结构链入

② 设置中断的触发方式;

③ 启动中断

6. 卸载中断

  卸载中断使用函数free_irq()函数,该函数定义在kernel/irq/manage.c中,需要用到的两个参数irq、dev_id。通过参数irq可以定位到action链表,再使用dev_id在链表中找到要卸载的表项(共享中断的情况)。如果它是唯一表项,那么删除中断,还需要调用irq_desc[irq].chip->shutdown()或者irq_desc[irq].chip->disable()来关闭中断

void free_irq(unsigned int irq, void *dev_id)
{
    struct irq_desc *desc = irq_to_desc(irq);
    struct irqaction **p;
    unsigned long flags;

    WARN_ON(in_interrupt());

    if (!desc)
        return;

    spin_lock_irqsave(&desc->lock, flags);
    p = &desc->action;
    for (;;) {
        struct irqaction *action = *p;

        if (action) {
            struct irqaction **pp = p;

            p = &action->next;
            if (action->dev_id != dev_id)
                continue;

            /* Found it - now remove it from the list of entries */
            *pp = action->next;

            /* Currently used only by UML, might disappear one day.*/
#ifdef CONFIG_IRQ_RELEASE_METHOD
            if (desc->chip->release)
                desc->chip->release(irq, dev_id);
#endif

            if (!desc->action) {
                desc->status |= IRQ_DISABLED;
                if (desc->chip->shutdown)
                    desc->chip->shutdown(irq);
                else
                    desc->chip->disable(irq);
            }
            spin_unlock_irqrestore(&desc->lock, flags);
            unregister_handler_proc(irq, action);

            /* Make sure it‘s not being used on another CPU */
            synchronize_irq(irq);
#ifdef CONFIG_DEBUG_SHIRQ
            /*
             * It‘s a shared IRQ -- the driver ought to be
             * prepared for it to happen even now it‘s
             * being freed, so let‘s make sure....  We do
             * this after actually deregistering it, to
             * make sure that a ‘real‘ IRQ doesn‘t run in
             * parallel with our fake
             */
            if (action->flags & IRQF_SHARED) {
                local_irq_save(flags);
                action->handler(irq, dev_id);
                local_irq_restore(flags);
            }
#endif
            kfree(action);
            return;
        }
        printk(KERN_ERR "Trying to free already-free IRQ %d\n", irq);
#ifdef CONFIG_DEBUG_SHIRQ
        dump_stack();
#endif
        spin_unlock_irqrestore(&desc->lock, flags);
        return;
    }
}

原文地址:https://www.cnblogs.com/wulei0630/p/9502143.html

时间: 2024-10-18 00:03:58

Linux中断处理流程的相关文章

嵌入式Linux裸机开发(八)——S5PV210中断处理流程

嵌入式Linux裸机开发(八)--S5PV210中断处理流程 中断是一个过程,是CPU在执行当前程序的过程中因硬件或软件的原因插入了另一段程序运行的过程.因硬件原因引起的中断过程的出现是不可预测的,即随机的,而软中断是事先安排的.引起中断的信号源称之为中断源.根据中断源的来源将中断分为外部中断和内部中断.中断源来自SoC内部(内部外设)的中断称为内部中断.中断源来自SoC外部,通过中断对应的GPIO引脚产生的中断称为外部中断.中断有四种状态: Inactive(不激活):没有被激活或挂起的中断 

linux中断流程详解

异常体系比较复杂,但是linux已经准备了很多的函数和框架,但是因为中断是和具体的开发板相关,所以中断需要我们自己来处理一些方面,但是这也是很少的一部分,很多公用的处理函数内核已经实现,linux内核搭建了一个非常容易扩充的中断处理体系. 中 断系统结构涉及的方面很多,而且分布在很多的函数中,这里我主要理清一些结构和流程顺序已经在哪些函数中实现,我不知道其他人怎么样?但是我自己一开始怎 是找不到linux内核是怎么把GPIO设置成中断的,我找了很久都找不到,还有我们很多的设置,初始化等等东西好像

Exynos4412 中断处理流程详解

Linux 中,当外设触发中断后,大体处理流程如下: a -- 具体CPU architecture相关的模块会进行现场保护,然后调用machine driver对应的中断处理handler: b -- machine driver对应的中断处理handler中会根据硬件的信息获取HW interrupt ID,并且通过irq domain模块翻译成IRQ number: c --  调用该IRQ number 对应的high level irq event handler,在这个high le

Linux系统启动流程分析与关机流程

Linux 系统启动流程分析 Linux系统的启动过程并不是大家想象中的那么复杂,其过程可以分为5个阶段: 内核的引导. 运行 init. 系统初始化. 建立终端. 用户登录系统. init程序的类型: SysV: init, CentOS 5之前, 配置文件: /etc/inittab. Upstart: init,CentOS 6, 配置文件: /etc/inittab, /etc/init/*.conf. Systemd: systemd, CentOS 7,配置文件: /usr/lib/

红帽系Linux系统安装流程及自动化安装

本文以Centos6.9系统为例,讲解红帽系linux系统启动流程.以便于理解系统安装原理并能自定义系统安装配置完成自动安装. 目录: 一.系统启动基本流程 二.光盘镜像方式安装(U盘同理) 1.配置文件概述 2.光盘安装系统过程 三.自动化安装 1.anaconda配置方式 2.kickstart配置文件格式 3.自动化安装示例 四.网络自动化安装 一.系统启动的基本流程 系统安装与平时系统的启动流程相似,首先让我们简要了解下: 1.BIOS程序 ①POST开机自检 ②列出可以启动的设备顺序,

记录Linux启动流程的工具bootchart

/*********************************************************************  * Author  : Samson  * Date    : 04/28/2014  * Test platform:  *              3.11.0-12-generic #19-Ubuntu  *              GNU bash, version 4.2.45  * ****************************

Linux系统启动流程与内管管理(上)

在讲linux系统启动流程之前,来讲讲linux的组成,这样能能帮助我们深入了解系统的启动流程,废话不多说直接上系统启动流程图 linux组成 linux:kernel+rootfs kenrel的作用:进程管理.内存管理.网络管理.驱动程序.文件系统.安全管理等 rootfs:程序和glibc 库:函数结合,function,调用接口(头文件负责传输) 过程调用:procedure,无返回值 函数调用:function 程序:二进制执行文件 内核设计流派 单内核:把所有功能集成与一个程序 如:

Linux系统管理(二):Linux系统启动流程

Linux系统启动流程 从11月底报名马哥的Linux培训班,12月初真正开始学习,已经过去一个多月的时间了,从学习Linux的发展史开始.跟着学习命令行.文件系统挂载与管理.目录.Bash Shell的基本特性等等,Linux真的是博大精深,今天以CentOS 5,CentOS 6的启动过程进行一下简单的总结.由于才疏学浅,知识所限,有些地方分析不妥之处还请各位高手不吝赐教. 系统启动过程主要经过以下几个流程: POST>BootSequence(BIOS)>Bootloader(MBR)&

linux shell 流程控制

linux shell 流程控制 导读 和Java.PHP等语言不一样,linux shell有一套自己的流程控制语句,其中包括条件语句(if),循环语句(for,while),选择语句(case).下面我将通过例子介绍下,各个语句使用方法. 一.shell条件语句(if用法) if语句结构[if/then/elif/else/fi] if 条件测试语句 then action [elif 条件 action else action ] fi shell命令,可以按照分号分割,也可以按照换行符分