文章目錄
- 進程的preempt_count變量
- thread_info
- preempt_count
- hardirq相關
- softirq相關
- 上下文
原文鏈接: https://zhuanlan.zhihu.com/p/88883239
進程的preempt_count變量
thread_info
在內核中,上下文的設置和判斷接口可以參考 include/linux/preempt.h 文件,整個機制的實現都依賴于一個變量:preempt_count,這個變量被定義在進程struct task_struct的 thread_info域中 ,也就是線程描述符中,線程描述符被放在內核棧的底部,在內核中可以通過 current_thread_info() 接口來獲取進程的 thread_info:
static inline struct thread_info *current_thread_info(void)
{return (struct thread_info *)(current_stack_pointer & ~(THREAD_SIZE - 1));
}register unsigned long current_stack_pointer asm ("sp");
其中 THREAD_SIZE 通常的大小為 4K 或者 8K,取出當前內核棧的 sp 指針,通過簡單地屏蔽掉 sp 的低 13 位,就可以獲取到 thread_info 的基地址。
在 arm 中,preempt_count 是 per task 的變量,而在 x86 中,preempt_count 是 percpu 類型的變量。
preempt_count
作為控制上下文的變量, preempt_count 是 int 型,一共 32 位。通過設置該變量不同的位來設置內核中的上下文標志,包括硬中斷上下文、軟中斷上下文、進程上下文等,通過判斷該變量的值就可以判斷當前程序所屬的上下文狀態。
整個 preempt_count 被分為幾個部分:
- bit 0-7:用于記錄關閉調度的次數。中斷上下文中,調度是關閉的,不會發生進程的切換,可以使用preempt_disable()來顯示地關閉調度,關閉次數由第0到7個bits組成的preemption count(注意不是preempt count)來記錄。每使用一次preempt_disable(),preemption count的值就會加1,使用preempt_enable()則會讓preemption count的值減1。
- bit 8-15:描述軟中斷的標志位,如果softirq count的值為正數,說明現在正處于softirq上下文中,因為軟中斷在單個CPU上是不會嵌套執行的,所以只需要用第8位就可以用來判斷當前是否處于軟中斷的上下文中,其他的9-15位用于記錄關閉軟中斷的次數
- bit 16-19:描述硬中斷嵌套次數,在老版本的linux 上支持中斷的嵌套,但是自從 2.6 版本之后內核就不再支持中斷嵌套,所以其實只用到了一位,如果這部分為正數表示在硬件中斷上下文,為 0 則表示不在。
- bit20 :用于指示 NMI 中斷,只有兩個狀態:發生并處理 NMI 中斷置 1,退出中斷清除。
- 其它 bit :沒有使用到,保留
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"。
#define hardirq_count() (preempt_count() & HARDIRQ_MASK)
#define in_irq() (hardirq_count())
hardirq count占據4個bits,理論上可以表示16層嵌套,但現在Linux系統并不支持hardirq的嵌套執行,所以實際使用的只有1個bit。
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上下文:
#define softirq_count() (preempt_count() & SOFTIRQ_MASK)
#define in_softirq() (softirq_count())
上下文
不管是hardirq上下文還是softirq上下文,都屬于我們俗稱的中斷上下文(interrupt context)。
為此,有一個名為in_interrupt()的宏專門用來判斷當前是否在中斷上下文中。
#define irq_count() (preempt_count() & (HARDIRQ_MASK | SOFTIRQ_MASK | NMI_MASK))
#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上下文的關系大概可以表示成這樣: