1. 程式人生 > 其它 >【轉載】Linux中的preempt_count

【轉載】Linux中的preempt_count

 

版權宣告:本文為CSDN博主「tangyongxiang_cn」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處連結及本宣告。
原文連結:https://blog.csdn.net/tangyongxiang_cn/article/details/121704682

 

preempt_count本質上是一個per-CPU的32位變數

,它在各種處理器架構下的存放位置和命名不盡相同,但其值都可以使用preempt_count()函式統一獲取。preempt_count邏輯相關的核心程式碼位於include/linux/preempt.h,雖然只是一個32位變數,但由於其和中斷、排程/搶佔密切相關,因此在系統中發揮的作用不容小覷。

來看下preempt_count是怎樣構成的:

hardirq相關

preempt_count中的第16到19個bit表示hardirq count,它記錄了進入hardirq/top half的巢狀次數,在這篇文章介紹的do_IRQ()中,irq_enter()用於標記hardirq的進入,此時hardirq count的值會加1。irq_exit

()用於標記hardirq的退出,hardirq count的值會相應的減1。如果hardirq count的值為正數,說明現在正處於hardirq上下文中,程式碼中可藉助in_irq()巨集實現快速判斷。注意這裡的命名是"in_irq"而不是"in_hardirq"。

  1.   #define hardirq_count() (preempt_count() & HARDIRQ_MASK)
  2.   #define in_irq() (hardirq_count())

hardirq count佔據4個bits,理論上可以表示16層巢狀,但現在Linux系統並不支援hardirq的巢狀執行,所以實際使用的只有1個bit。

之所以採用4個bits,一是歷史原因,因為早期Linux並不是將中斷處理的過程分為top half和bottom half,而是將中斷分為fast interrupt handler和slow interrupt handler,而slow interrupt handler是可以巢狀執行的,二是某些 driver 程式碼可能在top half中重新使能hardirq。

softirq相關

preempt_count中的第8到15個bit表示softirq count

,它記錄了進入softirq的巢狀次數,如果softirq count的值為正數,說明現在正處於softirq上下文中。由於softirq在單個CPU上是不會巢狀執行的,因此和hardirq count一樣,實際只需要一個bit(bit 8)就可以了。但這裡多出的7個bits並不是因為歷史原因多出來的,而是另有他用。

這個"他用"就是表示在程序上下文中,為了防止程序被softirq所搶佔,關閉/禁止softirq的次數,比如每使用一次local_bh_disable(),softirq count高7個bits(bit 9到bit 15)的值就會加1,使用local_bh_enable()則會讓softirq count高7個bits的的值減1。

程式碼中可藉助in_softirq()巨集快速判斷當前是否在softirq上下文:

  1.   #define softirq_count() (preempt_count() & SOFTIRQ_MASK)
  2.   #define in_softirq() (softirq_count())

這篇文章曾提到:進入softirq是在softirq上下文,關閉softirq搶佔也是在softirq上下文,但還是有辦法區分的。辦法就是使用in_serving_softirq()巨集來確切地表示現在是在處理softirq。

  1.   #define SOFTIRQ_OFFSET (1UL << 8)
  2.   #define in_serving_softirq() (softirq_count() & SOFTIRQ_OFFSET)
上下文

不管是hardirq上下文還是softirq上下文,都屬於我們俗稱的中斷上下文(interrupt context)。

為此,有一個名為in_interrupt()的巨集專門用來判斷當前是否在中斷上下文中。

  1.   #define irq_count() (preempt_count() & (HARDIRQ_MASK | SOFTIRQ_MASK | NMI_MASK))
  2.   #define in_interrupt() (irq_count())

與中斷上下文相對應的就是俗稱的程序上下文(process context)

#define in_task()  (!(preempt_count() & (HARDIRQ_MASK | SOFTIRQ_OFFSET | NMI_MASK)))			   
 

需要注意的是,並不是只有程序才會處在process context,核心執行緒

依然可以執行在process context。

在中斷上下文中,排程是關閉的,不會發生程序的切換,這屬於一種隱式的禁止排程,而在程式碼中,也可以使用preempt_disable

()來顯示地關閉排程,關閉次數由第0到7個bits組成的preemption count(注意不是preempt count)來記錄。每使用一次preempt_disable(),preemption count

的值就會加1,使用preempt_enable()則會讓preemption count的值減1。preemption count佔8個bits,因此一共可以表示最多256層排程關閉的巢狀。

處於中斷上下文,或者顯示地禁止了排程,preempt_count()的值都不為0,都不允許睡眠/排程的發生,這兩種場景被統稱為atomic上下文,可由in_atomic()巨集給出判斷。

#define in_atomic()	(preempt_count() != 0)
 

中斷上下文、程序上下文和atomic上下文的關係大概可以表示成這樣:

  1.   /*
  2.   * low level task data that entry.S needs immediate access to.
  3.   * __switch_to() assumes cpu_context follows immediately after cpu_domain.
  4.   */
  5.   struct thread_info {
  6.   unsigned long flags; /* low level flags */
  7.   mm_segment_t addr_limit; /* address limit */
  8.   struct task_struct *task; /* main task structure */
  9.   struct exec_domain *exec_domain; /* execution domain */
  10.   struct restart_block restart_block;
  11.   int preempt_count; /* 0 => preemptable, <0 => bug */
  12.   int cpu; /* cpu */
  13.   };

在支援可搶佔的系統中,一個程序的thread_info資訊定義如上。其中preempt_count代表的是該程序是否可以被搶佔,根據註釋的說明當peermpt_count等於0的時候當前程序可以被搶佔,當小於0存在bug,當大於0說明當前程序不可以被搶佔。比如當前程序在中斷上下文中或者使用了鎖。

  1.   <linux/include/preempt_mask.h>
  2.   ------------------------------------------
  3.   /*
  4.   * We put the hardirq and softirq counter into the preemption
  5.   * counter. The bitmask has the following meaning:
  6.   *
  7.   * - bits 0-7 are the preemption count (max preemption depth: 256)
  8.   * - bits 8-15 are the softirq count (max # of softirqs: 256)
  9.   *
  10.   * The hardirq count could in theory be the same as the number of
  11.   * interrupts in the system, but we run all interrupt handlers with
  12.   * interrupts disabled, so we cannot have nesting interrupts. Though
  13.   * there are a few palaeontologic drivers which reenable interrupts in
  14.   * the handler, so we need more than one bit here.
  15.   *
  16.   * PREEMPT_MASK: 0x000000ff
  17.   * SOFTIRQ_MASK: 0x0000ff00
  18.   * HARDIRQ_MASK: 0x000f0000
  19.   * NMI_MASK: 0x00100000
  20.   * PREEMPT_ACTIVE: 0x00200000
  21.   */
  22.   #define PREEMPT_BITS 8
  23.   #define SOFTIRQ_BITS 8
  24.   #define HARDIRQ_BITS 4
  25.   #define NMI_BITS 1

結合上述的示圖和程式碼的定義可知,bit0-7代表的是搶佔的次數,最大搶佔深度為256次,bit8-15代表的是軟中斷的次數,最大也是256次,bit16-19表示中斷的次數,註釋的大概意思是避免中斷巢狀,但是也不能防止某些驅動中斷巢狀使用中斷,所以巢狀16層也是最大次數了。bit20代表的NMI中斷,bit21代表當前搶佔是否active。

Linux系統為了方便得出各個欄位的值,提供了一系列巨集定義如下:

  1.   #define PREEMPT_SHIFT 0
  2.   #define SOFTIRQ_SHIFT (PREEMPT_SHIFT + PREEMPT_BITS) //0+8=8
  3.   #define HARDIRQ_SHIFT (SOFTIRQ_SHIFT + SOFTIRQ_BITS) //8+8=16
  4.   #define NMI_SHIFT (HARDIRQ_SHIFT + HARDIRQ_BITS) //16+4=20
  5.    
  6.   #define __IRQ_MASK(x) ((1UL << (x))-1)
  7.    
  8.   #define PREEMPT_MASK (__IRQ_MASK(PREEMPT_BITS) << PREEMPT_SHIFT)
  9.   #define SOFTIRQ_MASK (__IRQ_MASK(SOFTIRQ_BITS) << SOFTIRQ_SHIFT)
  10.   #define HARDIRQ_MASK (__IRQ_MASK(HARDIRQ_BITS) << HARDIRQ_SHIFT)
  11.   #define NMI_MASK (__IRQ_MASK(NMI_BITS) << NMI_SHIFT)
  12.    
  13.   #define PREEMPT_OFFSET (1UL << PREEMPT_SHIFT) //1<<0
  14.   #define SOFTIRQ_OFFSET (1UL << SOFTIRQ_SHIFT) //1<<8
  15.   #define HARDIRQ_OFFSET (1UL << HARDIRQ_SHIFT) //1<<16
  16.   #define NMI_OFFSET (1UL << NMI_SHIFT) //1<<20
  17.    
  18.   #define SOFTIRQ_DISABLE_OFFSET (2 * SOFTIRQ_OFFSET) //16
  19.    
  20.   #define PREEMPT_ACTIVE_BITS 1
  21.   #define PREEMPT_ACTIVE_SHIFT (NMI_SHIFT + NMI_BITS)
  22.   #define PREEMPT_ACTIVE (__IRQ_MASK(PREEMPT_ACTIVE_BITS) << PREEMPT_ACTIVE_SHIFT)
  23.    
  24.   #define hardirq_count() (preempt_count() & HARDIRQ_MASK) //硬中斷count
  25.   #define softirq_count() (preempt_count() & SOFTIRQ_MASK) //軟中斷count
  26.   #define irq_count() (preempt_count() & (HARDIRQ_MASK | SOFTIRQ_MASK \
  27.   | NMI_MASK)) //所有中斷=硬+軟+NMI

從上述的定義可以得出,如果想知道硬中斷的次數就使用hardirq_count,如果想知道中斷次數就使用softirq_count,如果想知道所有中斷的次數就使用irq_count。

  1.   /*
  2.   * Are we doing bottom half or hardware interrupt processing?
  3.   * Are we in a softirq context? Interrupt context?
  4.   * in_softirq - Are we currently processing softirq or have bh disabled?
  5.   * in_serving_softirq - Are we currently processing softirq?
  6.   */
  7.   #define in_irq() (hardirq_count())
  8.   #define in_softirq() (softirq_count())
  9.   #define in_interrupt() (irq_count())
  10.   #define in_serving_softirq() (softirq_count() & SOFTIRQ_OFFSET)

其中in_irq用於判斷當前程序是否在硬中斷中,in_softirq用於判斷是否當前程序在軟體中斷或者有別的程序disable了軟中斷,in_interrupt用於判斷當前程序是否在中斷中,而in_serving_softirq用於判斷當前程序是否在軟體中斷中,通過bit8這一位來判斷。

#define in_atomic()	((preempt_count() & ~PREEMPT_ACTIVE) != 0)
 

這個巨集可以判斷當前程序是否處於原子操作中。