前言
前面已经了解了底半部机制tasklet,工作队列,软中断,threaded_irq如何使用,这篇文章中将继续了解各种机制内部的调用关系。
软中断
初始化
软中断类型
//软中断的类型 enum { HI_SOFTIRQ=0, /* 最高优先级软中断 */ TIMER_SOFTIRQ, /* Timer定时器软中断 */ NET_TX_SOFTIRQ, /* 发送网络数据包软中断 */ NET_RX_SOFTIRQ, /* 接收网络数据包软中断 */ BLOCK_SOFTIRQ, /* 块设备软中断 */ BLOCK_IOPOLL_SOFTIRQ,/* 支持IO轮询的块设备软中断 */ TASKLET_SOFTIRQ, /* tasklet软中断 */ SCHED_SOFTIRQ, /* 进程调度及负载均衡的软中断 */ HRTIMER_SOFTIRQ,//高精度定时器(已经放弃) RCU_SOFTIRQ, //RCU相关的软中断 /* Preferable RCU should always be the last softirq */ NR_SOFTIRQS }; #define SOFTIRQ_STOP_IDLE_MASK (~(1 << RCU_SOFTIRQ)) /* map softirq index to softirq name. update 'softirq_to_name' in * kernel/softirq.c when adding a new softirq. */ /* 软中断描述符表,实际上就是一个全局的数组 */ extern const char * const softirq_to_name[NR_SOFTIRQS]; /* softirq mask and active fields moved to irq_cpustat_t in * asm/hardirq.h to get better cache usage. KAO */ /* 软件中断描述符,只包含一个handler函数指针 */ struct softirq_action { void (*action)(struct softirq_action *); }; asmlinkage void do_softirq(void); asmlinkage void __do_softirq(void); #ifdef __ARCH_HAS_DO_SOFTIRQ void do_softirq_own_stack(void); #else static inline void do_softirq_own_stack(void) { __do_softirq(); } #endif extern void open_softirq(int nr, void (*action)(struct softirq_action *)); extern void softirq_init(void); extern void __raise_softirq_irqoff(unsigned int nr); extern void raise_softirq_irqoff(unsigned int nr); extern void raise_softirq(unsigned int nr); /* 内核为每个CPU都创建了一个软中断处理内核线程 */ DECLARE_PER_CPU(struct task_struct *, ksoftirqd);
softirq_vec[]数组,类比硬件中断描述符表irq_desc[],通过软中断号可以找到对应的handler进行处理,比如图中的tasklet_action就是一个实际的handler函数;
软中断可以在不同的CPU上并行运行,在同一个CPU上只能串行执行;
每个CPU维护irq_cpustat_t状态结构,当某个软中断需要进行处理时,会将该结构体中的__softirq_pending字段或上1UL << XXX_SOFTIRQ;
软中断注册
软中断处理流程中,通过open_softirq接口来注册:
void open_softirq(int nr, void (*action)(struct softirq_action *)) { softirq_vec[nr].action = action;//将软中断描述符表中对应描述符的handler函数指针指向对应的函数 }
软中断执行->中断处理后
软中断执行的入口是invoke_softirq:
static inline void invoke_softirq(void) { if (!force_irqthreads) { #ifdef CONFIG_HAVE_IRQ_EXIT_ON_IRQ_STACK /* * We can safely execute softirq on the current stack if * it is the irq stack, because it should be near empty * at this stage. */ __do_softirq();//中断未强制线程化 #else /* * Otherwise, irq_exit() is called on the task stack that can * be potentially deep already. So call softirq in its own stack * to prevent from any overrun. */ do_softirq_own_stack(); #endif } else {//中断线程化 wakeup_softirqd();//唤醒内核线程来处理软中断 } }
invoke_softirq函数中,根据中断处理是否线程化进行分类处理,如果中断已经进行了强制线程化处理(中断强制线程化,需要在启动的时候传入参数threadirqs),那么直接通过wakeup_softirqd唤醒内核线程来执行,否则的话则调用__do_softirq函数来处理;
在接收到中断信号后,处理器进行异常模式切换,并跳转到异常向量表处进行执行,关键的流程为:
el0_irq->irq_handler->handle_arch_irq(gic->handle_irq)->handle_domain_irq->__handle_domain_irq;
在__handle_domain_irq函数中,irq_enter和irq_exit分别用于来标识进入和离开硬件中断上下文处理,这个从preempt_count_add/preempt_count_sub来操作HARDIRQ_OFFSET可以看出来,这也对应到了上文中的Context描述图;
在离开硬件中断上下文后,如果!in_interrupt() && local_softirq_pending为真,则进行软中断处理。这个条件有两个含义:1)!in_interrupt()表明不能处在中断上下文中,这个范围包括in_nmi、in_irq、in_softirq(Bottom-half disable)、in_serving_softirq,凡是处于这几种状态下,软中断都不会被执行;2)local_softirq_pending不为0,表明有软中断处理请求;
invoke_softirq函数中,根据中断处理是否线程化进行分类处理,如果中断已经进行了强制线程化处理(中断强制线程化,需要在启动的时候传入参数threadirqs),那么直接通过wakeup_softirqd唤醒内核线程来执行,否则的话则调用__do_softirq函数来处理;
Linux内核会为每个CPU都创建一个内核线程ksoftirqd,通过smpboot_register_percpu_thread函数来完成,其中当内核线程运行时,在满足条件的情况下会执行run_ksoftirqd函数,如果此时有软中断处理请求,调用__do_softirq来进行处理;
核心处理
asmlinkage __visible void __do_softirq(void) { unsigned long end = jiffies + MAX_SOFTIRQ_TIME;//软中断处理时间小于两毫秒; unsigned long old_flags = current->flags; int max_restart = MAX_SOFTIRQ_RESTART;//跳转到restart循环的次数不大于10次; struct softirq_action *h; bool in_hardirq; __u32 pending; int softirq_bit; /* * Mask out PF_MEMALLOC s current task context is borrowed for the * softirq. A softirq handled such as network RX might set PF_MEMALLOC * again if the socket is related to swap */ current->flags &= ~PF_MEMALLOC; pending = local_softirq_pending();//读取__softirq_pending字段,用于判断是否有处理请求 //可以类比于设备驱动中的状态寄存器,用于判断是否有软中断处理请求 account_irq_enter_time(current); __local_bh_disable_ip(_RET_IP_, SOFTIRQ_OFFSET);//关闭button-half in_hardirq = lockdep_softirq_start(); restart: /* Reset the pending bitmask before enabling irqs */ set_softirq_pending(0);//将__softirq_pending字段清零 local_irq_enable();//关闭本地中断 h = softirq_vec; while ((softirq_bit = ffs(pending))) {//软中断处理 //循环读取状态位,直到处理完每一个软中断请求 unsigned int vec_nr; int prev_count; h += softirq_bit - 1; vec_nr = h - softirq_vec; prev_count = preempt_count(); kstat_incr_softirqs_this_cpu(vec_nr); trace_softirq_entry(vec_nr); h->action(h); trace_softirq_exit(vec_nr); if (unlikely(prev_count != preempt_count())) { pr_err("huh, entered softirq %u %s %p with preempt_count %08x, exited with %08x?\n", vec_nr, softirq_to_name[vec_nr], h->action, prev_count, preempt_count()); preempt_count_set(prev_count); } h++; pending >>= softirq_bit; } /* 跳出while循环之后,再一次判断是否又有新的软中断请求到来(由于它可能被中断打断 ,也就意味着可能有新的请求到来),有新的请求到来,则有三个条件判断,满足的话跳 转到restart处执行,否则调用wakeup_sotfirqd来唤醒内核线程来处理: */ rcu_bh_qs(); local_irq_disable();//打开本地中断 pending = local_softirq_pending();//判断是否有新的请求到来 if (pending) { if (time_before(jiffies, end) && !need_resched() && --max_restart) /* time_before(jiffies, MAX_SOFTIRQ_TIME),软中断处理时间小于两毫秒; !need_resched,当前没有进程调度的请求 */ goto restart;//跳转到restart wakeup_softirqd();//唤醒内核线程来处理 } lockdep_softirq_end(in_hardirq); account_irq_exit_time(current); __local_bh_enable(SOFTIRQ_OFFSET);//打开button_half WARN_ON_ONCE(in_interrupt()); tsk_restore_flags(current, old_flags, PF_MEMALLOC); }
local_softirq_pending函数用于读取__softirq_pending字段,可以类比于设备驱动中的状态寄存器,用于判断是否有软中断处理请求;
软中断处理时会关闭Bottom-half,处理完后再打开;
软中断处理时,会打开本地中断,处理完后关闭本地中断
time_before(jiffies, MAX_SOFTIRQ_TIME),软中断处理时间小于两毫秒;
!need_resched,当前没有进程调度的请求;
max_restart = MAX_SOFTIRQ_RESTART,跳转到restart循环的次数不大于10次;
这三个条件的判断,是基于延迟和公平的考虑,既要保证软中断尽快处理,又不能让软中断处理一直占据系统。
软中断的触发
软中断的触发通过raise_softirq接口:
void raise_softirq(unsigned int nr) { unsigned long flags; local_irq_save(flags);//关闭本地中断 raise_softirq_irqoff(nr); local_irq_restore(flags);//打开本地中断 }
inline void raise_softirq_irqoff(unsigned int nr) { __raise_softirq_irqoff(nr); /* * If we're in an interrupt or softirq, we're done * (this also catches softirq-disabled code). We will * actually run the softirq once we return from * the irq or softirq. * * Otherwise we wake up ksoftirqd to make sure we * schedule the softirq soon. */ if (!in_interrupt())//如果不在中断上下文中,那么唤醒内核线程来处理软中断 wakeup_softirqd(); }
raise_softirq_irqoff函数中,最终会调用到or_softirq_pending,该函数会去读取本地CPU的irq_stat中__softirq_pending字段,然后将对应的软中断号给置位,表明有该软中断的处理请求
void __raise_softirq_irqoff(unsigned int nr) { trace_softirq_raise(nr); or_softirq_pending(1UL << nr);//将__softirq_pending字段的比特位置位,请求软中断处理 }
raise_softirq_irqoff函数中,最终会调用到or_softirq_pending,该函数会去读取本地CPU的irq_stat中__softirq_pending字段,然后将对应的软中断号给置位,表明有该软中断的处理请求