并发(二)——linux内核同步机制分析

目录

一、内核产生并发的原因

二、上下文

三、同步

3.1 基本的同步手段

3.1.1  preempt_enable / preempt_disable

3.1.2 local_irq_enable / local_irq_disable

3.1.3 local_bh_enable / local_bh_disable

3.1.4 spin_lock / spin_unlock

3.2 同步手段的组合

四、参考


一、内核产生并发的原因

  • 中断 (hardirq, softirq,tasklet)
  • 多核
  • 内核抢占

必须选用合适的同步手段避免竟态,保护临界区。

二、上下文

  • 进程上下文:
  • 中断上下文:hardirq上下文,softirq上下文

三、同步

访问临界区时,要分析可能会由哪种并发原因产生静态,从而选择合适的同步手段,可以按照下面的方法分析:

1. 当前访问资源的执行上下文是什么

2. 哪一种或者哪几种并发情况下会产生静态(hardirq/softirq/smp/preempt)

3.1 基本的同步手段

我们先分析单一的原因,在考虑多种并发原因下的同步方法

3.1.1  preempt_enable / preempt_disable

  • preempt_enable()/preempt_disable()

一个task 的thread_info结构中有int preempt_count字段,其格式如下,参考【3】:

内核抢占是在中断返回时检查是否可以抢占,通常需要满足两个条件:

1. preempt_count 为0

static __always_inline int preempt_count(void)
{
	return READ_ONCE(current_thread_info()->preempt_count);
}

当preempt_count为0时允许抢占意味着在hardirq上下文,softirq上下文默认都是不能抢占的

2. thread info中的flag成员是否设定了TIF_NEED_RESCHED

#define tif_need_resched() test_thread_flag(TIF_NEED_RESCHED)

static __always_inline bool need_resched(void)
{
	return unlikely(tif_need_resched());
}

preempt_disable仅仅是将preempt_count的抢占字段+1,那么preempt_count()不为0就无法被抢占了。

在执行上下文仅可能本cpu线程抢占时,使用该手段

3.1.2 local_irq_enable / local_irq_disable

  • local_irq_enable()/local_irq_disable    使能或者关闭本地中断,hardirq

注意,这是针对单个CPU的指令,如在x86上,就是IF bit的置位或清除,使用的情况是有中断和当前执行线程进入临界区

在执行上下文仅可能被本地hardirq打断时,使用该手段,主要有:

1 .处于进程上下文中的执行线程:被hardirq中断打断访问共享资源

2. 处于softirq上下文中的执行线程,被hardirq 中断打断访问共享资源

那么处于hardirq上下文中的执行线程,可不可能被另一个hardirq打断呢,是可能的,如更高优先级的中断。但是在linux中没有支持同一个hardirq中断嵌套,具体是在发生中断时,硬件都会将本地中断关闭,linux在执行完硬中断处理(即所谓上半部)后,才会打开中断,这时候才会处理另外的中断请求,所以在linux中一个hardirq中断不会打断另一个hardirq,不用考虑关中断

3.1.3 local_bh_enable / local_bh_disable

在执行上下文仅可能被软中断打断,使用该手段

1. 处于进程上下文中的执行线程:被softirq中断打断访问共享资源

2. 处于softirq上下文中的执行线程,不会被其他softirq抢占

3. 处于hardirq上下文中的执行线程,不会被软中断打断,因为软中断总是在中断处理程序执行完成后才去执行的。

3.1.4 spin_lock / spin_unlock

主要用来保护smp的并发,临界区内不能睡眠!

在执行上下文仅可能其他cpu访问,使用该手段

spin_lock实现需要关闭抢占,原因是可能在一次中断后,被本地cpu的其他高优先级线程抢占,新线程在进入临界区时spin会导致死锁。

3.2 同步手段的组合

有时候,一个临界区并发访问的原因不只一种,这时候就要将上面的四种同步手段组合,linux中我总结了如下规则:

优先级  hardirq > softirq > preemption,优先级相同时不用考虑并发

这里结合代码分析一下,首先linux在处理中断时,有如下操作:

  1. __irq_enter   : preempt_count_add(HARDIRQ_OFFSET)  //此时一定有 preempt_count() != 0,即无法抢占
  2. 中断处理         //中断上下文
  3. irq_exit :         preempt_count_sub(HARDIRQ_OFFSET); //在这之后才处理softirq、premption,考虑到同种中断不可嵌套,一个hardirq不能被其他hardirq,softirq,preemption打断,优先级最高

继续考虑软中断,在irq_exit时,执行软中断,而在软中断完成,中断返回时,才检查是否需要抢占,值得注意的是,可以抢占的一个条件是不在中断执行环境中,所以如果在软中断上下文被中断打断是不能被其他进程抢占的,具体代码的分析可以参见:中断(二)——linux下半部机制分析

考虑hardirq上下文,softirq上下文,进程上下文:

  1. 如果是hardirq上下文,不用担心并发(没人抢的过你,且linux不支持同种中断嵌套)
  2. 如果是softirq上下文,只有hardirq会产生竟态(同级不会)
  3. 如果是进程上下文,要考虑hardirq和softirq,抢占产生的竟态

此时,再考虑和多核竞争的spin_lock进行组合(注意spin 禁止了抢占):

针对情况1: 无

针对情况2: spin_lock_irq/spin_unlock_irq

针对情况3: spin_lock_irq/spin_unlock_irq

                     spin_lock_bh/spin_unlock_bh

需要注意的是,一定要仔细分析每一种场景下的并发可能,选取最合适的锁,我总结如下表,理解了无需记忆

并发原因hardirq上下文software上下文进程上下文
preempt无需无需preempt_disable

softirq

softirq+preempt

无需无需local_bh_disable

hardirq

hardirq + preempt

hardirq + softirq

无需local_irq_disablelocal_irq_disable

smp

smp + preempt

spin_lockspin_lockspin_lock
smp + softirqspin_lockspin_lockspin_lock_bh
smp + hardirqspin_lockspin_lock_irqspin_lock_irq

四、参考

【1】Linux内核设计与实现 Robert Love

【2】Linux内核同步机制之(四):spin lock

【3】linux kernel的中断子系统之(八):softirq

 

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值