zoukankan      html  css  js  c++  java
  • Linux内核自旋锁spinlock_t机制【转】

    转自:https://www.jianshu.com/p/f0d6e7103d9b

    spinlock用在什么场景?

    自旋锁用在临界区代码非常少的情况。

    spinlock在使用时有什么注意事项?

    • 临界区代码应该尽可能精简
    • 不允许睡眠(会出现死锁)
    • Need to have interrupts disabled when locked by ordinary threads, if shared by an interrupt handler。(会出现死锁)

    spinlock是怎么实现的?

    看一下源代码:

    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;
    

    如果忽略CONFIG_DEBUG_LOCK_ALLOC话,spinlock主要包含一个arch_spinlock_t的结构,从名字可以看出,这个结构是跟体系结构有关的。

    加锁流程

    加锁的相关源码如下:

    
    #define raw_spin_lock(lock) _raw_spin_lock(lock)
    
    static inline void spin_lock(spinlock_t *lock)
    {
        raw_spin_lock(&lock->rlock);
    }
    

    _raw_spin_lock完成实际的加锁动作。

    根据CPU体系结构,spinlock分为SMP版本和UP版本,这里以SMP版本为例来分析。SMP版本中,_raw_spin_lock为声明为:

    void __lockfunc _raw_spin_lock(raw_spinlock_t *lock)        __acquires(lock);
    

    再看_raw_spin_lock的实现,SMP版本中,看_raw_spin_lock最终调用了__raw_spin_lock,__raw_spin_lock的源代码如下:

    static inline void __raw_spin_lock(raw_spinlock_t *lock)
    {
        // 禁止抢占
        preempt_disable();
        // for debug
        spin_acquire(&lock->dep_map, 0, 0, _RET_IP_);
        // real work done here
        LOCK_CONTENDED(lock, do_raw_spin_trylock, do_raw_spin_lock);
    }
    

    LOCK_CONTENDED是一个通用的加锁流程。do_raw_spin_trylock和do_raw_spin_lock的实现依赖于具体的体系结构,以x86为例,do_raw_spin_trylock最终调用的是:

    do_raw_spin_trylock的源代码:

    static inline int do_raw_spin_trylock(raw_spinlock_t *lock)
    {
        // 体系结构相关
        return arch_spin_trylock(&(lock)->raw_lock);
    }
    

    以x86为例,arch_spin_trylock最终调用__ticket_spin_trylock函数。其源代码如下:

    // 定义在arch/x86/include/asm/spinlock_types.h
    typedef struct arch_spinlock {
        union {
            __ticketpair_t head_tail;
            struct __raw_tickets {
                __ticket_t head, tail; // 注意,x86使用的是小端模式,存在高地址空间的是tail
            } tickets;
        };
    } arch_spinlock_t;
    
    // 定义在arch/x86/include/asm中
    static __always_inline int __ticket_spin_trylock(arch_spinlock_t *lock)
    {
        arch_spinlock_t old, new;
        // 获取旧的ticket信息
        old.tickets = ACCESS_ONCE(lock->tickets);
        // head和tail不一致,说明锁正被占用,加锁不成功
        if (old.tickets.head != old.tickets.tail)
            return 0;
    
        new.head_tail = old.head_tail + (1 << TICKET_SHIFT); // 将tail + 1
    
        /* cmpxchg is a full barrier, so nothing can move before it */
        return cmpxchg(&lock->head_tail, old.head_tail, new.head_tail) == old.head_tail;
    }
    

    从上述代码中可知,__ticket_spin_trylock的核心功能,就是判断自旋锁是否被占用,如果没被占用,尝试原子性地更新lock中的head_tail的值,将tail+1,返回是否加锁成功。

    不考虑CONFIG_DEBUG_SPINLOCK宏的话, do_raw_spin_lock的源代码如下:

    static inline void do_raw_spin_lock(raw_spinlock_t *lock) __acquires(lock)
    {
        __acquire(lock);
        arch_spin_lock(&lock->raw_lock);
    }
    

    arch_spin_lock的源代码:

    static __always_inline void arch_spin_lock(arch_spinlock_t *lock)
    {
        __ticket_spin_lock(lock);
    }
    

    __ticket_spin_lock的源代码:

    static __always_inline void __ticket_spin_lock(arch_spinlock_t *lock)
    {
        register struct __raw_tickets inc = { .tail = 1 };
        
        // 原子性地把ticket中的tail+1,返回的inc是+1之前的原始值
        inc = xadd(&lock->tickets, inc);
    
        for (;;) {
            // 循环直到head和tail相等
            if (inc.head == inc.tail)
                break;
            cpu_relax();
            // 读取新的head值
            inc.head = ACCESS_ONCE(lock->tickets.head);
        }
        barrier();      /* make sure nothing creeps before the lock is taken */
    }
    

    ticket分成两个部分,一部分叫tail,相当于一个队列的队尾,一个部分叫head,相当于一个队列的队头。初始化的时候,tail和head都是0,表示无人占用锁。
    __ticket_spin_lock就是原子性地把tail+1,并且把+1之前的值记录下来,然后不断地和head进行比较。由于是原子性的操作,所以不同的锁竞争者拿到的tail值是不一样的。如果tail值和head一样了,说明这时候没人占用锁了,下一个拿到锁的就是自己了。

    举例来说,假设线程A和线程B竞争同一个自旋锁:

    1. 初始化tail=0, head=0,线程A将tail+1, 并返回tail的旧值0,将0和head值比较,相等,于是这时候线程A就拿到了锁。
    2. 线程A这时候也来拿锁,将tail值+1,变成2,返回tail的旧值1,将其和head值0比较,不相等,继续循环。
    3. 线程A用完锁了,将head值+1。
    4. 线程B读取head值,并将其和tail值比较,发现相等,获得锁。

    解锁流程

    对于SMP架构来说,spin_unlock最终调用的是__raw_spin_unlock,其源代码如下:

    static inline void __raw_spin_unlock(raw_spinlock_t *lock)
    {
        spin_release(&lock->dep_map, 1, _RET_IP_);
        // 主要的解锁工作  
        do_raw_spin_unlock(lock);
        // 启用抢占
        preempt_enable();
    }
    
    static inline void do_raw_spin_unlock(raw_spinlock_t *lock) __releases(lock)
    {
        arch_spin_unlock(&lock->raw_lock);
        __release(lock);
    }
    

    arch_spin_unlock在x86体系结构下的实现代码如下:

    static __always_inline void arch_spin_unlock(arch_spinlock_t *lock)
    {
        __ticket_spin_unlock(lock);
    }
    
    static __always_inline void __ticket_spin_unlock(arch_spinlock_t *lock)
    {
        // 将tickers的head值加1
        __add(&lock->tickets.head, 1, UNLOCK_LOCK_PREFIX);
    }
    
    

    考虑中断处理函数

    如果自旋锁可能在中断处理处理中使用,那么在获取自旋锁之前,必须禁止本地中断。则,持有锁的内核代码会被中断处理程序打断,接着试图去争用这个已经被持有的自旋锁。这样的结果是,中断处理函数自旋,等待该锁重新可用,但是锁的持有者在该中断处理程序执行完毕之前不可能运行,这就成为了双重请求死锁。注意,需要关闭的只是当前处理器上的中断。因为中断发生在不同的处理器上,即使中断处理程序在同一锁上自旋,也不会妨碍锁的持有者(在不同处理器上)最终释放。

    所以要使用spin_lock_irqsave() / spin_unlock_irqrestore()这个版本的加锁、解锁函数。
    函数spin_lock_irqsave():保存中断的当前状态,禁止本地中断,然后获取指定的锁。
    函数spin_unlock_reqrestore():对指定的锁解锁,让中断恢复到加锁前的状态。所以即使中断最初是被禁止的,代码也不会错误地激活它们。

    spinlock的几种变种

    1. rwlock_t 读写锁
    2. seqlock_t 顺序锁

    参考资料

    Ticket spinklocks
    Linux内核源代码



    作者:Jiafu89
    链接:https://www.jianshu.com/p/f0d6e7103d9b
    来源:简书
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
  • 相关阅读:
    linq to entity group by 时间
    Entity Framework Core for Console
    EF Core 多个DbContext迁移命令
    .net for TCP服务端 && 客户端
    创建Windows Service
    EF Code First 快速创建
    在Docker中创建Mongo容器的后续设置
    Docker入门
    Python之collections.defaultdict
    Hough transform(霍夫变换)
  • 原文地址:https://www.cnblogs.com/sky-heaven/p/12802063.html
Copyright © 2011-2022 走看看