最近看各種上下文,發現和ThreadInfo中的preemption字段密切,於是便調查了下。
看下Linux源碼中的注釋:
/*
* We put the hardirq and softirq counter into the preemption
* counter. The bitmask has the following meaning:
*
* - bits 0-7 are the preemption count (max preemption depth: 256)
* - bits 8-15 are the softirq count (max # of softirqs: 256)
*
* The hardirq count can in theory reach the same as NR_IRQS.
* In reality, the number of nested IRQS is limited to the stack
* size as well. For archs with over 1000 IRQS it is not practical
* to expect that they will all nest. We give a max of 10 bits for
* hardirq nesting. An arch may choose to give less than 10 bits.
* m68k expects it to be 8.
*
* - bits 16-25 are the hardirq count (max # of nested hardirqs: 1024)
* - bit 26 is the NMI_MASK
* - bit 27 is the PREEMPT_ACTIVE flag
*
* PREEMPT_MASK: 0x000000ff
* SOFTIRQ_MASK: 0x0000ff00
* HARDIRQ_MASK: 0x03ff0000
* NMI_MASK: 0x04000000
*/
這里其實把preempt_count划分成了四部分:搶占計數器、軟中斷計數、硬件中斷計數、NMI計數。
搶占計數器:0-7位
軟中斷計數器:8-15位
硬中斷計數器:16-25位
NMI標識:26位
基於上面的信息,看下Linux內核中判斷各個上下文的宏:
#define hardirq_count() (preempt_count() & HARDIRQ_MASK) #define softirq_count() (preempt_count() & SOFTIRQ_MASK) #define irq_count() (preempt_count() & (HARDIRQ_MASK | SOFTIRQ_MASK | NMI_MASK))
可以看到這里判斷硬件中斷/軟中斷/中斷上下文的實質,就是根據thread_info結構中的preempt_count字段,額忘記列出preempt_count()的實現了:#define preempt_count() (current_thread_info()->preempt_count),這時明白了吧!!!
不過后面幾個掩碼的計算有點繞,參考下源碼:
#define __IRQ_MASK(x) ((1UL << (x))-1) #define PREEMPT_MASK (__IRQ_MASK(PREEMPT_BITS) << PREEMPT_SHIFT) #define SOFTIRQ_MASK (__IRQ_MASK(SOFTIRQ_BITS) << SOFTIRQ_SHIFT) #define HARDIRQ_MASK (__IRQ_MASK(HARDIRQ_BITS) << HARDIRQ_SHIFT) #define NMI_MASK (__IRQ_MASK(NMI_BITS) << NMI_SHIFT)
實質上就是取對應的位。
* * Are we doing bottom half or hardware interrupt processing? * Are we in a softirq context? Interrupt context? * in_softirq - Are we currently processing softirq or have bh disabled? * in_serving_softirq - Are we currently processing softirq? */ #define in_irq() (hardirq_count()) #define in_softirq() (softirq_count()) #define in_interrupt() (irq_count()) #define in_serving_softirq() (softirq_count() & SOFTIRQ_OFFSET)
而這里只是對上面各種計數的包裝,只要對應的計數不為0,則表示在對應的上下文中。in_interrupt表示中斷上下文,包括了硬件中斷、軟中斷和NMI中斷。in_serving_softirq判斷是否當前正在處理軟中斷。
#define in_nmi() (preempt_count() & NMI_MASK)
in_nmi判斷當前是否在NMI中斷上下文。
既然如此,咱們再結合內核搶占調度的情況,看下搶占調度函數
asmlinkage void __sched notrace preempt_schedule(void) { struct thread_info *ti = current_thread_info(); /* * If there is a non-zero preempt_count or interrupts are disabled, * we do not want to preempt the current task. Just return.. */ if (likely(ti->preempt_count || irqs_disabled())) return; do { add_preempt_count_notrace(PREEMPT_ACTIVE); __schedule(); sub_preempt_count_notrace(PREEMPT_ACTIVE); /* * Check again in case we missed a preemption opportunity * between schedule and now. */ barrier(); } while (need_resched()); }
可以看到這里進入函數起始,便對當前線程的preempt_count進行了判斷,根據上面的介紹,當內核處理任何一個上下文中時,preempt_count均不可能為0,所以我們也可以根據此發現在軟中斷、硬件中斷、禁止內核搶占、NMI上下文中均不允許調度。當然,我們看到后面后又個irqs_disabled,意味着如果當前禁用了硬件中斷,則同樣不會發生搶占調度。
補充:
關閉中斷的實質是設置EFLAGS寄存器的IF標志位,CLI指令可執行這一操作,當標志位被設置為0時,表示當前關閉中斷狀態,而STI指令開中斷,二者匹配使用。這里的中斷指的是可屏蔽的硬件中斷。
參考資料:
LInux3.10.1內核源碼