Linux 中断线程化

文章目录

  • [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, &param); /* 中断线程使用 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() 逻辑类似,这里就不再展开。

相关推荐
探序基因2 小时前
安装R包arrow
linux·运维·服务器
AI+程序员在路上2 小时前
linux中bash与sh脚本区别
linux·运维·bash
路弥行至2 小时前
linux运行脚本出现错误信息 /bin/bash^M: bad interpreter解决方法
linux·运维·开发语言·经验分享·笔记·其他·bash
我爱学习好爱好爱2 小时前
Elasticsearch 7.17.10 双节点集群部署实战(基于 Rocky Linux 9.6)
大数据·linux·elasticsearch
豆浆煮粉2 小时前
基于 Linux+CMake 从零集成 Lua 脚本引擎 (附 Sol2 避坑指南)
linux·lua
NEAI_N2 小时前
离网设备的加密解密方案
linux·服务器·网络
左手の明天2 小时前
Linux内核裁剪深入浅出:从原理到实操,打造轻量化嵌入式内核
linux·arm开发·c++
萝卜白菜。2 小时前
annotation扫描引起的StackOverflowError问题
linux·运维·服务器
瀚高PG实验室2 小时前
瀚高安全版 V4.5.10卸载后残留了db_ha的agent进程导致6666端口被占用
linux·数据库·安全·瀚高数据库