分类: LINUX
2013-07-23 13:07:08
摘要:本文主要讲述linux如何处理ARM cortex A9多核处理器的中断处理过程的C函数部分。主要是在中断上下文和线程上下文处理ISR的过程。
法律声明:《LINUX3.0内核源代码分析》系列文章由谢宝友()发表于http://xiebaoyou.blog.chinaunix.net,文章中的LINUX3.0源代码遵循GPL协议。除此以外,文档中的其他内容由作者保留所有版权。谢绝转载。
本连载文章并不是为了形成一本适合出版的书籍,而是为了向有一定内核基本的读者提供一些linux3.0源码分析。因此,请读者结合《深入理解LINUX内核》第三版阅读本连载。
大多数中断是由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,需要整整一本书来描述,请从下载<深入理解并行编程>了解更多内容。
* 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;
}