preempt_count:等于0表示可抢占,大于0表示不可抢占,小于0表示bug。
源代码文件:
include/linux/preempt.h
include/asm-generic/preempt.h
include/linux/preempt_mask.h
linux系统在运行时,总会处于某一种特定的上下文中,例如,进程上下文,中断上下文等。为了判断系统当前运行的上下文状态,内核提供了preempt_count变量来记录当前系统运行的上下文信息。
注意,对于preempt_count的操作一般都是在最外层包了一层皮来关闭中断的情况下进行的,操作完成后开启中断。
1、数据结构
抢占计数器preempt_count变量实现和体系结构相关的,比如x86的preempt_count实现为percpu变量,而arm的preempt_count记录在当前进程的进程描述符中。
arch/x86/include/asm/preempt.h
DECLARE_PER_CPU(int, __preempt_count);
arch/arm/include/asm/thread_info.h
struct thread_info {
/*底层使用的标志位*/
unsigned long flags; /* low level flags */
/*等于0表示可抢占,大于0表示不可抢占,小于0表示为bug*/
int preempt_count; /* 0 => preemptable, <0 => bug */
/*地址大小的范围*/
mm_segment_t addr_limit; /* address limit */
/*进程核心结构*/
struct task_struct *task; /* main task structure */
/*可执行域*/
struct exec_domain *exec_domain; /* execution domain */
__u32 cpu; /* cpu */
__u32 cpu_domain; /* cpu domain */
/*硬件上下文*/
struct cpu_context_save cpu_context; /* cpu context */
/*系统调用号*/
__u32 syscall; /* syscall number */
__u8 used_cp[16]; /* thread used copro */
unsigned long tp_value[2]; /* TLS registers */
#ifdef CONFIG_CRUNCH
struct crunch_state crunchstate;
#endif
union fp_state fpstate __attribute__((aligned(8)));
union vfp_state vfpstate;
#ifdef CONFIG_ARM_THUMBEE
unsigned long thumbee_state; /* ThumbEE Handler Base register */
#endif
};
2、preempt_count字段含义
/*
* 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
*/
#define PREEMPT_BITS 8
#define SOFTIRQ_BITS 8
#define HARDIRQ_BITS 4
#define NMI_BITS 1
#define PREEMPT_SHIFT 0
#define SOFTIRQ_SHIFT (PREEMPT_SHIFT + PREEMPT_BITS) //8
#define HARDIRQ_SHIFT (SOFTIRQ_SHIFT + SOFTIRQ_BITS) //16
#define NMI_SHIFT (HARDIRQ_SHIFT + HARDIRQ_BITS) //20
#define __IRQ_MASK(x) ((1UL << (x))-1)
/*各个域mask值*/
#define PREEMPT_MASK (__IRQ_MASK(PREEMPT_BITS) << PREEMPT_SHIFT) //(1<<8)-1=0xff
#define SOFTIRQ_MASK (__IRQ_MASK(SOFTIRQ_BITS) << SOFTIRQ_SHIFT) //((1<<8)-1)<<8 = 0xff<<8 = 0xff00
#define HARDIRQ_MASK (__IRQ_MASK(HARDIRQ_BITS) << HARDIRQ_SHIFT) //((1<<4)-1)<<16 = 0xf<<16 = 0xf0000
#define NMI_MASK (__IRQ_MASK(NMI_BITS) << NMI_SHIFT) //((1<<1)-1)<<20 = 0x1<<20 = 0x100000
#define PREEMPT_OFFSET (1UL << PREEMPT_SHIFT) //1UL<<0=0x1
/*由于软中断在单个cpu上不会嵌套执行,因此仅第8位就可以用来判断当前是否处于软中断上下文中,而其它的9~15位用于表示是否禁用了中断下半部*/
#define SOFTIRQ_OFFSET (1UL << SOFTIRQ_SHIFT) //1UL<<8 = 0x100
#define HARDIRQ_OFFSET (1UL << HARDIRQ_SHIFT) //1UL<<16 = 0x10000
#define NMI_OFFSET (1UL << NMI_SHIFT) //1UL<<20 = 0x100000
/*用于中断下半部的开启/关闭*/
#define SOFTIRQ_DISABLE_OFFSET (2 * SOFTIRQ_OFFSET ) //0x200
#define PREEMPT_ACTIVE_BITS 1
#define PREEMPT_ACTIVE_SHIFT (NMI_SHIFT + NMI_BITS) //21
/*所有的有效位,NMI_MASK|HARDIRQ_MASK|SOFTIRQ_MASK|PREEMPT_MASK (0x1fffff);~0x200000=0xffcfffff*/
#define PREEMPT_ACTIVE (__IRQ_MASK(PREEMPT_ACTIVE_BITS) << PREEMPT_ACTIVE_SHIFT) //((1<<1)-1)<<21 = 0x1<<21 = 0x200000
#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))
/*
* 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())
/*是否位于BH(下半部)临界区或正在处理软中断*/
#define in_softirq() (softirq_count())
/*是否位于NMI、硬中断上下文、软中断上下文,或位于BH(下半部)临界区*/
#define in_interrupt() (irq_count())
/*是否位于软中断上下文*/
#define in_serving_softirq() (softirq_count() & SOFTIRQ_OFFSET)
/*
* Are we in NMI context?
*/
/*NMI(不可屏蔽)中断上下文*/
#define in_nmi() (preempt_count() & NMI_MASK)
/*
* Are we running in atomic context? WARNING: this macro cannot
* always detect atomic context; in particular, it cannot know about
* held spinlocks in non-preemptible kernels. Thus it should not be
* used in the general case to determine whether sleeping is possible.
* Do not use in_atomic() in driver code.
*/
/*当前状态是否处于中断上下文或禁止抢占状态,如果是,说明系统运行在原子上下文中(atomic context)*/
#define in_atomic() ((preempt_count() & ~PREEMPT_ACTIVE) != 0)
3、核心API说明
#define PREEMPT_ENABLED (0)
static __always_inline int preempt_count(void)
{
return current_thread_info()->preempt_count;
}
static __always_inline int *preempt_count_ptr(void)
{
return ¤t_thread_info()->preempt_count;
}
static __always_inline void preempt_count_set(int pc)
{
*preempt_count_ptr() = pc;
}
static __always_inline void __preempt_count_add(int val)
{
*preempt_count_ptr() += val;
}
static __always_inline void __preempt_count_sub(int val)
{
*preempt_count_ptr() -= val;
}
#define preempt_count_add(val) __preempt_count_add(val)
#define preempt_count_sub(val) __preempt_count_sub(val)
#define preempt_count_dec_and_test() __preempt_count_dec_and_test()
#define __preempt_count_inc() __preempt_count_add(1)
#define __preempt_count_dec() __preempt_count_sub(1)
#define preempt_count_inc() preempt_count_add(1)
#define preempt_count_dec() preempt_count_sub(1)
include/linux/preempt.h
常用API说明
preempt_enable() decrement the preempt counter
preempt_disable() increment the preempt counter
preempt_enable_no_resched() decrement, but do not immediately preempt
preempt_check_resched() if needed, reschedule
preempt_count() return the preempt counter
/*preempt_diable:preempt_count的计数 +1,(preempt_count:0,表示可以抢占,>0:不可抢占,<0:bug)*/
#define preempt_disable() \
do { \
preempt_count_inc(); \
barrier(); \
} while (0)
/*preempt_enable,不进行抢占调度*/
#define sched_preempt_enable_no_resched() \
do { \
barrier(); \
preempt_count_dec(); \
} while (0)
#define preempt_enable_no_resched() sched_preempt_enable_no_resched()
#ifdef CONFIG_PREEMPT
/*preempt_enable:preempt_count的计数 -1*/
#define preempt_enable() \
do { \
barrier(); \
if (unlikely(preempt_count_dec_and_test())) \
__preempt_schedule();/*抢占调度*/ \
} while (0)
#define preempt_check_resched() \
do { \
if (should_resched()) \
__preempt_schedule(); \
} while (0)
#else
#define preempt_enable() \
do { \
barrier(); \
preempt_count_dec(); \
} while (0)
#define preempt_check_resched() do { } while (0)
#endif
中断下半部开启和关闭
#define SOFTIRQ_DISABLE_OFFSET (2 * SOFTIRQ_OFFSET ) //0x200
static inline void local_bh_enable(void)
{
__local_bh_enable_ip(_THIS_IP_, SOFTIRQ_DISABLE_OFFSET);
}
void __local_bh_enable_ip(unsigned long ip, unsigned int cnt)
{
WARN_ON_ONCE(in_irq() || irqs_disabled());
#ifdef CONFIG_TRACE_IRQFLAGS
local_irq_disable();
#endif
/*
* Are softirqs going to be turned on now:
*/
if (softirq_count() == SOFTIRQ_DISABLE_OFFSET)
trace_softirqs_on(ip);
/*
* Keep preemption disabled until we are done with softirq processing:
* 保持禁止抢占,直到完成软件中断(softirq)处理完成。所以先减去(cnt-1),最后在调用preempt_count_dec再减一。
*/
preempt_count_sub(cnt - 1);
if (unlikely(!in_interrupt() && local_softirq_pending())) {
/*
* Run softirq if any pending. And do it in its own stack
* as we may be calling this deep in a task call stack already.
*/
do_softirq(); /*执行软中断*/
}
preempt_count_dec();
#ifdef CONFIG_TRACE_IRQFLAGS
local_irq_enable();
#endif
preempt_check_resched();
}
static inline void local_bh_disable(void)
{
__local_bh_disable_ip(_THIS_IP_, SOFTIRQ_DISABLE_OFFSET);
}
void __local_bh_disable_ip(unsigned long ip, unsigned int cnt)
{
unsigned long flags;
WARN_ON_ONCE(in_irq());
raw_local_irq_save(flags);
/*
* The preempt tracer hooks into preempt_count_add and will break
* lockdep because it calls back into lockdep after SOFTIRQ_OFFSET
* is set and before current->softirq_enabled is cleared.
* We must manually increment preempt_count here and manually
* call the trace_preempt_off later.
*/
__preempt_count_add(cnt);
/*
* Were softirqs turned off above:
*/
if (softirq_count() == (cnt & SOFTIRQ_MASK))
trace_softirqs_off(ip);
raw_local_irq_restore(flags);
if (preempt_count() == cnt) {
#ifdef CONFIG_DEBUG_PREEMPT
current->preempt_disable_ip = get_parent_ip(CALLER_ADDR1);
#endif
trace_preempt_off(CALLER_ADDR0, get_parent_ip(CALLER_ADDR1));
}
}