中断详解(七)——软中断与微任务

 我们知道,中断处理程序被分为上半部函数和下半部函数。而软中断、微任务、工作队列都是是下半部函数的机制。(至于工作队列的方式,她是以另一个线程的方式实现的,初始化时创建,调用时唤醒,其实request_thread_irq是将中断线程化,包括利用定时器实现延时处理,这都不是在中断上下文中)
    软中断和微任务都是通过 do_softirq执行的。
     中断处理在do_IRQ中调用软中断 do_softirq
unsigned int __irq_entry do_IRQ(struct pt_regs *regs)
{
    ......
    irq_exit();
}
 
void irq_exit(void)                                                                                                                     
{
        ......
        invoke_softirq();  //调用软irq处理
}   
#ifdef __ARCH_IRQ_EXIT_IRQS_DISABLED
# define invoke_softirq()   __do_softirq()
#else
# define invoke_softirq()   do_softirq()
#endif


软中断:
获取软中断向量表
asmlinkage void do_softirq(void)
{
    __u32 pending;
    unsigned long flags;
 
    // 这个函数判断,如果当前有硬件中断嵌套,或有软中断正在执行时候,则马上返回
    //入口判断主要是为了和 ksoftirqd 互斥。
    if (in_interrupt())
        return;
 
    local_irq_save(flags);// 关中断执行以下代码  
 
    pending = local_softirq_pending();// 判断是否有 pending 的软中断需要处理。
 
    if (pending) // 如果有则调用 __do_softirq() 进行实际处理  
        __do_softirq();
 
    local_irq_restore(flags); // 开中断继续执行 
}

asmlinkage void __do_softirq(void)
{
    struct softirq_action *h;  // 软件中断处理结构,此结构中包括了 ISR 中 注册的回调函数。   
    __u32 pending;
    unsigned long end = jiffies + MAX_SOFTIRQ_TIME;
    int cpu;
    int max_restart = MAX_SOFTIRQ_RESTART;
 
    pending = local_softirq_pending();  // 得到当前所有 pending 的软中断。
    account_system_vtime(current);
 
     /* 执行到这里要屏蔽其他软中断,每个 CPU 上同时运行的软中断只能有一个。*/
    __local_bh_disable((unsigned long)__builtin_return_address(0),
                SOFTIRQ_OFFSET);
    lockdep_softirq_enter();
 
    cpu = smp_processor_id(); // 针对 SMP 得到当前正在处理的 CPU 
restart:
     // 每次循环在允许硬件 ISR 强占前,首先重置软中断的标志位。   
    /* Reset the pending bitmask before enabling irqs */
    set_softirq_pending(0);
 
    // 开中断运行,(注意:以前运行状态一直是关中断运行)
    //这里以后的代码才可能被硬件中断抢占。   
    local_irq_enable();
    h = softirq_vec; //获取软中断向量表
 
    do {
        if (pending & 1) {  // 如果对应的软中断设置 pending 标志则表明需要进一步处理他所注册的函数。   
            int prev_count = preempt_count();
            kstat_incr_softirqs_this_cpu(h - softirq_vec);
 
            trace_softirq_entry(h, softirq_vec);
            h->action(h); // 在这里执行了这个软中断所注册的回调函数。  
            trace_softirq_exit(h, softirq_vec);
            if (unlikely(prev_count != preempt_count())) {
                printk(KERN_ERR "huh, entered softirq %td %s %p"
                       "with preempt_count %08x,"
                       " exited with %08x?\n", h - softirq_vec,
                       softirq_to_name[h - softirq_vec],
                       h->action, prev_count, preempt_count());
                preempt_count() = prev_count;
            }
 
            rcu_bh_qs(cpu);
        }
        h++;// 继续找,直到把软中断向量表中所有 pending 的软中断处理完成。   
        pending >>= 1;    // 从代码里能看出按位右移操作,表明一次循环只处理 32 个软中断的回调函数
    } while (pending);
 
    local_irq_disable(); // 再次关中断  
 
    pending = local_softirq_pending();
    if (pending) {
        if (time_before(jiffies, end) && !need_resched() &&
            --max_restart)  // 如果刚才触发的硬件中断注册了软中断,并且重复执行次数没有到 max_restart个 次的话
            goto restart;
         
        //超过max_restart个硬件中断注册了软中断,满负荷,需要另开线程处理软中断
        // 此函数实际是调用 wake_up_process() 来唤醒 ksoftirqd   
        wakeup_softirqd(); 
 
    }
 
    lockdep_softirq_exit();
 
    account_system_vtime(current);
 
    // 到最后才开软中断执行环境,允许软中断执行。注意:这里
    // 使用的不是 local_bh_enable(),不会再次触发 do_softirq()的调用。   
    __local_bh_enable(SOFTIRQ_OFFSET); 
}

void wakeup_softirqd(void)
{
    /* Interrupts are disabled: no need to stop preemption */
    struct task_struct *tsk = __get_cpu_var(ksoftirqd); //ksoftirqd 线程
 
    if (tsk && tsk->state != TASK_RUNNING)
        wake_up_process(tsk); //唤醒ksoftirqd线程                                                                                       
}
 
static int ksoftirqd(void * __bind_cpu)
{
    // 设置当前进程状态为可中断的状态,
     set_current_state(TASK_INTERRUPTIBLE);
                                       
    current->flags |= PF_KSOFTIRQD; // 设置当前进程不允许被挂起   

    while (!kthread_should_stop()) { //循环判断当前进程是否会停止
        preempt_disable();// 禁止当前进程被抢占。 
        if (!local_softirq_pending()) {  // 首先判断系统当前没有需要处理的 pending 状态的软中断
           // 没有的话在主动放弃 CPU 前先要允许抢占,因为一直是在不允许抢占状态下执行的代码。   
            preempt_enable_no_resched(); 
           // 主动放弃 CPU 将当前进程放入睡眠队列,并转换新的进程执行(调度器相关不记录在此),但此进程再次被唤醒时,直接执行下一语句。
            schedule();
            preempt_disable(); // 当进程再度被调度时,在以下处理期间内禁止当前进程被抢占。   
        }
 
        __set_current_state(TASK_RUNNING); // 设置当前进程为运行状态。
       
        /*
         * 循环判断是否有 pending 的软中断,如果有则调用 do_softirq()来做具体处理。
         * 注意:这里又是个 do_softirq() 的入口点,
         * 那么在 __do_softirq() 当中循环处理 10 次软中断的回调函数后,
         * 如果更有 pending 的话,会又调用到这里
         * 那么在这里则又会有可能去调用 __do_softirq() 来处理软中断回调函数。
         * 在__do_softirq()中,处理 10 次还处理不完的话说明系统正处于繁忙状态。
         * 综上,在系统非常繁忙时,这个进程将会和 do_softirq() 相互交替执行,
         * 这时此进程占用 CPU 应该会非常高,
         */
        while (local_softirq_pending()) {
            /* Preempt disable stops cpu going offline.
               If already offline, we'll be on wrong CPU:
               don't process */
            if (cpu_is_offline((long)__bind_cpu))// 如果当前被关联的 CPU 无法继续处理则跳转 
                goto wait_to_die;  // 到 wait_to_die 标记出,等待结束并退出。 

             // 执行 do_softirq() 来处理具体的软中断回调函数。
             //如果此时有一个正在处理的软中断的话,则会马上返回 注意in_interrupt()
            do_softirq();
            preempt_enable_no_resched();// 允许当前进程被抢占。

            // 这个函数有可能间接的调用 schedule() 来转换当前进程,而且上面已允许当前进程可被抢占。
            //也就是说在处理完一轮软中断回调函数时,有可能会转换到其他进程。
            cond_resched();
            preempt_disable(); // 禁止当前进程被抢占。
            rcu_sched_qs((long)__bind_cpu);
        }// 处理完所有软中断了吗?没有的话继续循环以上步骤   

        preempt_enable();// 允许当前进程被抢占
        set_current_state(TASK_INTERRUPTIBLE); // 设置当前进程状态为可中断的状态,
    }
    __set_current_state(TASK_RUNNING);// 设置当前进程为运行状态
    return 0;
 
wait_to_die:// 一直等待到当前进程被停止
    preempt_enable();
    /* Wait for kthread_stop */
    set_current_state(TASK_INTERRUPTIBLE);
    
    // 判断当前进程是否会被停止,如果不是的话则设置进程状态为可中断状态并放弃当前 CPU 主动转换。
    // 也就是说这里将一直等待当前进程将被停止时候才结束。   
    while (!kthread_should_stop()) {
        schedule();
        set_current_state(TASK_INTERRUPTIBLE);
    }
   // 如果将会停止则设置当前进程为运行状态后直接返回。调度器会根据优先级来使当前进程运行。
    __set_current_state(TASK_RUNNING);
    return 0;
}







  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值