rk3288 linux中的中断处理流程

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);
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

习惯就好zz

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值