文章目录
- [1. 前言](#1. 前言)
- [2. 实现分析](#2. 实现分析)
-
- [2.1 创建中断处理线程](#2.1 创建中断处理线程)
- [2.2 唤醒中断线程处理中断](#2.2 唤醒中断线程处理中断)
1. 前言
限于作者能力水平,本文可能存在谬误,因此而给读者带来的损失,作者不做任何承诺。
2. 实现分析
2.1 创建中断处理线程
c
// kernel/irq/manage.c
// @handler: 中断处理主函数,在中断上下文调用
// @thread_fn: 中断线程中断处理回调,在中断线程里调用
int request_threaded_irq(unsigned int irq, irq_handler_t handler,
irq_handler_t thread_fn, unsigned long irqflags,
const char *devname, void *dev_id)
{
...
/* 不指定任何接口, 则默认使用 irq_default_primary_handler() 接口 */
if (!handler) { /* 线程化中断的 @handler 通常设为 NULL */
if (!thread_fn)
return -EINVAL;
handler = irq_default_primary_handler;
}
...
/* 分配 中断行为 描述符 */
action = kzalloc(sizeof(struct irqaction), GFP_KERNEL);
if (!action)
return -ENOMEM;
/* 设定 中断行为 描述符 */
action->handler = handler;
action->thread_fn = thread_fn;
...
retval = __setup_irq(irq, desc, action);
...
}
static int
__setup_irq(unsigned int irq, struct irq_desc *desc, struct irqaction *new)
{
...
if (new->thread_fn && !nested) { /* 中断线程化 */
ret = setup_irq_thread(new, irq, false); /* 创建设置中断线程 */
...
}
...
/* 唤醒中断线程执行 */
if (new->thread)
wake_up_process(new->thread);
...
}
static int
setup_irq_thread(struct irqaction *new, unsigned int irq, bool secondary)
{
struct task_struct *t;
struct sched_param param = {
.sched_priority = MAX_USER_RT_PRIO/2, /* 中断线程 使用 实时优先级 (50) */
};
if (!secondary) {
t = kthread_create(irq_thread, new, "irq/%d-%s", irq,
new->name);
} else {
t = kthread_create(irq_thread, new, "irq/%d-s-%s", irq,
new->name);
param.sched_priority -= 1;
}
...
sched_setscheduler_nocheck(t, SCHED_FIFO, ¶m); /* 中断线程使用 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;
/*
* Tell the thread to set its affinity. This is
* important for shared interrupt handlers as we do
* not invoke setup_affinity() for the secondary
* handlers as everything is already set up. Even for
* interrupts marked with IRQF_NO_BALANCE this is
* correct as we want the thread to move to the cpu(s)
* on which the requesting code placed the interrupt.
*/
/* 设置 IRQTF_AFFINITY 标记,指示 中断线程 自身设置 其 CPU 亲和性 */
set_bit(IRQTF_AFFINITY, &new->thread_flags);
return 0;
}
从上面分析看到,中断线程有一个公共入口 irq_thread(),处理逻辑将在 2.2 中展开。
2.2 唤醒中断线程处理中断
发生中断时,ARM 架构下从 gic_handle_irq() 开始处理中断:
c
gic_handle_irq()
handle_domain_irq()
__handle_domain_irq()
irq_enter();
generic_handle_irq(irq);
generic_handle_irq_desc()
...
__handle_irq_event_percpu()
irq_exit();
// kernel/irq/handle.c
irqreturn_t __handle_irq_event_percpu(struct irq_desc *desc, unsigned int *flags)
{
...
for_each_action_of_desc(desc, action) {
irqreturn_t res;
...
res = action->handler(irq, action->dev_id);
...
switch (res) {
/*
* 线程化中断,主接口 action->handler() 应该返回 IRQ_WAKE_THREAD ,
* 譬如线程化中断的默认主接口 irq_default_primary_handler()
*/
case IRQ_WAKE_THREAD:
__irq_wake_thread(desc, action); /* 唤醒 中断线程 处理中断 */
/* Fall through to add to randomness */
case IRQ_HANDLED: /* 中断处理完毕 */
*flags |= action->flags;
break;
default:
break;
}
...
}
...
}
/* 唤醒 中断线程 处理中断 */
void __irq_wake_thread(struct irq_desc *desc, struct irqaction *action)
{
...
/*
* Wake up the handler thread for this action. If the
* RUNTHREAD bit is already set, nothing to do.
*/
/* 用 IRQTF_RUNTHREAD 标志位告知中断线程中断发生了 */
if (test_and_set_bit(IRQTF_RUNTHREAD, &action->thread_flags))
return;
...
wake_up_process(action->thread); /* 唤醒 中断线程 处理中断 */
}
线程化中断在公共入口 irq_thread() 中处理中断:
c
/*
* Interrupt handler thread
*/
static int irq_thread(void *data)
{
struct callback_head on_exit_work;
struct irqaction *action = data;
struct irq_desc *desc = irq_to_desc(action->irq);
irqreturn_t (*handler_fn)(struct irq_desc *desc,
struct irqaction *action);
if (force_irqthreads && test_bit(IRQTF_FORCED_THREAD,
&action->thread_flags))
handler_fn = irq_forced_thread_fn;
else
handler_fn = irq_thread_fn;
...
/*
* 对设置了 IRQTF_AFFINITY 标记的 中断,设置线程的 CPU 亲和性
* 为 中断的 CPU 亲和性 desc->irq_common_data.affinity 。
*/
irq_thread_check_affinity(desc, action);
while (!irq_wait_for_interrupt(action)) {
irqreturn_t action_ret;
/*
* 用户空间可能中途通过 /proc/irq/<N>/affinity 来修改中断
* 的 CPU 亲和性,因此要在循环内部动态监测这种变化。
*/
irq_thread_check_affinity(desc, action);
action_ret = handler_fn(desc, action); // irq_thread_fn() 或 irq_forced_thread_fn()
if (action_ret == IRQ_WAKE_THREAD)
irq_wake_secondary(desc, action);
wake_threads_waitq(desc);
}
...
}
中断发生是先设置了 IRQTF_RUNTHREAD 标记,然后唤醒中断线程;然后在上面的 irq_thread() 的 while 循环里,通过 irq_wait_for_interrupt() 检查是否发生了中断要处理:
c
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)) { // 检测 IRQTF_RUNTHREAD 标记
__set_current_state(TASK_RUNNING);
return 0;
}
schedule();
set_current_state(TASK_INTERRUPTIBLE);
}
__set_current_state(TASK_RUNNING);
return -1;
}
如果 irq_wait_for_interrupt() 检查到 IRQTF_RUNTHREAD 标记,说明有中断要处理,则调用 irq_thread_fn() 或 irq_forced_thread_fn() 处理中断,这里分析下 irq_thread_fn():
c
static irqreturn_t irq_thread_fn(struct irq_desc *desc,
struct irqaction *action)
{
irqreturn_t ret;
ret = action->thread_fn(action->irq, action->dev_id); /* 调用 request_threaded_irq() 注册时传递 @thread_fn 函数 */
if (ret == IRQ_HANDLED)
atomic_inc(&desc->threads_handled);
irq_finalize_oneshot(desc, action);
return ret;
}
irq_forced_thread_fn() 和 irq_thread_fn() 逻辑类似,这里就不再展开。