您的位置:首页 > 运维架构 > Linux

《LINUX3.0内核源代码分析》第二章:中断和异常(2)

2013-07-10 14:46 232 查看
原文地址:《LINUX3.0内核源代码分析》第二章:中断和异常(2) 作者:xiebaoyou

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

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

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

1.1.1 中断处理(C函数)
1.1.1.1 一般中断的处理
大多数中断是由asm_do_IRQ函数处理:

/**

* 我们暂且可以认为,绝大部分中断是从汇编跳到本函数处理的。当然,IPI和local_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表示进入中断处理过程。该函数进行如下处理:

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

* 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_regs每CPU变量的内容。

*/

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_irq、handle_level_irq、handle_fasteoi_irq、handle_edge_irq、handle_edge_eoi_irq、handle_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_entry和trace_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标志取或,只要其中一个ISR有IRQF_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);

}

/**

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

*/

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;

}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐