Linux中的preempt_count - 知乎
https://www.cnblogs.com/hellokitty2/p/15652312.html
LWN:关于preempt_count()的四个小讨论!-CSDN博客
主要是参考这些文章
之前一直认为只要是in_interrupt()返回非0值,那么就可以认为当前在中断上下文。即使中一个内核线程里面in_interrupt返回非0值(比如local_bh_disable)
但是最近又有另外一种说法,in_interrupt(),in_softirq()这两个只有是真正的中断上半部以及软中断上下文才会返回非0值。在进程上下文中使用这两个函数一定会返回false或者是不应该在一个内核线程里面调用???(我这句话说的不太准确,具体是啥记不清楚了)。
其实我之前理解,类似于in_interrupt这些函数最终都是通过preempt count来判断的。只要这变量的值不为0,那就是非进程上下文,并不会因为调用的地方实际是在一个内核线程里面还是软中断处理流程里面而改变。只要是改变了preempt count的值,那就等同于改变了当前代码的一个上下文。
因此准备重新回顾一下preempt count的作用。
在像 Linux 这样的多任务系统中,没有任何一个线程可以保证它每次想运行的时候都能独占处理器。内核总是有能力(多数情况下)抢占一个正在运行的线程,而选择一个优先级更高的线程来执行。那个新线程可能是另一个不同的进程,但也可能是一个硬件中断,或者什么其他外部事件。为了正确地协调系统中所有任务能正确运行,内核必须跟踪当前的执行状态(execution state),包括已经被抢占或可能阻止线程被抢占的各种情况。
用来进行这个追踪记录的基础,就是在系统中每个任务里存储的 preemption counter。
这个 counter 可以用来指示当前线程的状态、它是否可以被抢占,以及它是否被允睡眠。要实现这个功能的话,就必须在这个 counter 里面记录若干种不同状态,因此这个 preempt_count 也被分成了几个字段(sub-fields):
#define PREEMPT_BITS 8
#define SOFTIRQ_BITS 8
#define HARDIRQ_BITS 4
#define NMI_BITS 1
preempt_count 这个成员被用来判断当前进程是否可以被抢占。如果 preempt_count 不等于0(可能是代码调用preempt_disable显式的禁止了抢占,也可能是处于中断上下文等),说明当前不能进行抢占,如果 preempt_count 等于0,说明已经具备了抢占的条件。
我记得书上还有一种说法,preempt_count可以看做当前进程加锁的次数。当该进程准备让出cpu时需要检查这个值是否为0。不能在只有锁的情况下进程任务调度(好像也不是很准确哈!!休眠锁不就可以在加锁情况下调度嘛)
preemption disable count(低8bit):用于记录当前进程被显示禁用抢占的次数(preempt_disable调用次数)。最多嵌套调用2^8次
下面代码假设内核支持抢占。可以看到preempt_disable就是修改的最后一个字节
#define preempt_disable() \
do { \
preempt_count_inc(); \
barrier(); \
} while (0)
#define __preempt_count_inc() __preempt_count_add(1)
static __always_inline void __preempt_count_add(int val)
{
*preempt_count_ptr() += val;
}
static __always_inline int *preempt_count_ptr(void)
{
return ¤t_thread_info()->preempt_count;
}
softirq_count:preempt_count中的第8到15个bit表示softirq count,它记录了进入softirq的嵌套次数。
可以看到在进入软中断时。就会先标识其已经进入了软中断上下文 __local_bh_disable_ip(_RET_IP_, SOFTIRQ_OFFSET);其修改的就是bit8开始的内容。
smlinkage __visible void __do_softirq(void)
{
..........................
__local_bh_disable_ip(_RET_IP_, SOFTIRQ_OFFSET);
.....................
}
#define PREEMPT_OFFSET (1UL << PREEMPT_SHIFT)
#define SOFTIRQ_OFFSET (1UL << SOFTIRQ_SHIFT)
#define HARDIRQ_OFFSET (1UL << HARDIRQ_SHIFT)
#define NMI_OFFSET (1UL << NMI_SHIFT)
static __always_inline void __local_bh_disable_ip(unsigned long ip, unsigned int cnt)
{
preempt_count_add(cnt);
barrier();
}
在同一个cpu上,软中断是串行执行的。(同一个软中断可以在不同的cpu上同时执行,tasklet除外)。因此如果只是为了标识是不是正在处理软中断,1个bit就行了。那么其他7个bit是干什么的呢?
另外的7bit为了记录,防止进程被softirq所抢占,关闭/禁止softirq的次数,比如每使用一次local_bh_disable(),softirq count高7个bits(bit 9到bit 15)的值就会加1,使用local_bh_enable()则会让softirq count高7个bits的的值减1。
可以看到local_bh_disable其实是修改的bit10开始的内容。因此另外7个bit可以认为是显示禁止软中断的嵌套次数
static inline void local_bh_disable(void)
{
__local_bh_disable_ip(_THIS_IP_, SOFTIRQ_DISABLE_OFFSET);
}
#define SOFTIRQ_DISABLE_OFFSET (2 * SOFTIRQ_OFFSET)
因此为了分清楚是正在处理软中断还是说进程里面显示禁止了软中断,我们只需要判断bit8就行(进入softirq是在softirq上下文,关闭softirq抢占也是在softirq上下文如何区分)
//这个能够判断是否是正在处理软中断
#define in_serving_softirq() (softirq_count() & SOFTIRQ_OFFSET)
#define SOFTIRQ_OFFSET (1UL << SOFTIRQ_SHIFT)// 1 << 8
#define SOFTIRQ_SHIFT (PREEMPT_SHIFT + PREEMPT_BITS)// 0 + 8
#define PREEMPT_BITS 8
#define PREEMPT_SHIFT 0
hardirq count:4bit,用于表示硬件中断嵌套的次数,最多可以嵌套16层(参考的文章说现在linux不支持中断嵌套)。当进入硬件中断时,会将其+1,退出-1。
#define __irq_enter() \
do { \
account_irq_enter_time(current); \
preempt_count_add(HARDIRQ_OFFSET); \
trace_hardirq_enter(); \
} while (0)
中断上下文:不管是hardirq和softirq都称为中断上下文。下图是抄自知乎。
其中正在处理软中断(进入do_softirq)或者是关闭softirq抢占(local_bh_enable)都属于软中断上下文。
正在处理中断上半部属于hardirq上下文(关闭中断属于中断上下文吗???感觉不是呢?local_irq_disable不会去修改preempt count的值呢,不理解这个是什么意思)
因此对于中断上下文的判断就是判断非bit0到bit7的值是否为非0
#define in_interrupt() (irq_count())
#define irq_count() (preempt_count() & (HARDIRQ_MASK | SOFTIRQ_MASK \
| NMI_MASK))//就是判断高bit8-bit20是否为非0,非0就是在中断上下文
是否在软中断上下文和硬中断上下文,同样也是判断对应的值
#define hardirq_count() (preempt_count() & HARDIRQ_MASK)
#define softirq_count() (preempt_count() & SOFTIRQ_MASK)
进程上下文判断:我的内核代码里面没有呢?(当前的内核版本是3.16,后面下了个5.4的里面就有这个了)。文章来源:https://www.toymoban.com/news/detail-728921.html
#define in_task() (!(preempt_count() & (HARDIRQ_MASK | SOFTIRQ_OFFSET | NMI_MASK)))
另外我感觉就是判断低8bit的值是否为0呢。如果我在一个进程里面显示禁用抢占,那in_task不就返回0了嘛?这个难道不是进程上下文??后面找一个支持抢占的内核试试文章来源地址https://www.toymoban.com/news/detail-728921.html
到了这里,关于关于preempt count的疑问的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!