1.和軟中斷相關的數據結構:
softing_vec數組(kernel/softirq.c)
1 static struct softirq_action softirq_vec[NR_SOFTIRQS] __cacheline_aligned_in_smp;
NR_SOFTIRQS值為10,說明內核支持10個軟中斷函數。
softirq_action結構體(include/linux/interrupt.h)
1 struct softirq_action2 {3 void (*action)(struct softirq_action *);4 };
action是函數指針變量,指向了某個軟中斷函數。
irq_cpustat_t結構體(arch/x86/include/asm/hardirq.h)
1 typedef struct { 2 unsigned int __softirq_pending; 3 unsigned int __nmi_count; /* arch dependent */ 4 #ifdef CONFIG_X86_LOCAL_APIC 5 unsigned int apic_timer_irqs; /* arch dependent */ 6 unsigned int irq_spurious_count; 7 unsigned int icr_read_retry_count; 8 #endif 9 #ifdef CONFIG_HAVE_KVM10 unsigned int kvm_posted_intr_ipis;11 #endif12 unsigned int x86_platform_ipis; /* arch dependent */13 unsigned int apic_perf_irqs;14 unsigned int apic_irq_work_irqs;15 #ifdef CONFIG_SMP16 unsigned int irq_resched_count;17 unsigned int irq_call_count;18 /*19 * irq_tlb_count is double-counted in irq_call_count, so it must be20 * subtracted from irq_call_count when displaying irq_call_count21 */22 unsigned int irq_tlb_count;23 #endif24 #ifdef CONFIG_X86_THERMAL_VECTOR25 unsigned int irq_thermal_count;26 #endif27 #ifdef CONFIG_X86_MCE_THRESHOLD28 unsigned int irq_threshold_count;29 #endif30 #if IS_ENABLED(CONFIG_HYPERV) || defined(CONFIG_XEN)31 unsigned int irq_hv_callback_count;32 #endif33 } ____cacheline_aligned irq_cpustat_t;
每個cpu都有一個這樣的結構體變量,在軟中斷中,我們要使用的是第2行的成員,32位的軟中斷掩碼。當有一個軟中斷被掛起(將要被執行)的時候,會設置該掩碼中的相應位。
2.軟中斷的執行過程
首先使用open_softirq()函數注冊軟中斷函數,代碼如下(kernel/softirq.c):
1 void open_softirq(int nr, void (*action)(struct softirq_action *))2 {3 softirq_vec[nr].action = action;4 }
將軟中斷函數指針action存入softirq_vec數組的對應元素中。
接著,使用raise_softirq()激活軟中斷,代碼如下(kernel/softirq.c):
1 void raise_softirq(unsigned int nr)2 {3 unsigned long flags;4 5 local_irq_save(flags);6 raise_softirq_irqoff(nr);7 local_irq_restore(flags);8 }
第5行關閉本地中斷,第7行恢復中斷。第6行激活nr所對應的軟中斷函數。接著分析 raise_softirq_irqoff(),代碼如下(kernel/softirq.c):
1 inline void raise_softirq_irqoff(unsigned int nr) 2 { 3 __raise_softirq_irqoff(nr); 4 5 /* 6 * If we're in an interrupt or softirq, we're done 7 * (this also catches softirq-disabled code). We will 8 * actually run the softirq once we return from 9 * the irq or softirq.10 *11 * Otherwise we wake up ksoftirqd to make sure we12 * schedule the softirq soon.13 */14 if (!in_interrupt())15 wakeup_softirqd();16 }
第3行__raise_softirq_irqoff函數設置了軟中斷掩碼的相應位,代碼如下(kernel/softirq.c)。然后第14行判斷軟中斷是否已經激活或者被禁用,如果沒有,那么在15行激活內核線程ksoftirq,去執行軟中斷。
1 void (unsigned int nr)2 {3 trace_softirq_raise(nr);4 or_softirq_pending(1UL << nr);5 }
具體而言,在第4行的函數中設置掩碼位。
3.下面分析下在哪些地方都可以進入軟中斷。
第一個地方,當然就是上邊所提到的,內核線程ksoftirq被激活的時候。下面看看ksoftirq線程(kernel/softirq.c)。
1 DEFINE_PER_CPU(struct task_struct *, ksoftirqd);
給每個cpu都定義一個指向struct task_struct類型的結構體變量,很顯然,該變量存的是ksoftirq線程的進程描述符。(由此也說明,linux的線程和進程是一個東西)
接著,我們要看看ksoftirq線程要執行的函數(kernel/softirq.c)。
1 static void run_ksoftirqd(unsigned int cpu) 2 { 3 local_irq_disable(); 4 if (local_softirq_pending()) { 5 /* 6 * We can safely run softirq on inline stack, as we are not deep 7 * in the task stack here. 8 */ 9 __do_softirq();10 rcu_note_context_switch(cpu);11 local_irq_enable();12 cond_resched();13 return;14 }15 local_irq_enable();16 }
該函數就是ksoftirq線程的線程體。第9行__do_softirq()中調用所有軟中斷函數。
我們回過頭來再分析下wakeup_softirqd(),看看ksoftirq線程怎樣被喚醒(kernel/softirq.c)。
1 static void wakeup_softirqd(void)2 {3 /* Interrupts are disabled: no need to stop PReemption */4 struct task_struct *tsk = __this_cpu_read(ksoftirqd);5 6 if (tsk && tsk->state != TASK_RUNNING)7 wake_up_process(tsk);8 }
第4行把本地cpu的ksoftirq線程的描述符讀到tsk變量中,第6行中判斷ksoftirq線程如果沒有運行的話,第7行喚醒該線程。
第二個地方,中斷處理程序do_IRQ完成處理或者調用irq_exit函數時。下面看看irq_exit代碼(kernel/softirq.c)。
1 void irq_exit(void) 2 { 3 #ifndef __ARCH_IRQ_EXIT_IRQS_DISABLED 4 local_irq_disable(); 5 #else 6 WARN_ON_ONCE(!irqs_disabled()); 7 #endif 8 9 account_irq_exit_time(current);10 preempt_count_sub(HARDIRQ_OFFSET);11 if (!in_interrupt() && local_softirq_pending())12 invoke_softirq();13 14 tick_irq_exit();15 rcu_irq_exit();16 trace_hardirq_exit(); /* must be last! */17 }
不用我說了吧,我覺得你一眼就能瞄見了第12行??聪耰nvoke_softirq函數(kernel/softirq.c)。
1 static inline void invoke_softirq(void) 2 { 3 if (!force_irqthreads) { 4 #ifdef CONFIG_HAVE_IRQ_EXIT_ON_IRQ_STACK 5 /* 6 * We can safely execute softirq on the current stack if 7 * it is the irq stack, because it should be near empty 8 * at this stage. 9 */10 __do_softirq();11 #else12 /*13 * Otherwise, irq_exit() is called on the task stack that can14 * be potentially deep already. So call softirq in its own stack15 * to prevent from any overrun.16 */17 do_softirq_own_stack();18 #endif19 } else {20 wakeup_softirqd();21 }22 }
第3行force_irqthreads值為0,所以該函數也調用了__do_softirq()來執行軟中斷。
還有幾處地方暫時不分析了,以后有空補上。
4.下面來看下__do_softirq()函數(kernel/softirq.c)。
1 asmlinkage __visible void __do_softirq(void) 2 { 3 unsigned long end = jiffies + MAX_SOFTIRQ_TIME; 4 unsigned long old_flags = current->flags; 5 int max_restart = MAX_SOFTIRQ_RESTART; 6 struct softirq_action *h; 7 bool in_hardirq; 8 __u32 pending; 9 int softirq_bit;10 11 /*12 * Mask out PF_MEMALLOC s current task context is borrowed for the13 * softirq. A softirq handled such as network RX might set PF_MEMALLOC14 * again if the socket is related to swap15 */16 current->flags &= ~PF_MEMALLOC;17 18 pending = local_softirq_pending();19 account_irq_enter_time(current);20 21 __local_bh_disable_ip(_RET_IP_, SOFTIRQ_OFFSET);22 in_hardirq = lockdep_softirq_start();23 24 restart:25 /* Reset the pending bitmask before enabling irqs */26 set_softirq_pending(0);27 28 local_irq_enable();29 30 h = softirq_vec;31 32 while ((softirq_bit = ffs(pending))) {33 unsigned int vec_nr;34 int prev_count;35 36 h += softirq_bit - 1;37 38 vec_nr = h - softirq_vec;39 prev_count = preempt_count();40 41 kstat_incr_softirqs_this_cpu(vec_nr);42 43 trace_softirq_entry(vec_nr);44 h->action(h);45 trace_softirq_exit(vec_nr);46 if (unlikely(prev_count != preempt_count())) {47 pr_err("huh, entered softirq %u %s %p with preempt_count %08x, exited with %08x?/n",48 vec_nr, softirq_to_name[vec_nr], h->action,49 prev_count, preempt_count());50 preempt_count_set(prev_count);51 }52 h++;53 pending >>= softirq_bit;54 }55 56 rcu_bh_qs(smp_processor_id());57 local_irq_disable();58 59 pending = local_softirq_pending();60 if (pending) {61 if (time_before(jiffies, end) && !need_resched() &&62 --max_restart)63 goto restart;64 65 wakeup_softirqd();66 }67 68 lockdep_softirq_end(in_hardirq);69 account_irq_exit_time(current);70 __local_bh_enable(SOFTIRQ_OFFSET);71 WARN_ON_ONCE(in_interrupt());72 tsk_restore_flags(current, old_flags, PF_MEMALLOC);73 }
在該函數中循環調用的所有的被激活的軟中斷函數。第5行MAX_SOFTIRQ_RESTART值為10,表示最多循環10次(不能讓其他進程等太久),第32行獲取pending表中第一被設置的比特位,第44行開始執行設置過的軟中斷函數。第53行對pending進行右移運算,然后進入下次循環。直到將本輪所有已設置的軟中斷函數全部執行完,退出循環。第59行重新獲得本地cpu的軟中斷掩碼,第61行如果時間沒有超出end而且沒有出現更高優先級的進程并且10次尋環未用完,那么跳回restart,重新for循環。否則,第65行喚醒softirqd內核線程。然后退出本函數。
至此,軟中斷的處理過程就分析完了。
新聞熱點
疑難解答