【轉載】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"。
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上下文:
這篇文章曾提到:進入softirq是在softirq上下文,關閉softirq搶佔也是在softirq上下文,但還是有辦法區分的。辦法就是使用in_serving_softirq()巨集來確切地表示現在是在處理softirq。
上下文
不管是hardirq上下文還是softirq上下文,都屬於我們俗稱的中斷上下文(interrupt context)。
為此,有一個名為in_interrupt()的巨集專門用來判斷當前是否在中斷上下文中。
與中斷上下文相對應的就是俗稱的程序上下文(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上下文的關係大概可以表示成這樣:
- /*
- * low level task data that entry.S needs immediate access to.
- * __switch_to() assumes cpu_context follows immediately after cpu_domain.
- */
- struct thread_info {
- unsigned long flags; /* low level flags */
- mm_segment_t addr_limit; /* address limit */
- struct task_struct *task; /* main task structure */
- struct exec_domain *exec_domain; /* execution domain */
- struct restart_block restart_block;
- int preempt_count; /* 0 => preemptable, <0 => bug */
- int cpu; /* cpu */
- };
在支援可搶佔的系統中,一個程序的thread_info資訊定義如上。其中preempt_count代表的是該程序是否可以被搶佔,根據註釋的說明當peermpt_count等於0的時候當前程序可以被搶佔,當小於0存在bug,當大於0說明當前程序不可以被搶佔。比如當前程序在中斷上下文中或者使用了鎖。
- <linux/include/preempt_mask.h>
- ------------------------------------------
- /*
- * 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 could in theory be the same as the number of
- * interrupts in the system, but we run all interrupt handlers with
- * interrupts disabled, so we cannot have nesting interrupts. Though
- * there are a few palaeontologic drivers which reenable interrupts in
- * the handler, so we need more than one bit here.
- *
- * PREEMPT_MASK: 0x000000ff
- * SOFTIRQ_MASK: 0x0000ff00
- * HARDIRQ_MASK: 0x000f0000
- * NMI_MASK: 0x00100000
- * PREEMPT_ACTIVE: 0x00200000
- */
結合上述的示圖和程式碼的定義可知,bit0-7代表的是搶佔的次數,最大搶佔深度為256次,bit8-15代表的是軟中斷的次數,最大也是256次,bit16-19表示中斷的次數,註釋的大概意思是避免中斷巢狀,但是也不能防止某些驅動中斷巢狀使用中斷,所以巢狀16層也是最大次數了。bit20代表的NMI中斷,bit21代表當前搶佔是否active。
Linux系統為了方便得出各個欄位的值,提供了一系列巨集定義如下:
從上述的定義可以得出,如果想知道硬中斷的次數就使用hardirq_count,如果想知道中斷次數就使用softirq_count,如果想知道所有中斷的次數就使用irq_count。
- /*
- * 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?
- */
其中in_irq用於判斷當前程序是否在硬中斷中,in_softirq用於判斷是否當前程序在軟體中斷或者有別的程序disable了軟中斷,in_interrupt用於判斷當前程序是否在中斷中,而in_serving_softirq用於判斷當前程序是否在軟體中斷中,通過bit8這一位來判斷。
#define in_atomic() ((preempt_count() & ~PREEMPT_ACTIVE) != 0)
這個巨集可以判斷當前程序是否處於原子操作中。