【转载】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)

这个宏可以判断当前进程是否处于原子操作中。

 

posted @ 2022-04-06 19:09  张志伟122  阅读(207)  评论(0编辑  收藏  举报