设备驱动中的spin_lock(kernel-4.7)

原创 2017年01月03日 23:24:27

在linux kernel的实现中,经常会遇到这样的场景:共享数据被中断上下文和进程上下文访问,该如何保护呢?如果只有进程上下文的访问,那么可以考虑使用semaphore或者mutex的锁机制,但是现在中断上下文也参和进来,那些可以导致睡眠的lock就不能使用了,这时候,可以考虑使用spin_lock

1、spin lock的特点
总结spin lock的特点如下:
(1)spin lock是一种死等的锁机制。当发生访问资源冲突的时候,可以有两个选择:一个是死等,一个是挂起当前进程,调度其他进程执行。spin lock是一种死等的机制,当前的执行thread会不断的重新尝试直到获取锁进入临界区。
(2)只允许一个thread进入。semaphore可以允许多个thread进入,spin lock不行,一次只能有一个thread获取锁并进入临界区,其他的thread都是在门口不断的尝试。
(3)执行时间短。由于spin lock死等这种特性,因此它使用在那些代码不是非常复杂的临界区(当然也不能太简单,否则使用原子操作或者其他适用简单场景的同步机制就OK了),如果临界区执行时间太长,那么不断在临界区门口“死等”的那些thread是多么的浪费CPU啊(当然,现代CPU的设计都会考虑同步原语的实现,例如ARM提供了WFE和SEV这样的类似指令,避免CPU进入busy loop的悲惨境地)
(4)可以在中断上下文执行。由于不睡眠,因此spin lock可以在中断上下文中适用。

2、 场景分析
对于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
在这样的场景下,使用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_t的数据类型,其本质上是一个整数值(对该数值的操作需要保证原子性),该数值表示spin lock是否可用。初始化的时候被设定为1。当thread想要持有锁的时候调用spin_lock函数,该函数将spin lock那个整数值减去1,然后进行判断,如果等于0,表示可以获取spin lock,如果是负数,则说明其他thread的持有该锁,本thread需要spin。

内核中的spinlock_t的数据类型定义如下:


typedef struct raw_spinlock {
    arch_spinlock_t raw_lock;
#ifdef CONFIG_GENERIC_LOCKBREAK
    unsigned int break_lock;
#endif
#ifdef CONFIG_DEBUG_SPINLOCK
    unsigned int magic, owner_cpu;
    void *owner;
#endif
#ifdef CONFIG_DEBUG_LOCK_ALLOC
    struct lockdep_map dep_map;
#endif
} raw_spinlock_t;


typedef struct spinlock {
    union {
        struct raw_spinlock rlock;

#ifdef CONFIG_DEBUG_LOCK_ALLOC
# define LOCK_PADSIZE (offsetof(struct raw_spinlock, dep_map))
        struct {
            u8 __padding[LOCK_PADSIZE];
            struct lockdep_map dep_map;
        };
#endif
    };
} spinlock_t;

spin_lock的代码如下:


static __always_inline void spin_lock(spinlock_t *lock)
{
    raw_spin_lock(&lock->rlock);
}

其中 raw_spin_lock 定义如下:

#define raw_spin_lock(lock) _raw_spin_lock(lock)

_raw_spin_lock 调用了__raw_spin_lock

void __lockfunc _raw_spin_lock(raw_spinlock_t *lock)
{
    __raw_spin_lock(lock);
}

__raw_spin_lock 如下:


static inline void __raw_spin_lock(raw_spinlock_t *lock)
{
    preempt_disable();
    spin_acquire(&lock->dep_map, 0, 0, _RET_IP_);
    LOCK_CONTENDED(lock, do_raw_spin_trylock, do_raw_spin_lock);
}

最终实际的获取spin lock还是要靠arch相关的代码实现。

ARM平台中涉及spin_lock的代码位于arch/arm/include/asm/spinlock.hspinlock_type.h,和通用代码类似,spinlock_type.h定义ARM相关的spin lock定义以及初始化相关的宏;spinlock.h中包括了各种具体的实现。

ARM平台中的arch_spinlock_t定义如下(little endian):


typedef struct {
    union {
        u32 slock;
        struct __raw_tickets {
#ifdef __ARMEB__
            u16 next;
            u16 owner;
#else
            u16 owner;
            u16 next;
#endif
        } tickets;
    };
} arch_spinlock_t;

arch_spin_lock,其ARM32的代码如下:


static inline void arch_spin_lock(arch_spinlock_t *lock)
{
    unsigned long tmp;
    u32 newval;
    arch_spinlock_t lockval;

    prefetchw(&lock->slock);
    __asm__ __volatile__(
"1: ldrex   %0, [%3]\n"
"   add %1, %0, %4\n"
"   strex   %2, %1, [%3]\n"
"   teq %2, #0\n"
"   bne 1b"
    : "=&r" (lockval), "=&r" (newval), "=&r" (tmp)
    : "r" (&lock->slock), "I" (1 << TICKET_SHIFT)
    : "cc");

    while (lockval.tickets.next != lockval.tickets.owner) {
        wfe();
        lockval.tickets.owner = ACCESS_ONCE(lock->tickets.owner);
    }

    smp_mb();
}

未完待续。。。

版权声明:本文为博主原创文章,未经博主允许不得转载。博主出没群号331983270, 104504347

spin_lock、spin_lock_irq、spin_lock_irqsave区别

void spin_lock(spinlock_t *lock); void spin_lock_irq(spinlock_t *lock); void spin_lock_irqsave(spin...
  • luckywang1103
  • luckywang1103
  • 2014年12月22日 19:16
  • 5276

linux驱动之自旋锁(spinlock)

dasads
  • u013256018
  • u013256018
  • 2015年09月08日 19:30
  • 1033

spinlock与中断、抢占的关系

在kernel中,一个线程获得了spinlock,那么这个线程可以被interrupt吗? 可以,spinlock 并没有关中断,貌似spinlock_irqsave 才关中断了。 tips1:spi...
  • liduxun
  • liduxun
  • 2015年08月21日 13:27
  • 1950

spin_lock与内核抢占

spin_lock与内核抢占 小狼@http://blog.csdn.net/xiaolangyangyang 内核抢占特性         2.6内核版本加入了抢占的特性,但我们可以配...
  • xiaolangyangyang
  • xiaolangyangyang
  • 2016年04月18日 10:32
  • 588

深入分析_linux_spinlock_实现机制

在 x86 平台上,spinlock 主要通过处理器的 lock 指令前缀实现当某个线程的一条指令访问某个内存的时候,其他的线程的指令无法访问该内存的功能。(可见都是由处理器特性来保证!) 因此在 ...
  • electrombile
  • electrombile
  • 2016年05月01日 11:10
  • 2530

自旋锁spin_lock

原子变量适用在多核之间多单一共享变量进行互斥访问,如果要保护多个变量,并且这些变量之间有逻辑关系时,原子变量就不适用了。例如:常见的双向链表。假设有三个链表节点A、B、C。需要将节点B插入节点A、C之...
  • longwang155069
  • longwang155069
  • 2016年08月01日 19:01
  • 1117

一次spinlock死锁故障的定位(太经典,收藏!)

一次spinlock死锁故障的定位 1350阅读 0评论2015-07-21 humjb_1983 分类:LINUX 本文讲述一次spinlock死锁故障的定位过程,目的不在于问题本...
  • lqxandroid2012
  • lqxandroid2012
  • 2016年12月12日 11:50
  • 1400

spinlock变量没有初始化

最近在一次的稳定性测试中发现如下问题: c7 BUG: spinlock bad magic on CPU#7, Binder_5/6373 c7 lock: system_int_lock+0x...
  • longwang155069
  • longwang155069
  • 2016年08月16日 20:01
  • 1105

用户态spinlock

spin用户态加锁源码分析 源码分析版本:glic-2.9 spin_lock加锁 pthread_spin_lock: int pthread_spin_lock (lock) ...
  • abccheng
  • abccheng
  • 2016年08月31日 18:03
  • 670

spinlock造成死锁的原因

看了这个网址,对自选锁造成的死锁有了更深刻对理解。 http://blog.csdn.net/zhanglei4214/article/details/6837697 spin_lock比sp...
  • shenyanxxxy
  • shenyanxxxy
  • 2013年08月06日 10:56
  • 1054
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:设备驱动中的spin_lock(kernel-4.7)
举报原因:
原因补充:

(最多只允许输入30个字)