文章目录
- 进程的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上下文的关系大概可以表示成这样: