linux硬件中断,分为上下两部分。
- 上部分简单快速,(接收到一个中断,就立即执行,但只做有限的工作)
- 下部分稍后执行,在适合的时机,下半部会被开中断执行。
中断处理原则:
1.不嵌套
2.越快越好
下半部的处理方式有soft_irq,tasklet,workqueue三种。
- soft_irq用在对底半执行时间要求比较紧急或者非常重要的场合,在中断上下文执行。
- tasklet是在中断上下文执行
- workqueue是在process上下文,因此可以执行可能sleep的操作。
软件中断类型
系统中,注册软件中断的一些代码。下面是系统中设置的软件中断类型。
enum
{
HI_SOFTIRQ=0, /* 高优先级tasklet */ /* 优先级最高 */
TIMER_SOFTIRQ, /* 时钟相关的软中断 */
NET_TX_SOFTIRQ, /* 将数据包传送到网卡 */
NET_RX_SOFTIRQ, /* 从网卡接收数据包 */
BLOCK_SOFTIRQ, /* 块设备的软中断 */
BLOCK_IOPOLL_SOFTIRQ, /* 支持IO轮询的块设备软中断 */
TASKLET_SOFTIRQ, /* 常规tasklet */
SCHED_SOFTIRQ, /* 调度程序软中断 */
HRTIMER_SOFTIRQ, /* Unused, but kept as tools rely on the
numbering. Sigh! */ /* 高精度计时器软中断 */
RCU_SOFTIRQ, /* Preferable RCU should always be the last softirq */ /* RCU锁软中断,该软中断总是最后一个软中断 */
NR_SOFTIRQS /* 软中断数,为10 */
};
系统通过设置softirq_veq[nr]的标记位,void (*action)(struct softirq_action *))是中断处理函数。
void open_softirq(int nr, void (*action)(struct softirq_action *))
{
softirq_vec[nr].action = action;
}
内核的软件中断触发函数
void raise_softirq(unsigned int nr)
{
unsigned long flags;
local_irq_save(flags);
raise_softirq_irqoff(nr);
local_irq_restore(flags);
}
比如在内核初始化时已经注册了tasklet
void __init softirq_init(void)
{
int cpu;
for_each_possible_cpu(cpu) {
per_cpu(tasklet_vec, cpu).tail =
&per_cpu(tasklet_vec, cpu).head;
per_cpu(tasklet_hi_vec, cpu).tail =
&per_cpu(tasklet_hi_vec, cpu).head;
}
open_softirq(TASKLET_SOFTIRQ, tasklet_action);
open_softirq(HI_SOFTIRQ, tasklet_hi_action);
}
中断流程框架
中断的处理流程框架,上半部分
@ arch/arm/kernel/entry-armv.S
__irq_svc:
svc_entry
irq_handler
#ifdef CONFIG_PREEMPT
get_thread_info tsk
ldr r8, [tsk, #TI_PREEMPT] @ get preempt count
ldr r0, [tsk, #TI_FLAGS] @ get flags
teq r8, #0 @ if preempt count != 0
movne r0, #0 @ force flags to 0
tst r0, #_TIF_NEED_RESCHED
blne svc_preempt
#endif
svc_exit r5, irq = 1 @ return from exception
UNWIND(.fnend )
ENDPROC(__irq_svc)
/*
* Interrupt handling. 在handle_arch_irq中,最终会跳转到asm_do_IRQ—》__handle_domain_irq中,进入irq_domain的处理
*/
.macro irq_handler
#ifdef CONFIG_MULTI_IRQ_HANDLER
ldr r1, =handle_arch_irq @把handle_arch_irq地址保存到r1
mov r0, sp @把sp保存到r0
badr lr, 9997f @把下条指令的下个位置,即本宏结束的位置放入lr
ldr pc, [r1] @把r1载入pc,即跳转到handle_arch_irq , 在函数__gic_init_bases中设置了set_handle_irq(gic_handle_irq);
#else
arch_irq_handler_default
#endif
9997:
.endm
static void __exception_irq_entry gic_handle_irq(struct pt_regs *regs)
{
u32 irqstat, irqnr;
struct gic_chip_data *gic = &gic_data[0];
void __iomem *cpu_base = gic_data_cpu_base(gic);
do {
irqstat = readl_relaxed(cpu_base + GIC_CPU_INTACK);
irqnr = irqstat & GICC_IAR_INT_ID_MASK; //读取发生的中断号
if (likely(irqnr > 15 && irqnr < 1021)) {
if (static_key_true(&supports_deactivate))
writel_relaxed(irqstat, cpu_base + GIC_CPU_EOI);
handle_domain_irq(gic->domain, irqnr, regs); //如果在16和1020之间,用handle_domain_irq处理。
continue;
}
if (irqnr < 16) {
writel_relaxed(irqstat, cpu_base + GIC_CPU_EOI);
if (static_key_true(&supports_deactivate))
writel_relaxed(irqstat, cpu_base + GIC_CPU_DEACTIVATE);
#ifdef CONFIG_SMP
/*
* Ensure any shared data written by the CPU sending
* the IPI is read after we've read the ACK register
* on the GIC.
*
* Pairs with the write barrier in gic_raise_softirq
*/
smp_rmb();
handle_IPI(irqnr, regs); //如果是0~15就是cpu中断,调用handle_IPI处理
#endif
continue;
}
break;
} while (1);
}
#ifdef CONFIG_HANDLE_DOMAIN_IRQ
/**
* __handle_domain_irq - Invoke the handler for a HW irq belonging to a domain
* @domain: The domain where to perform the lookup
* @hwirq: The HW irq number to convert to a logical one
* @lookup: Whether to perform the domain lookup or not
* @regs: Register file coming from the low-level handling code
*
* Returns: 0 on success, or -EINVAL if conversion has failed
*/
int __handle_domain_irq(struct irq_domain *domain, unsigned int hwirq,
bool lookup, struct pt_regs *regs)
{
struct pt_regs *old_regs = set_irq_regs(regs);
unsigned int irq = hwirq;
int ret = 0;
irq_enter(); //进入硬件中断
#ifdef CONFIG_IRQ_DOMAIN
if (lookup)
irq = irq_find_mapping(domain, hwirq); //找到硬件中断号
#endif
/*
* Some hardware gives randomly wrong interrupts. Rather
* than crashing, do something sensible.
*/
if (unlikely(!irq || irq >= nr_irqs)) {
ack_bad_irq(irq);
ret = -EINVAL;
} else {
generic_handle_irq(irq); //通过硬件中断号去调用处理函数,这里是中断的上半部
}
irq_exit(); //退出硬件中断
set_irq_regs(old_regs);
return ret;
}
#endif
中断的处理流程框架,下半部分中断的流程:
/*
* Enter an interrupt context.
*/
void irq_enter(void)
{
rcu_irq_enter();
if (is_idle_task(current) && !in_interrupt()) {
/*
* Prevent raise_softirq from needlessly waking up ksoftirqd
* here, as softirq will be serviced on return from interrupt.
*/
local_bh_disable();
tick_irq_enter();
_local_bh_enable();
}
__irq_enter(); //进入硬件中断
}
#define __irq_enter() \
do { \
account_irq_enter_time(current); \
preempt_count_add(HARDIRQ_OFFSET); \ //preempt_count++,标记位表示需要处理中断
trace_hardirq_enter(); \
} while (0)
void irq_exit(void)
{
#ifndef __ARCH_IRQ_EXIT_IRQS_DISABLED
local_irq_disable();
#else
WARN_ON_ONCE(!irqs_disabled());
#endif
account_irq_exit_time(current);
preempt_count_sub(HARDIRQ_OFFSET); //preempt_count--,表示硬件中断处理完毕
//in_interrupt()判断preempt_count是否非0,大于1是有中断嵌套,并且没有处于中断上下文
//local_softirq_pending()判断是否有软中断需要执行,raise_softirq()函数就是来置位这个变量
if (!in_interrupt() && local_softirq_pending())
invoke_softirq();
tick_irq_exit();
rcu_irq_exit();
trace_hardirq_exit(); /* must be last! */
}
asmlinkage __visible void __softirq_entry __do_softirq(void)
{
//....
local_irq_enable(); //强开中断
//....处理软件中断
local_irq_disable(); //强关中断
//....
__local_bh_enable(SOFTIRQ_OFFSET); //preempt_count--,完成软件中断
//....
}
流程图
工作队列
中断下半部分,虽然中断是开的,可以处理各类中断。但是整个中断处理其实没有走完,App部分是无法执行的。
如果下半部分执行耗时太长,这个期间App是无法相应的。
然后有内核线程kworker去和App同时竞争执行,去避免系统卡顿App的情况。
kworker会去“工作队列”中取出一个个“工作”来执行它里面的函数。
#define DECLARE_WORK(n, f) \
struct work_struct n = __WORK_INITIALIZER(n, f)
static inline bool schedule_work(struct work_struct *work)
{
return queue_work(system_wq, work);
}
内核的新技术thread_irq
工作队列内个work线程只能在一个CPU中执行,在SMP中效率太低,所以引入了threaded_irq
extern int __must_check
request_threaded_irq(unsigned int irq, irq_handler_t handler,
irq_handler_t thread_fn,
unsigned long flags, const char *name, void *dev);