Chinaunix首页 | 论坛 | 博客
  • 博客访问: 162815
  • 博文数量: 84
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 1
  • 用 户 组: 普通用户
  • 注册时间: 2014-03-09 10:55
文章分类
文章存档

2014年(84)

我的朋友

分类: LINUX

2014-05-15 15:33:45

摘要:本文主要讲述linux如何处理ARM cortex A9多核处理器的中断处理过程的C函数部分。主要是在中断上下文和线程上下文处理ISR的过程。

 

法律声明LINUX3.0内核源代码分析》系列文章由谢宝友()发表于http://xiebaoyou.blog.chinaunix.net,文章中的LINUX3.0源代码遵循GPL协议。除此以外,文档中的其他内容由作者保留所有版权。谢绝转载。

 

本连载文章并不是为了形成一本适合出版的书籍,而是为了向有一定内核基本的读者提供一些linux3.0源码分析。因此,请读者结合《深入理解LINUX内核》第三版阅读本连载。

 

1.1.1      中断处理(C函数)
1.1.1.1         一般中断的处理

大多数中断是由asm_do_IRQ函数处理:

/**

 * 我们暂且可以认为,绝大部分中断是从汇编跳到本函数处理的。当然,IPIlocal_timer不是。

 *                 irq:            产生中断的外部中断号。

 *                  regs:                  被中断打断的寄存器现场。

 */

asmlinkage void __exception_irq_entry

asm_do_IRQ(unsigned int irq, struct pt_regs *regs)

{

    /**

     * 将当前正在处理的中断现场保存到每CPU变量__irq_regs中去。

     * 这样做的目的,是为了在其他代码中,直接读取__irq_regs中的值,找到中断前的现场。

     * 而不用将regs参数层层传递下去。

     */

    struct pt_regs *old_regs = set_irq_regs(regs);

 

    /**

     * 调用irq_enter表示进入中断处理过程。该函数进行如下处理:

     *              1rcu模块记录内部计数,表示当前退出了NOHZ状态。关于rcu,需要整整一本书来描述,请从下载<深入理解并行编程>了解更多内容。

     *              2、处理NOHZ事件。

     *              3、将当前任务的抢占计数中的硬中断计数加1.该计数表示当前中断嵌套层次。

     *              4、调试信息,表示当前已经进入中断的事实。

     */

    irq_enter();

 

    /*

     * Some hardware gives randomly wrong interrupts.  Rather

     * than crashing, do something sensible.

     */

    if (unlikely(irq >= nr_irqs)) {/* 中断号错误,超过最大中断号,这会发生吗? */

             if (printk_ratelimit())/* 调用printk_ratelimit,控制在一定时间内,不重复打印警告信息,防止打印风暴将系统阻塞 */

                       printk(KERN_WARNING "Bad IRQ%u\n", irq);

             ack_bad_irq(irq);/* 错误的中断号并不需要应答,只需要对错误中断进行计数。 */

    } else {

             generic_handle_irq(irq);/* 这里进行正常的中断处理 */

    }

 

    /* AT91 specific workaround */

    irq_finish(irq);/* 这是提供给各个体系结构自行实现的回调钩子,一般没有 */

 

    /**

     * 中断退出过程,主要处理以下内容:

     *              1、调试钩子,记录退出中断的事实。

     *              2、在任务的抢占计数字段中,递减中断计数

     *              3、处理软中断

     *              4、调用rcu模块的函数,表示已经退出中断。

     */

    irq_exit();

    /**

     * 恢复__irq_regsCPU变量的内容。

     */

    set_irq_regs(old_regs);

}

 

一般情况下,会调用generic_handle_irq来处理中断,这个函数是对generic_handle_irq_desc简单封装:

/**

 * 处理特定的中断。

 *                  irq:   要处理的中断号。

 */

int generic_handle_irq(unsigned int irq)

{

    /**

     * 根据中断号,取得中断描述符。

     */

    struct irq_desc *desc = irq_to_desc(irq);

 

    /**

     * 如果中断描述符为空,说明irq编号过大,或者对某些特定体系结构来说,该中断还不被支持。直接返回即可。

     */

    if (!desc)

             return -EINVAL;

    /**

     * 根据中断描述符中的信息,得到该中断ISR,并处理中断。

     */

    generic_handle_irq_desc(irq, desc);

    return 0;

}

 

generic_handle_irq_desc调用中断描述符的handle_irq回调函数。对于不同的中断类型,handle_irq回调函数可能是handle_simple_irqhandle_level_irqhandle_fasteoi_irqhandle_edge_irqhandle_edge_eoi_irqhandle_percpu_irq。这里我们以handle_level_irq为例解释中断处理流程。

/**

 * 处理电平触发类型的中断

 *                  irq:            中断号

 *                  desc:                  中断描述符

 */

void

handle_level_irq(unsigned int irq, struct irq_desc *desc)

{

    /**

     * 这里锁住描述符的自旋锁,有两个原因:

     *              其他核可能同时收到了中断,将所有同一中断号的中断交给同一个CPU处理,可以避免ISR中做复杂的同步。这个原因是由于unix系统历史原因造成的。

     *              其他核可能在调用request_irq等函数注册ISR,需要使用该锁保护desc中的数据不被破坏。

     * 注意:这里使用的是raw_spin_lock而不是spin_lock,因为实时内核中,spin_lock已经可以睡眠了。而目前处于硬中断中,不能睡眠。

     */

    raw_spin_lock(&desc->lock);

    /**

     * 应答中断,并同时屏障该中断源。

     */

    mask_ack_irq(desc);

 

    /**

     * 如果其他核正在处理该中断,则退出。

     * 虽然本函数处于中断描述符的lock锁保护之中,但是handle_irq_event函数在调用ISR时,会将锁打开。

     * 也就是说,其他核在处理ISR时,本核可能进入锁保护的代码中来。

     */

    if (unlikely(irqd_irq_inprogress(&desc->irq_data)))

             if (!irq_check_poll(desc))

                       goto out_unlock;

 

    /**

     * IRQS_REPLAY标志是为了挽救丢失的中断。这个几乎不会碰上,暂时不深入分析这个标志。运行到此,中断已经在处理了,就不必考虑挽救丢失的中断了。

     * IRQS_WAITING标志表示初始化进程正在等待中断的到来,当探测一些老式设备时,驱动用此方法确定硬件产生的中断号。

     * 比如一些老式的鼠标、键盘、ISA设备需要这么做。它们不是PCI设备,必须用中断探测的方法确定其中断号。

     * 当然,运行到这里,可以将IRQS_WAITING标志去除了,以通知初始化函数,相应的中断号已经触发。

     */

    desc->istate &= ~(IRQS_REPLAY | IRQS_WAITING);

    /**

     * 记录下中断在本CPU上触发的次数、本CPU总中断次数。在/proc中要用到这些统计值。

     */

    kstat_incr_irqs_this_cpu(irq, desc);

 

    /*

     * If its disabled or no action available

     * keep it masked and get out of here

     */

    /**

     * action是中断描述符表的头指针,如果为空则说明本中断没有挂接处理函数。

     * irqd_irq_disabled表示相应的中断已经被屏蔽,也退出。

     */

    if (unlikely(!desc->action || irqd_irq_disabled(&desc->irq_data)))

             goto out_unlock;

 

    /**

     * handle_irq_event要么是在中断上下文调用ISR,要么是唤醒处理线程处理中断。

     * 注意:这个函数会临时打开中断描述符的自旋锁。

     */

    handle_irq_event(desc);

 

    /**

     * 如果中断没有被屏障,并且不是一次性的中断,那么需要将该中断线重新打开。

     */

    if (!irqd_irq_disabled(&desc->irq_data) && !(desc->istate & IRQS_ONESHOT))

             unmask_irq(desc);

out_unlock:

    /**

     * 释放自旋锁。

     */

    raw_spin_unlock(&desc->lock);

}

 

 

 

handle_irq_event如下所示:

/**

 * 处理ISR

 */

irqreturn_t handle_irq_event(struct irq_desc *desc)

{

    struct irqaction *action = desc->action;

    irqreturn_t ret;

 

    /**

     * 清除挂起标志。当本核正在调用ISR的过程中,如果发生了同样的中断,那么其他核在收到中断时,会发现本核将IRQD_IRQ_INPROGRESS设置到描述符中。

     * 那么其他核会设置IRQS_PENDING并退出。本核在处理完ISR后,会判断此标志并重新执行ISR,在重新执行ISR前,应当将IRQS_PENDING标志清除。

     */

    desc->istate &= ~IRQS_PENDING;

    /**

     * 在释放自旋锁前,设置IRQD_IRQ_INPROGRESS标志,表示本核正在处理该中断。其他核不应当再处理同样的中断。

     */

    irqd_set(&desc->irq_data, IRQD_IRQ_INPROGRESS);

    /**

     * 在开始调用ISR前,释放自旋锁,避免其他核被长时间挂起。

     */

    raw_spin_unlock(&desc->lock);

 

    /**

     * handle_irq_event_percpu会遍历中断描述符中的ISR链表,并回调ISR

     */

    ret = handle_irq_event_percpu(desc, action);

 

    /**

     * 重新获得自旋锁,以维持中断描述符中的标志。

     */

    raw_spin_lock(&desc->lock);

    /**

     * 清除IRQD_IRQ_INPROGRESS标志。此时其他核如果产生同样的中断,则将中断交给其他核处理。

     * 如果在处理中断期间,其他核产生了同样的中断,并设置了IRQS_PENDING,则由上层函数继续循环处理中断。

     */

    irqd_clear(&desc->irq_data, IRQD_IRQ_INPROGRESS);

    return ret;

}

 

/**

 * 唤醒中断处理线程或者在中断上下文处理中断

 *                  desc:         中断描述符

 *                  action:      ISR链表头。

 */

irqreturn_t

handle_irq_event_percpu(struct irq_desc *desc, struct irqaction *action)

{

    /**

     * retval表示中断处理结果,默认设置为IRQ_NONE表示该中断没有被ISR响应。

     */

    irqreturn_t retval = IRQ_NONE;

    unsigned int random = 0, irq = desc->irq_data.irq;

 

    /**

     * 这里的循环是遍历ISR链表,循环调用ISR处理函数。

     */

    do {

             irqreturn_t res;

 

             /**

              * trace_irq_handler_entrytrace_irq_handler_exit这两个调试函数应当是内核设计者对驱动开发者不太放心。

              * 因此在这些跟踪所有驱动注册的ISR

              */

             trace_irq_handler_entry(irq, action);

             /**

              * 这里调用驱动注册的ISR对中断进行处理。

              */

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

             trace_irq_handler_exit(irq, action, res);

 

             /**

              * 老版本的内核会根据ISR注册时的标志,在中断处理函数中将中断打开或者关闭。

              * 新版本内核应当是完全实现了中断线程化,长时间运行的中断ISR放到线程中去了,也就是说,在中断上下文都应当是关中断运行。

              * 这里的警告应当是找出那些不符合新版本要求的ISR,在这里打印警告,并强制将中断关闭。

              */

             if (WARN_ONCE(!irqs_disabled(),"irq %u handler %pF enabled interrupts\n",

                             irq, action->handler))

                       local_irq_disable();

 

             switch (res) {/* 根据ISR的返回值决定下一步操作。主要是处理中断线程化。 */

             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;

                       }

 

                       /**

                        * 唤醒本ISR对应的中断线程,由线程执行真正的处理函数。

                        */

                       irq_wake_thread(desc, action);

 

                       /* Fall through to add to randomness */

             case IRQ_HANDLED:/* 已经在中断上下文中处理了ISR */

                       random |= action->flags;/* 将所有ISR标志取或,只要其中一个ISRIRQF_SAMPLE_RANDOM标志,就将本中断作为一个中断源 */

                       break;

 

             default:

                       break;

             }

 

             /**

              * 将所有ISR的返回值取或,那么,只要有一个返回了IRQ_HANDLED,上层都会认为中断得到了正确的处理。

              */

             retval |= res;

             /**

              * 处理下一个ISR.

              */

             action = action->next;

    } while (action);

 

    /**

     * 如果本中断是一个随机源,则处理随机种子。

     */

    if (random & IRQF_SAMPLE_RANDOM)

             add_interrupt_randomness(irq);

 

    /**

     * 在旧版本的内核中,如果多次产生中断而且没有ISR对中断进行响应,那么就会认为是有问题主板并禁用该中断。

     * 新版本内核允许通过调试标志来关闭这个功能。

     * 如果打开了这个功能,则进行中断统计,并在合适的时候关闭该中断。

     */

    if (!noirqdebug)

             note_interrupt(irq, desc, retval);

    return retval;

}

 

1.1.1.1         中断线程化

中断线程化功能已经从实时补丁合入到主流内核中。在linux3.0中,驱动可以将ISR注册为在中断上下文运行,或者在线程上下文中运行。

执行ISR的线程主函数是irq_thread

/**

 * 线程化中断主处理函数

 *                  data:         线程参数,即ISR描述符

 */

static int irq_thread(void *data)

{

    /**

     * 线程优先级,默认为50.

     */

    static const struct sched_param param = {

             .sched_priority = MAX_USER_RT_PRIO/2,

    };

    struct irqaction *action = data;

    struct irq_desc *desc = irq_to_desc(action->irq);

    irqreturn_t (*handler_fn)(struct irq_desc *desc,

                       struct irqaction *action);

    int wake;

 

    if (force_irqthreads & test_bit(IRQTF_FORCED_THREAD,

                                         &action->thread_flags))

             handler_fn = irq_forced_thread_fn;

    else

             handler_fn = irq_thread_fn;

 

    sched_setscheduler(current, SCHED_FIFO, ¶m);

    current->irqaction = action;

 

    /**

     * 等待中断将本线程唤醒。或者线程被中止则退出。

     */

    while (!irq_wait_for_interrupt(action)) {

 

             /**

              * 检查中断亲和性是否被重新设置,如果设置了,则将线程迁移到相应的CPU上。

              */

             irq_thread_check_affinity(desc, action);

 

             /**

              * 记录活动中断线程个数。

              */

             atomic_inc(&desc->threads_active);

 

             /**

              * 获取中断描述符的自旋锁。并关中断。这是因为我们将要判断中断标志,这些标志在其他核或者中断中都可能被修改。

              */

             raw_spin_lock_irq(&desc->lock);

             if (unlikely(irqd_irq_disabled(&desc->irq_data))) {/* 中断被禁止 */

                       /*

                        * CHECKME: We might need a dedicated

                        * IRQ_THREAD_PENDING flag here, which

                        * retriggers the thread in check_irq_resend()

                        * but AFAICT IRQS_PENDING should be fine as it

                        * retriggers the interrupt itself --- tglx

                        */

                       desc->istate |= IRQS_PENDING;/* 此时必须退出,但是确实产生了中断,导致中断丢失,这里设置挂起标志,待下次处理。 */

                       raw_spin_unlock_irq(&desc->lock);

             } else {

                       irqreturn_t action_ret;

 

                       raw_spin_unlock_irq(&desc->lock);

                       /**

                        * 在线程上下文调用ISR回调函数。

                        */

                       action_ret = handler_fn(desc, action);

                       /**

                        * 这里是检测中断线是否有问题。如果遇到有问题的主板,这里禁止相应的中断线。

                        */

                       if (!noirqdebug)

                                note_interrupt(action->irq, desc, action_ret);

             }

 

             /**

              * 递减活动线程计数。如果已经没有活动线程,则设置waketrue

              */

             wake = atomic_dec_and_test(&desc->threads_active);

 

             /**

              * 已经没有活动线程了,并且有线程正在等待IRQ处理结束,则唤醒等待的线程,告知它所有中断都已经处理完毕。

              */

             if (wake && waitqueue_active(&desc->wait_for_threads))

                       wake_up(&desc->wait_for_threads);

    }

 

    /* Prevent a stale desc->threads_oneshot */

    irq_finalize_oneshot(desc, action, true);

 

    /*

     * Clear irqaction. Otherwise exit_irq_thread() would make

     * fuzz about an active irq thread going into nirvana.

     */

    current->irqaction = NULL;

    return 0;

}

 

阅读(637) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~