进程上下文和中断上下文


原文链接: 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上下文的关系大概可以表示成这样:
请添加图片描述

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值