1 中断分为同步中断(中断)和异步中断(异常)
1.1 中断和异常的不同
-
中断由IO设备和定时器产生,用户的一次按键会引起中断。异步。
-
异常一般由程序错误产生或者由内核必须处理的异常条件产生。同步。缺页异常,断点int3
异常如果由程序错误产生,内核通过发送一个信号来处理异常
如果由内核必须处理的异常条件诱发,那么内核必须执行所需要的所有步骤。
(思考信号产生的动机)
1.2 中断和进程上下文切换的不同
中断切换的代码不是一个进程,而是一个内核控制路径,代表中断发生时正在执行的进程在内核中执行。
1.3 中断敏感性
-
当内核正打算做一些事情,中断随时可能会到来,因此中断必须很快完成(以便内核处理它的事情),尽可能把多的事情放到后面执行。
-
中断随时可能到来,在处理一个中断时必须允许其他中断到来。这样可以允许尽量多的IO设备处于忙碌状态。所以中断应该允许嵌套
-
临界区中,中断必须被禁止。这个要求对于临界区来说是必要的,但是对于内核规则来说是不应当的。所以应该减少临界区。
1.4 中断向量表
非屏蔽中断和异常的向量是固定的,可屏蔽中断的向量可以通过编程来改变。
1.5 IRQ和中断
IRQ(Interrupt request)是设备用来向可编程中断控制器发送中断请求的,IRQ在硬件上和中断控制器相连接。一个设备可能有多条IRQ,如PCI卡有4条。
当设备产生中断信号时,会发送到IRQ线上。中断信号会被中断控制器一直监视,如果有条IRQ线上同时出现中断信号,那么会选取IRQn值最小的那一个优先处理。
中断控制器会将从IRQn上接受来的中断信号转换为对应的向量,将这个向量放到I/O端口上以便CPU需要的时候通过读取数据总线来读。中断控制器将接受到的中断信号转发到CPU的INTR引脚上产生一个中断
接下来就是等待CPU把这个中断信号写进可编程中断控制器的I/O端口来确认这个向量。如果确实是,则清INTR线。
IRQ编号从0开始,IRQn对应的intel的缺省向量为n+32。IRQ和向量的对应关系可以通过一些指令来重新编程。
选择性的禁止IRQ线相当于”服务台”(中断控制器)让客户的需求(设备中断)排队,当开启IRQ时还会来处理需求
通过cli sti来开启和屏蔽可屏蔽中断相当于服务台暂时不上班,客户的需求会被忽略。
1.6 多APIC系统和中断分发方式
-
静态分发
-
动态分发 中断在CPU之间分发
CPU产生处理器之间的中断,这在SMP系统中很有用。
如果是单处理器,那么APIC可以弱化成8259A,LIN0 LIN1分别作为INTR和NMI引脚使用
也可以作为一个外部的I/O APIC使用(虽然它在处理器内部),本地APIC被激活。
中断框架 注册以及调用
中断框架中对 IRQ Line 和 PIC 分别做了抽象。暂且不讨论SMP情形,将外设-中断控制器-处理器这条通路简化为下图:
(不同的硬件会将中断控制器放在不同的位置:如X86的8259放在CPU外,嵌入式领域的SoC一般在芯片内集成)
一个外部设备要想将中断发送给CPU ,首先要配置好PIC。系统中断处理框架已将提供了PIC配置的接口供设备驱动程序使用(设备可能不是一开始就连接在PIC某一中断引脚上的,所以需要在设备驱动程序中调用PIC配置相关接口)
PIC的配置包含以下工作:
- 设定外设的中断触发类型 水平or边沿
- 将外设的中断引脚编号映射到处理器可见的软件中断号irq
- 屏蔽掉某些外部设备的中断触发
处理器提供的PIC配置接口最终也是要落实到配置PIC寄存器上的,比如ARM GIC-400提供了一系列控制寄存器来完成以上所有配置工作。
软件中断号是处理器可见的irq号,用来标识中断到来时从PIC中读取到的中断号码。现代PIC模块多是级联的,也就是说一个SoC上有多个中断控制器,每个中断控制器上都有外部设备接入IRQ Line,一般情况下每个PIC上的IRQ Line编号是一致的,为了区分不同的设备的IRQ编码就需要带上PIC的编码,也就是IRQ Domin的概念,IRQ Line编号在多中断控制器级联的情况下被称为HW Interrupt ID。也就是说这种情况下HW Interrupt ID + IRQ Domin才能标识出一个外设的中断。不管是单PIC还是多PIC级联,最后到处理器面前的只有软件中断号irq。
Linux初始化阶段会准备好中断向量表,其中一箱就是外部设备的中断向量,这是一个通用的外部中断处理函数的入口地址。从该地址运行通用的中断处理函数。进入通用中断处理函之后系统必须知道正在处理的中断是哪一个设备产生的,而这正是由前面提到的中断号irq决定的。
中断向量表的初始化以及通用外部中断处理函数的编写都由操作系统负责
外部中断发生时预先设计好的处理器硬件逻辑往往会做一些特定的动作,为从软件层面发起中断处理做准备。
不同的处理器行为不同,抽象出一个一般路径:
- 保存当前任务的上下文寄存器在中断栈中。
- 屏蔽处理器响应外部中断的能力
- 根据中断向量表找到通用外部中断处理函数入口地址
- 跳转
不同的架构平台上,通用的软件处理函数也有不同,一般过程是:
- 设法从PIC中得到导致本次中断的外部设备对应的软件中断号irq(一般用汇编实现)
- 调用一个C函数(do_IRQ)来处理中断
- C函数返回时完成中断现场恢复工作
- 被中断的任务开始继续执行
为什么要关中断执行:因为各个设备的中断处理函数在驱动程序中实现,内核无法保证这些函数的执行时间,如果时间过长会导致系统措施很多中断,丢失数据或者操作系统响应过长时间。
为了解决这个问题,操作系统将中断处理过程分为两个部分:HARDIRQ和SOFTIRQ。
HARDIRQ部分在中断关闭的情况下执行,只做最关键的操作,尽可能短。irq_enter函数。
SOFTIRQ部分在中断打开的情况下执行,这个阶段系统仍可以响应外部设备的中断,耗时操作可以放在这个部分。irq_exit中完成
do_IRQ源码:
asmlinkage void do_IRQ(int irq, struct pt_regs *regs)
{
struct pt_regs *oldregs = set_irq_regs(regs);
irq_enter(); //HARDIRQ部分的开始 更新系统中的一些统计量 标识出HARDIRQ上下文
generic_handle_irq(irq); //核心
irq_exit(); //SOFTIRQ部分
static inline void generic_handle_irq(unsigned int irq)
{
//irq_desc 沟通从通用的中断处理函数到设备特定的中断处理例程ISR之间的桥梁作用
struct irq_desc *desc = &irq_desc[irq];
//取出对应的irq_desc对象 运行handle_irq函数
desc->handl_irq(irq, desc);
}
下图是对irq_desc结构以及irq_desc数组的描述图
irq_desc数组定义:
struct irq_desc irq_desc[NR_IRQS] __cacheline_aligned_in_smp = {
[0 ... NR_IRQS-1] = {
.handle_irq = handle_bad_irq,
.depth = 1,
.lock = __RAW_SPIN_LOCK_UNLOCKED(irq_desc->lock),
}
};
irq_desc数组的初始化在early_irq_init函数中进行:
int __init early_irq_init(void)
{
int count, i, node = first_online_node;
struct irq_desc *desc;
init_irq_default_affinity();
printk(KERN_INFO "NR_IRQS: %d\n", NR_IRQS);
desc = irq_desc;
count = ARRAY_SIZE(irq_desc);
for (i = 0; i < count; i++) {
desc[i].kstat_irqs = alloc_percpu(unsigned int);
alloc_masks(&desc[i], node);
raw_spin_lock_init(&desc[i].lock);
lockdep_set_class(&desc[i].lock, &irq_desc_lock_class);
mutex_init(&desc[i].request_mutex);
init_waitqueue_head(&desc[i].wait_for_threads);
desc_set_defaults(i, &desc[i], node, NULL, NULL);
}
return arch_early_irq_init();
}
struct irq_desc结构包含的成员,这个结构体十分重要
比较关键的成员已经注释
struct irq_desc {
struct irq_common_data irq_common_data;
struct irq_data irq_data; //保存软件中断号irq和chip相关数据 包括irq_domain
unsigned int __percpu *kstat_irqs; //per-cpu型成员 统计系统中中断计数
irq_flow_handler_t handle_irq; //函数指针 指向和当前设备中断触发电信号类型相关的函数(指向通用处理函数)
//边沿触发就对应一个边沿触发类的处理函数
//电平触发类的就对应一个电平触发类的处理函数
//如果没有区分那就提供一个常规处理函数就行
//handle_irq指向的函数内部才会调用设备特定的中断服务例程
//不同平台的handle_irq的具体实现在系统初始化阶段提供,设备驱动程序员不必关心
struct irqaction *action; /* IRQ action list */
//针对某一个具体设备的中断处理的抽象,是一个链表
//通过request_irq向其中挂载设备特定的中断处理函数
//action中有irq_handler_t类型的handler成员,这个才是对应设备中断服务例程ISR
//设备驱动中安装或卸载的中断处理服务历程ISR也就是装载这个成员里
//irq_desc结构内部有一个对IRQ Line的操作成员handle_irq
//还有一个对这条Line上的所有设备的处理函数的成员action
//handle_irq和action成员负责中断处理的不同部分,handle_irq与irq中断号一一对应,代表了对IRQ Line上的处理动作
//action代表具体的设备相关的处理函数
//action是一个链表,多个设备可以共享一条IRQ Line (共享同一中断号irq)所以接到同一链表上,后面IRQ Line上有中断到来时处理器可以遍历这条链表以执行所有处理函数
unsigned int status_use_accessors;
unsigned int core_internal_state__do_not_mess_with_it;
unsigned int depth; /* nested irq disables */
unsigned int wake_depth; /* nested wake enables */
unsigned int tot_count;
unsigned int irq_count; /* For detecting broken IRQs */
unsigned long last_unhandled; /* Aging timer for unhandled count */
unsigned int irqs_unhandled;
atomic_t threads_handled;
int threads_handled_last;
raw_spinlock_t lock;
struct cpumask *percpu_enabled;
const struct cpumask *percpu_affinity;
#ifdef CONFIG_SMP
const struct cpumask *affinity_hint;
struct irq_affinity_notify *affinity_notify;
#ifdef CONFIG_GENERIC_PENDING_IRQ
cpumask_var_t pending_mask;
#endif
#endif
unsigned long threads_oneshot;
atomic_t threads_active;
wait_queue_head_t wait_for_threads;
#ifdef CONFIG_PM_SLEEP
unsigned int nr_actions;
unsigned int no_suspend_depth;
unsigned int cond_suspend_depth;
unsigned int force_resume_depth;
#endif
#ifdef CONFIG_PROC_FS
struct proc_dir_entry *dir;
#endif
#ifdef CONFIG_GENERIC_IRQ_DEBUGFS
struct dentry *debugfs_file;
const char *dev_name;
#endif
#ifdef CONFIG_SPARSE_IRQ
struct rcu_head rcu;
struct kobject kobj;
#endif
struct mutex request_mutex;
int parent_irq;
struct module *owner;
const char *name; //查/proc/interrupts文件显示的对应名称
} ____cacheline_internodealigned_in_smp;
来看看struct irq_data结构的irq_data成员保存了哪些
struct irq_data {
u32 mask;
unsigned int irq; //软件中断号
unsigned long hwirq;
struct irq_common_data *common;
struct irq_chip *chip; //软件层面对PIC的一个抽象 屏蔽不同平台上PIC的差异 提供统一的PIC操作接口
struct irq_domain *domain; //用于映射hw interrupt id和irq
#ifdef CONFIG_IRQ_DOMAIN_HIERARCHY
struct irq_data *parent_data;
#endif
void *chip_data;
};
irq_chip对PIC的抽象,提供统一PIC操作接口
provider需要实现接口中的API以供上层软件操作PIC
示例:
static struct irq_chip irq_chip = {
.irq_mask = combiner_irq_chip_mask_irq,
.irq_unmask = combiner_irq_chip_unmask_irq,
.name = "qcom-irq-combiner"
};
平台的初始化函数负责实现该平台使用的PIC的对象并将其安装到irq_desc数组中
从这个结构中各个函数的名字可以看出,PIC配置工作主要包括设定外部设备的中断触发类型,屏蔽或者启用某一个设备的终端信号,向发出信号的设备发送中断响应信号等。
struct irq_chip {
const char *name;
unsigned int (*irq_startup)(struct irq_data *data);
void (*irq_shutdown)(struct irq_data *data);
void (*irq_enable)(struct irq_data *data);
void (*irq_disable)(struct irq_data *data);
void (*irq_ack)(struct irq_data *data);
void (*irq_mask)(struct irq_data *data);
void (*irq_mask_ack)(struct irq_data *data);
void (*irq_unmask)(struct irq_data *data);
void (*irq_eoi)(struct irq_data *data);
int (*irq_set_affinity)(struct irq_data *data, const struct cpumask *dest, bool force);
int (*irq_retrigger)(struct irq_data *data);
int (*irq_set_type)(struct irq_data *data, unsigned int flow_type);
int (*irq_set_wake)(struct irq_data *data, unsigned int on);
void (*irq_bus_lock)(struct irq_data *data);
void (*irq_bus_sync_unlock)(struct irq_data *data);
#ifdef CONFIG_DEPRECATED_IRQ_CPU_ONOFFLINE
void (*irq_cpu_online)(struct irq_data *data);
void (*irq_cpu_offline)(struct irq_data *data);
#endif
void (*irq_suspend)(struct irq_data *data);
void (*irq_resume)(struct irq_data *data);
void (*irq_pm_shutdown)(struct irq_data *data);
void (*irq_calc_mask)(struct irq_data *data);
void (*irq_print_chip)(struct irq_data *data, struct seq_file *p);
int (*irq_request_resources)(struct irq_data *data);
void (*irq_release_resources)(struct irq_data *data);
void (*irq_compose_msi_msg)(struct irq_data *data, struct msi_msg *msg);
void (*irq_write_msi_msg)(struct irq_data *data, struct msi_msg *msg);
int (*irq_get_irqchip_state)(struct irq_data *data, enum irqchip_irq_state which, bool *state);
int (*irq_set_irqchip_state)(struct irq_data *data, enum irqchip_irq_state which, bool state);
int (*irq_set_vcpu_affinity)(struct irq_data *data, void *vcpu_info);
void (*ipi_send_single)(struct irq_data *data, unsigned int cpu);
void (*ipi_send_mask)(struct irq_data *data, const struct cpumask *dest);
int (*irq_nmi_setup)(struct irq_data *data);
void (*irq_nmi_teardown)(struct irq_data *data);
unsigned long flags;
};
handle_irq和action之间的层次关系
设备注册的中断处理函数调用结束后,中断流程进入SOFTIRQ部分,如果有等待的softirq需要处理,则处理之,否则返回通用中断处理函数:
irq_set_handler
设定irq_desc数组中某一项即设定irq_desc[irq]
irq_desc[irq]代表了某一个PIC的某一个IRQ Line上的所有中断处理函数,使处理器能够调用到中断处理函数前必须要注册。
因为调用分为两步,所以注册也分为两步:
- 注册irq_desc[irq].handle_irq
- 注册irq_desc[irq].action->hanlder
第一级注册在平台初始化期间被安装到irq_desc数组,会被do_IRQ调用,主要面向某一中断线IRQ Line
第二级注册就是设备驱动开发人员在驱动程序中用request_irq注册的,会被irq_desc[irq].handle_irq
调用,主要面向中断线上链接的具体设备。
内核准备了两个函数来让平台的初始化代码通过handle_irq注册第一级中断处理函数:
- irq_set_handler
- irq_ set_chained_handler
这两个注册函数区别仅在于对应IRQ Line上的设备是否支持中断共享
/*
* handler: 安装在irq_desc[irq].handle_irq上的第一级处理函数
* irq:对应的irq号
*/
static inline void
irq_set_handler(unsigned int irq, irq_flow_handler_t handle)
{
__irq_set_handler(irq, handle, 0, NULL);
}
/*
* is_chained标志是否对应项支持中断共享
*/
void
__irq_set_handler(unsigned int irq, irq_flow_handler_t handle, int is_chained,
const char *name)
{
unsigned long flags;
struct irq_desc *desc = irq_get_desc_buslock(irq, &flags, 0);
if (!desc)
return;
__irq_do_set_handler(desc, handle, is_chained, name);
irq_put_desc_busunlock(desc, flags);
}
EXPORT_SYMBOL_GPL(__irq_set_handler);
static inline void
irq_set_chained_handler(unsigned int irq, irq_flow_handler_t handle)
{
__irq_set_handler(irq, handle, 1, NULL);
}
中断共享由is_chained字段决定
如果支持共享:
desc->status_use_accessors |= _IRQ_NOPROBE | _IRQ_NOREQUEST;
_IRQ_NOREQUEST是指irq_desc[irq]来说无法通过request_irq安装中断处理例程
_IRQ_NOPROBE是指无法对irq_desc[irq]执行中断号的探测机制,这也说明如果irq_desc[irq]对应项是支持中断共享的,那就不能支持自动探测中断号。
handle_irq具体可以对应不同的中断触发类型的处理函数,处理边沿中断触发信号的函数:
void handle_edge_irq(struct irq_desc *desc)
{
raw_spin_lock(&desc->lock);
//清除这两位,用来实现设备软件中断号自动探测机制
desc->istate &= ~(IRQS_REPLAY | IRQS_WAITING);
/*
* If we're currently running this IRQ, or its disabled,
* we shouldn't process the IRQ. Mark it pending, handle
* the necessary masking and go out
*/
if (unlikely(irqd_irq_disabled(&desc->irq_data) ||
irqd_irq_inprogress(&desc->irq_data) || !desc->action)) {
if (!irq_check_poll(desc)) {
desc->istate |= IRQS_PENDING;
mask_ack_irq(desc);
goto out_unlock;
}
}
//更新与中断相关的一些统计量 比如某个CPU上的中断次数
kstat_incr_irqs_this_cpu(desc);
/* Start handling the irq */
//利用PIC对象的irq_ack例程向设备发出一个中断响应信号
//硬件上配置了相关寄存器后使得当前发出中断信号的设备中产生一个信号电平的转换
//防止设备在它的中断已经在设备驱动程序中处理时依然不听的发出同一中断信号(已经在做了,别叫了)
desc->irq_data.chip->irq_ack(&desc->irq_data);
//核心部分
do {
//检查 是空指针说明目前还没有设备驱动程序注册ISR到该中断线上
if (unlikely(!desc->action)) {
//调用这个PIC的irq_mask例程屏蔽当前中断线在PIC中对应的中断位
//将dssc->irq_data.state_use_accessors的IRQD_IRQ_MASKED置位
//没有安装中断处理函数的外部中断应该屏蔽掉它直到有处理函数安装上去,否则该设备将会不同的中断处理器
mask_irq(desc);
goto out_unlock;
}
/*
* When another irq arrived while we were handling
* one, we could have masked the irq.
* Reenable it, if it was not disabled in meantime.
*/
//上面注释的意思是说,如果在处理一个中断的同时新的中断出现了,那么应该将state位置位pending悬而未决
//PIC中将对应的中断线屏蔽掉,这样while循环的条件才会在下一次进入时满足
if (unlikely(desc->istate & IRQS_PENDING)) {
if (!irqd_irq_disabled(&desc->irq_data) &&
irqd_irq_masked(&desc->irq_data))
unmask_irq(desc);
}
//调用设备驱动注册的中断处理例程后做最后的工作
handle_irq_event(desc);
} while ((desc->istate & IRQS_PENDING) &&
!irqd_irq_disabled(&desc->irq_data));
out_unlock:
raw_spin_unlock(&desc->lock);
}
EXPORT_SYMBOL(handle_edge_irq);
irqreturn_t handle_irq_event(struct irq_desc *desc)
{
struct irqaction *action = desc->action;
irqreturn_t ret;
//清除悬而未决(马上就做完的事情,就不要在高高挂起了)
desc->istate &= ~IRQS_PENDING;
//设置当前中断线为正在处理状态
irqd_set(&desc->irq_data, IRQD_IRQ_INPROGRESS);
raw_spin_unlock(&desc->lock);
//要执行设备驱动注册的函数了
ret = handle_irq_event_percpu(desc, action);
raw_spin_lock(&desc->lock);
//
irqd_clear(&desc->irq_data, IRQD_IRQ_INPROGRESS);
return ret;
}
irqreturn_t
handle_irq_event_percpu(struct irq_desc *desc, struct irqaction *action)
{
irqreturn_t retval = IRQ_NONE;
unsigned int random = 0, irq = desc->irq_data.irq;
//遍历action链表
do {
irqreturn_t res;
trace_irq_handler_entry(irq, action);
//调用
res = action->handler(irq, action->dev_id);
trace_irq_handler_exit(irq, action, res);
if (WARN_ONCE(!irqs_disabled(),"irq %u handler %pF enabled interrupts\n",
irq, action->handler))
local_irq_disable();
//处理返回值的不同情况
switch (res) {
case IRQ_WAKE_THREAD://中断线程化?TODO
/*
* Set result to handled so the spurious check
* does not trigger.
*/
res = IRQ_HANDLED;
/*
* 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;
}
irq_wake_thread(desc, action);
/* Fall through to add to randomness */
case IRQ_HANDLED:
random |= action->flags;
break;
default:
break;
}
retval |= res;
action = action->next;
} while (action);
if (random & IRQF_SAMPLE_RANDOM)
add_interrupt_randomness(irq);
if (!noirqdebug)
note_interrupt(irq, desc, retval);
return retval;
}
设备驱动注册中断
中断注册过程讲完了,驱动程序中安装一个设备中断服务例程通过调用request_irq函数完成
/*
* irq:软件中断号irq
* handler:ISR函数,驱动实现
* flags:标志变量,影响内核安装ISR时的一些行为
* name:proc文件系统中生成name的一个入口点
* dev:对于共享中断线的设备处理函数很关键,free_irq时通过dev找到对应的action函数才能删除,dev必须有唯一性
*/
static inline int __must_check
request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
const char *name, void *dev)
{
//thread_fn指定用于中断处理的线程irq_thread有关 传入NULL不使用irq_thread机制
return request_threaded_irq(irq, handler, NULL, flags, name, dev);
}
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)
{
struct irqaction *action;
struct irq_desc *desc;
int retval;
/*
* Sanity-check: shared interrupts must pass in a real dev-ID,
* otherwise we'll have trouble later trying to figure out
* which interrupt is which (messes up the interrupt freeing
* logic etc).
*/
if ((irqflags & IRQF_SHARED) && !dev_id)
return -EINVAL;
desc = irq_to_desc(irq);
if (!desc)
return -EINVAL;
if (!irq_settings_can_request(desc))
return -EINVAL;
if (!handler) {
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;
action->flags = irqflags;
action->name = devname;
action->dev_id = dev_id;
chip_bus_lock(desc);
//超级复杂 但是核心就是设置处理函数 desc->action = action;
//分为两个情况设置 desc->action还是空的 and desc->action已经有节点
//在/proc/irq目录下创建新目录项 方便调试
retval = __setup_irq(irq, desc, action);
chip_bus_sync_unlock(desc);
if (retval)
kfree(action);
#ifdef CONFIG_DEBUG_SHIRQ_FIXME
if (!retval && (irqflags & IRQF_SHARED)) {
/*
* It's a shared IRQ -- the driver ought to be prepared for it
* to happen immediately, so let's make sure....
* We disable the irq to make sure that a 'real' IRQ doesn't
* run in parallel with our fake.
*/
unsigned long flags;
disable_irq(irq);
local_irq_save(flags);
handler(irq, dev_id);
local_irq_restore(flags);
enable_irq(irq);
}
#endif
return retval;
}
中断处理的thread_fn机制
thread_fn机制将中断处理过程发生在进程上下文而非中断上下文。
用request_threaded_irq函数安装中断时需要在struct irqaction对象中实现他的thread_fn成员,函数内部会创建一个irq_thread的独立线程
这个线程创建出来以TASK_INTERRUPTIBLE状态存在,睡眠等待中断的发生。
中断发生时在action->handler中负责唤醒睡眠的irq_thread 该线程调用action->thread_fn处理中断实际工作。
if (new->thread_fn && !nested) {
struct task_struct *t;
t = kthread_create(irq_thread, new, "irq/%d-%s", irq,
new->name);
if (IS_ERR(t))
return PTR_ERR(t);
/*
* 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;
转自:
https://blog.csdn.net/weixin_43604927/article/details/129456463