• spinlock 和抢占


     为了防止死锁,spinlock需要关中断和禁止抢占

    场景分析

    对于spin lock,其保护的资源可能来自多个CPU CORE上的进程上下文和中断上下文的中的访问,其中,进程上下文包括:用户进程通过系统调用访问,内核线程直接访问,来自workqueue中work function的访问(本质上也是内核线程)。中断上下文包括:HW interrupt context(中断handler)、软中断上下文(soft irq,当然由于各种原因,该softirq被推迟到softirqd的内核线程中执行的时候就不属于这个场景了,属于进程上下文那个分类了)、timer的callback函数(本质上也是softirq)、tasklet(本质上也是softirq)。

    先看最简单的单CPU上的进程上下文的访问。如果一个全局的资源被多个进程上下文访问,这时候,内核如何交错执行呢?对于那些没有打开preemptive选项的内核,所有的系统调用都是串行化执行的,因此不存在资源争抢的问题。如果内核线程也访问这个全局资源呢?本质上内核线程也是进程,类似普通进程,只不过普通进程时而在用户态运行、时而通过系统调用陷入内核执行,而内核线程永远都是在内核态运行,但是,结果是一样的,对于non-preemptive的linux kernel,只要在内核态,就不会发生进程调度,因此,这种场景下,共享数据根本不需要保护(没有并发,谈何保护呢)。如果时间停留在这里该多么好,单纯而美好,在继续前进之前,让我们先享受这一刻。

    场景一 禁止抢占

    当打开premptive选项后,事情变得复杂了,我们考虑下面的场景:

    (1)进程A在某个系统调用过程中访问了共享资源R

    (2)进程B在某个系统调用过程中也访问了共享资源R

    会不会造成冲突呢?假设在A访问共享资源R的过程中发生了中断,中断唤醒了沉睡中的优先级更高的B,在中断返回现场的时候发生进程切换,B启动执行,并通过系统调用访问了R,如果没有锁保护,则会出现两个thread进入临界区,导致程序执行不正确。OK,我们加上spin lock看看如何:A在进入临界区之前获取了spin lock,同样的,在A访问共享资源R的过程中发生了中断,中断唤醒了沉睡中的优先级更高的B,B在访问临界区之前仍然会试图获取spin lock,这时候由于A进程持有spin lock而导致B进程进入了永久的spin……怎么破?

    linux的kernel很简单,在A进程获取spin lock的时候,禁止本CPU上的抢占(上面的永久spin的场合仅仅在本CPU的进程抢占本CPU的当前进程这样的场景中发生)。

    如果A和B运行在不同的CPU上,那么情况会简单一些:A进程虽然持有spin lock而导致B进程进入spin状态,不过由于运行在不同的CPU上,A进程会持续执行并会很快释放spin lock,解除B进程的spin状态。

    多CPU core的场景和单核CPU打开preemptive选项的效果是一样的,这里不再赘述。

    场景二-关中断

    我们继续向前分析,现在要加入中断上下文这个因素。访问共享资源的thread包括:

    (1)运行在CPU0上的进程A在某个系统调用过程中访问了共享资源R

    (2)运行在CPU1上的进程B在某个系统调用过程中也访问了共享资源R

    (3)外设P的中断handler中也会访问共享资源R,中断handler在cpu0上执行

    在这样的场景下,使用spin lock可以保护访问共享资源R的临界区吗?我们假设CPU0上的进程A持有spin lock进入临界区,这时候,外设P发生了中断事件,并且调度到了CPU1上执行,看起来没有什么问题,执行在CPU1上的handler会稍微等待一会CPU0上的进程A,等它立刻临界区就会释放spin lock的,但是,如果外设P的中断事件被调度到了CPU0上执行会怎么样?CPU0上的进程A在持有spin lock的状态下被中断上下文抢占,而抢占它的CPU0上的handler在进入临界区之前仍然会试图获取spin lock,悲剧发生了,CPU0上的P外设的中断handler永远的进入spin状态,这时候,CPU1上的进程B也不可避免在试图持有spin lock的时候失败而导致进入spin状态。为了解决这样的问题,linux kernel采用了这样的办法:如果涉及到中断上下文的访问,spin lock需要和禁止本CPU上的中断联合使用。

    linux kernel中提供了丰富的bottom half的机制,虽然同属中断上下文,不过还是稍有不同。我们可以把上面的场景简单修改一下:外设P不是中断handler中访问共享资源R,而是在的bottom half中访问。使用spin lock+禁止本地中断当然是可以达到保护共享资源的效果,但是使用牛刀来杀鸡似乎有点小题大做,这时候disable bottom half就OK了。

    最后,我们讨论一下中断上下文之间的竞争。同一种中断handler之间在uni core和multi core上都不会并行执行,这是linux kernel的特性。如果不同中断handler需要使用spin lock保护共享资源,对于新的内核(不区分fast handler和slow handler),所有handler都是关闭中断的,因此使用spin lock不需要关闭中断的配合。bottom half又分成softirq和tasklet,同一种softirq会在不同的CPU上并发执行,因此如果某个驱动中的sofirq的handler中会访问某个全局变量,对该全局变量是需要使用spin lock保护的,不用配合disable CPU中断或者bottom half。tasklet更简单,因为同一种tasklet不会多个CPU上并发,具体我就不分析了,大家自行思考吧。

    抢spinlock,到底谁先抢到?

     

    spinlock显然需要这个一模一样的机制。这就叫Ticket spinlocks。2.6.25之后的spinlock是这样实现的:
    宋宝华: 几个人一起抢spinlock,到底谁先抢到?
    owner类似于柜台语音报的号,next是取票的号。它的逻辑大概类似于,谁取票先把spinlock的next暂存到本地local_next,然后把spinlock的next+1,所以后来的人取到的票号肯定更大;谁释放锁就把owner加1。如果spinlock释放后,owner正好等于某个CPU本地暂存的local_next,则这个CPU获得spinlock。

    spinlock本身将含有owner和next:

    typedef struct {
    
       union {
    
           unsigned int slock;        
    
           struct __raw_tickets {
    
               unsigned short owner;
    
               unsigned short next;
    
           } tickets;
    
       };
    
    } arch_spinlock_t; 

    获取spinlock的过程变成
    取号+等待叫号等于自己取的号:

    宋宝华: 几个人一起抢spinlock,到底谁先抢到?

    当然,真实的取号过程要通过ldrex、strex这样的指令来实现原子性:
    宋宝华: 几个人一起抢spinlock,到底谁先抢到?
    上述代码中,wfe()类似你在发呆、玩微信,这个时候,叫号机通过sev()来唤醒你。

    获取spinlock时,为什么要将调用preempt_disable?

  • 相关阅读:
    UVA 558 Wormholes
    HDU 1565 方格取数(1)
    poj2607
    poj2552
    poj2491
    poj2502
    poj2613
    .NET Framework 4 与 .NET Framework 4 Client Profile的区别与联系
    .Net Framework 4.0 和 2.0/3.0/3.5
    企业IT系统
  • 原文地址:https://www.cnblogs.com/dream397/p/15902203.html
Copyright © 2020-2023  润新知