【深入理解Linux内核锁】四、自旋锁

原创 嵌入式艺术 2023-08-30 07:14
1、什么是自旋锁?

自旋锁是一种典型的对临界资源进行互斥访问的手段。

它的底层实现逻辑是:原子变量+判断检测。

原子变量我们可以理解为一把锁,通过操作原子变量(锁)的状态,并对其进行判断,如果锁未被锁定,我们就继续往下执行;如果锁已经被锁定,我们就原地自旋,直到等到锁被打开。

ARM平台下,自旋锁的实现使用了ldrexstrex、以及内存屏障指令dmbdsbwfesev等。

2、自旋锁思想

  • 自旋锁主要针对于SMP或者单CPU但内核可抢占的情况,对于单CPU内核不可抢占的情况时,自旋锁退化为空操作。
  • 自旋锁实际为忙等锁,当锁不可用时,CPU一直处于等待状态,直到该锁被释放。
  • 自旋锁可能会导致内核死锁,当递归使用自旋锁时,则将该CPU锁死。
  • 在多核SMP的情况下,任何一个核拿到了自旋锁,该核上的抢占调度也暂时禁止了,但是没有禁止另外一个核的抢占调度。
  • 在自旋锁锁定期间,不能调用引起进程调度的函数,如copy_from_user()copy_to_user()kmalloc()msleep(),否则会导致内核崩溃

3、自旋锁的定义及实现

3.1 API接口

// 定义自旋锁
spinlock_t lock;

// 初始化自旋锁
spin_lock_init(&lock)

// 获得自旋锁
spin_lock(&lock)  // 获取自旋锁,如果立即获得锁,则直接返回,否则,自旋等待,直到锁被释放
spin_trylock(&lock)  // 尝试获取自旋锁,如果立即获得锁,返回true,否则直接返回false,不原地等待

// 释放自旋锁
spin_unlock(&lock)

自旋锁保证了不受其他CPU或者单CPU内的抢占进程的干扰,但是对于临界区代码,仍然有可能会受到中断和底半部的影响。

为了解决这种问题,我们就要使用自旋锁的衍生。

spin_lock_irq() = spin_lock() + local_irq_disable()   // 获取自旋锁并关中断
spin_unlock_irq() = spin_unlock() + local_irq_enable()  // 释放自旋锁并开中断
spin_lock_irqsave() = spin_lock() + local_irq_save()  // 获取自旋锁并关中断,保存中断状态
spin_unlock_irqrestore() = spin_unlock() + local_irq_restore()//释放自旋锁,开中断并恢复中断状态
spin_lock_bh() = spin_lock() + local_bh_disable()   // 获取自旋锁并关底半部中断
spin_unlock_bh() = spin_unlock() + local_bh_enable()  // 释放自旋锁并发开底半部中断

当我们的临界区代码,有可能被进程或者中断访问时,就需要在进程上下文中,调用spin_lock_irqsave()spin_unlock_irqrestore(),在中断上下文中调用spin_lock()spin_unlock(),如下图:

image-20230605140910971

3.2 API实现

3.2.1 结构体spinlock_t、raw_spinlock、arch_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 {
            u6 __padding[LOCK_PADSIZE];
            struct lockdep_map dep_map;
        };
#endif
    };
spinlock_t;

typedef struct raw_spinlock {
    arch_spinlock_t raw_lock;
#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 {
    union {
        u32 slock;
        struct __raw_tickets {
#ifdef __ARMEB__
            u16 next;
            u16 owner;
#else
            u16 owner;
            u16 next;
#endif
        } tickets;
    };
arch_spinlock_t;

结构体名称spinlock_traw_spinlockarch_spinlock_t

文件位置include/linux/spinlock.harch/arm/include/asm/spinlock_types.h

主要作用:结构体层层嵌套,用于定义一个自旋锁。

  • slock:32位无符号整形数据,用于锁的控制

  • __raw_ticketsunion类型,用于基于票证锁算法的自旋锁。

    每个线程进入代码段时,会尝试获取自旋锁,如果获取失败,它们会在锁的等待队列中排队。然后,等待队列中的线程会按照优先级顺序依次抢占锁的拥有权,直到某个线程成功获取自旋锁并执行完关键代码,释放锁资源为止。

    • owner :表示当前持有自旋锁的线程的索引
    • next :表示下一个等待获取自旋锁的线程的索引

这里使用的union联合体,其共享内存空间,其具体区别可看下面:

structunion区别:https://blog.csdn.net/lishuo0204/article/details/118957959

3.2.2 spin_lock_init

#define spin_lock_init(_lock)    \
do {       \
    spinlock_check(_lock);    \
    raw_spin_lock_init(&(_lock)->rlock);  \
} while (0)


static __always_inline raw_spinlock_t *spinlock_check(spinlock_t *lock)
{
    return &lock->rlock;
}}

define raw_spin_lock_init(lock)    \
    do { *(lock) = __RAW_SPIN_LOCK_UNLOCKED(lock); } while (0)


#define __RAW_SPIN_LOCK_UNLOCKED(lockname) \
    (raw_spinlock_t) __RAW_SPIN_LOCK_INITIALIZER(lockname)


#define __RAW_SPIN_LOCK_INITIALIZER(lockname) \
    {     \
    .raw_lock = __ARCH_SPIN_LOCK_UNLOCKED, \
    SPIN_DEBUG_INIT(lockname)  \
    SPIN_DEP_MAP_INIT(lockname) }


#define __ARCH_SPIN_LOCK_UNLOCKED { { 0 } }

函数名称spin_lock_init

文件位置include/linux/spinlock.h

主要作用:初始化自旋锁

函数调用流程

//  spin_lock_init
spin_lock_init(include/linux/spinlock.h)
    |--> spinlock_check         //  对锁进行检查,判断是否存在
    |--> raw_spin_lock_init     //  初始化锁
        |--> __RAW_SPIN_LOCK_UNLOCKED(include/linux/spinlock_types.h)
            |--> __RAW_SPIN_LOCK_INITIALIZER    //  将锁初始为__ARCH_SPIN_LOCK_UNLOCKED未上锁状态

上述函数主要通过宏定义给变量.raw_lock = __ARCH_SPIN_LOCK_UNLOCKED赋值,初始化为0,即为未上锁的状态;并且提供了两个调试接口:CONFIG_DEBUG_SPINLOCKCONFIG_DEBUG_LOCK_ALLOC,默认为关闭。

这里面有个关于spinlock_check存在的意义的讨论,感兴趣的可以看一下:https://stackoverflow.com/questions/52551594/spinlock-initialization-function

3.2.3 spin_lock

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

#define raw_spin_lock(lock) _raw_spin_lock(lock)

#ifndef CONFIG_INLINE_SPIN_LOCK
void __lockfunc _raw_spin_lock(raw_spinlock_t *lock)
{
    __raw_spin_lock(lock);
}
EXPORT_SYMBOL(_raw_spin_lock);

#ifndef CONFIG_INLINE_SPIN_LOCK
void __lockfunc _raw_spin_lock(raw_spinlock_t *lock)
{
    __raw_spin_lock(lock);
}
EXPORT_SYMBOL(_raw_spin_lock);

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

#define preempt_disable() \
do { \
    preempt_count_inc(); \
    barrier(); \
} while (0)


#define LOCK_CONTENDED(_lock, try, lock) \
    lock(_lock)


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

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 = READ_ONCE(lock->tickets.owner);
    }

    smp_mb();
}

函数名称spin_lock

文件位置include/linux/spinlock.h

主要作用:用于在进程或线程首次尝试获取锁的时候进行自旋,不停地检查锁的状态,如果锁已经被其他进程或线程占用,则自旋等待,直到锁被释放。

函数调用流程

//  spin_lock
spin_lock(include/linux/spinlock.h)
    |--> raw_spin_lock
        |--> _raw_spin_lock(include/linux/spinlock_api_smp.h)
            |--> __raw_spin_lock
                |--> __raw_spin_lock
                    |--> preempt_disable
          |--> preempt_count_inc
          |--> barrier
                    |--> spin_acquire
                    |--> LOCK_CONTENDED
                        |--> do_raw_spin_lock
                            |--> arch_spin_lock(arch/arm/include/asm/spinlock.h)

实现流程

  1. preempt_disable(); 禁用内核抢占,确保当前 CPU 执行该代码时不会被其他进程或线程抢占。

  • 其通过preempt_count_inc增加抢占计数器的值,通过抢占计数器来实现对任务的执行顺序进行管理。
  • 通过内存屏障barrier来确保前面的操作完成后再继续执行后面的代码。
  • spin_acquire(&lock->dep_map, 0, 0, _RET_IP_); 通过调用 spin_acquire() 函数获取自旋锁,用于保护共享资源不被两个、或者多个线程所修改。

    • spin_acquirelockdep工具的一部分,主要用于动态检测死锁。
    • lock->dep_map是锁的依赖地图,_RET_IP_是调用者的返回地址。这两个参数都是用于lockdep的调试信息。
    • lockdep是一个强大的锁调试工具,它可以跟踪锁的所有获取和释放,并动态地检测可能的死锁情况。
  • LOCK_CONTENDED(lock, do_raw_spin_trylock, do_raw_spin_lock); 实际为调用do_raw_spin_lock函数来实现获取锁并自旋的操作。

  • 下面为arch_spin_lock汇编代码分析

    1. prefetchw(&lock->slock) 函数用于提前加载锁的地址到处理器缓存中,从而提高锁的获取效率。

    2. ldrex %0, [%3] 用于以原子方式读取锁的值到寄存器 %0 中,%3 为锁的地址。

    3. add %1, %0, %4用于将当前获取锁的 CPU 分配的新值加上原锁值 %0 以及%4 固定常量,结果存放在%1新值中。

    4. strex %2, %1, [%3] 用于以原子方式将更新的值 %1 写入锁的地址所指定的内存位置,%2 为写入结果。

      这里解释一下为什么要做add处理

      spinlock_t的结构体可知,是由联合体组成,并且可以通过ownernext两个字段访问,next在高16位,owner在低16位。

      这里上锁的操作,是将其值设置为1 << TICKET_SHIFT,也就是高16位,即next字段设置为1,表示下一个等待获取自旋锁的线程的索引。

    5. teq %2, #0:用于测试写入结果 %2 是否为0,如果为0,表示锁获取成功,反之则跳转到标签1b处执行。

    6. 当获取锁成功之后,程序会执行 while 循环,不断等待锁的所有权被赋予当前 CPU, 直到锁的所有权的拥有者持有锁为止。

    7. smp_mb() 函数执行一条内存屏障,确保所有关键数据的顺序性已经刷新到内存中。

    综上,spin_lock代码的作用是获取自旋锁,让当前线程获得临界资源的控制权,避免多个线程同时修改共享资源而造成数据冲突。同时,通过禁用内核抢占和使用内联函数优化的方式,保证了原子操作的执行效率和可靠性。

    3.2.4 spin_unlock

    static __always_inline void spin_unlock(spinlock_t *lock)
    {
        raw_spin_unlock(&lock->rlock);
    }

    #define raw_spin_unlock(lock)  _raw_spin_unlock(lock)

    void __lockfunc _raw_spin_unlock(raw_spinlock_t *lock)
    {
        __raw_spin_unlock(lock);
    }

    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);
    }

    static inline void arch_spin_unlock(arch_spinlock_t *lock)
    {
        smp_mb();
        lock->tickets.owner++;
        dsb_sev();
    }

    函数名称spin_unlock

    嵌入式艺术 分享一些高级嵌入式相关知识,包括:计算机基础、操作系统、Linux驱动、Linux内核、RT-thread等,除此之外,并且会组织一些简单的训练项目,一起成长。 我的创作理念:专注分享高质量的嵌入式文章,让大家读有所得!
    评论
    • 物联网(IoT)的快速发展彻底改变了从智能家居到工业自动化等各个行业。由于物联网系统需要高效、可靠且紧凑的组件来处理众多传感器、执行器和通信设备,国产固态继电器(SSR)已成为满足中国这些需求的关键解决方案。本文探讨了国产SSR如何满足物联网应用的需求,重点介绍了它们的优势、技术能力以及在现实场景中的应用。了解物联网中的固态继电器固态继电器是一种电子开关设备,它使用半导体而不是机械触点来控制负载。与传统的机械继电器不同,固态继电器具有以下优势:快速切换:确保精确快速的响应,这对于实时物联网系统至
      克里雅半导体科技 2025-01-03 16:11 185浏览
    • 彼得·德鲁克被誉为“现代管理学之父”,他的管理思想影响了无数企业和管理者。然而,关于他的书籍分类,一种流行的说法令人感到困惑:德鲁克一生写了39本书,其中15本是关于管理的,而其中“专门写工商企业或为企业管理者写的”只有两本——《为成果而管理》和《创新与企业家精神》。这样的表述广为流传,但深入探讨后却发现并不完全准确。让我们一起重新审视这一说法,解析其中的矛盾与根源,进而重新认识德鲁克的管理思想及其著作的真正价值。从《创新与企业家精神》看德鲁克的视角《创新与企业家精神》通常被认为是一本专为企业管
      优思学院 2025-01-06 12:03 104浏览
    • 在快速发展的能源领域,发电厂是发电的支柱,效率和安全性至关重要。在这种背景下,国产数字隔离器已成为现代化和优化发电厂运营的重要组成部分。本文探讨了这些设备在提高性能方面的重要性,同时展示了中国在生产可靠且具有成本效益的数字隔离器方面的进步。什么是数字隔离器?数字隔离器充当屏障,在电气上将系统的不同部分隔离开来,同时允许无缝数据传输。在发电厂中,它们保护敏感的控制电路免受高压尖峰的影响,确保准确的信号处理,并在恶劣条件下保持系统完整性。中国国产数字隔离器经历了重大创新,在许多方面达到甚至超过了全球
      克里雅半导体科技 2025-01-03 16:10 122浏览
    • 根据Global Info Research项目团队最新调研,预计2030年全球封闭式电机产值达到1425百万美元,2024-2030年期间年复合增长率CAGR为3.4%。 封闭式电机是一种电动机,其外壳设计为密闭结构,通常用于要求较高的防护等级的应用场合。封闭式电机可以有效防止外部灰尘、水分和其他污染物进入内部,从而保护电机的内部组件,延长其使用寿命。 环洋市场咨询机构出版的调研分析报告【全球封闭式电机行业总体规模、主要厂商及IPO上市调研报告,2025-2031】研究全球封闭式电机总体规
      GIRtina 2025-01-06 11:10 99浏览
    • 每日可见的315MHz和433MHz遥控模块,你能分清楚吗?众所周知,一套遥控设备主要由发射部分和接收部分组成,发射器可以将控制者的控制按键经过编码,调制到射频信号上面,然后经天线发射出无线信号。而接收器是将天线接收到的无线信号进行解码,从而得到与控制按键相对应的信号,然后再去控制相应的设备工作。当前,常见的遥控设备主要分为红外遥控与无线电遥控两大类,其主要区别为所采用的载波频率及其应用场景不一致。红外遥控设备所采用的射频信号频率一般为38kHz,通常应用在电视、投影仪等设备中;而无线电遥控设备
      华普微HOPERF 2025-01-06 15:29 114浏览
    • PLC组态方式主要有三种,每种都有其独特的特点和适用场景。下面来简单说说: 1. 硬件组态   定义:硬件组态指的是选择适合的PLC型号、I/O模块、通信模块等硬件组件,并按照实际需求进行连接和配置。    灵活性:这种方式允许用户根据项目需求自由搭配硬件组件,具有较高的灵活性。    成本:可能需要额外的硬件购买成本,适用于对系统性能和扩展性有较高要求的场合。 2. 软件组态   定义:软件组态主要是通过PLC
      丙丁先生 2025-01-06 09:23 77浏览
    • 自动化已成为现代制造业的基石,而驱动隔离器作为关键组件,在提升效率、精度和可靠性方面起到了不可或缺的作用。随着工业技术不断革新,驱动隔离器正助力自动化生产设备适应新兴趋势,并推动行业未来的发展。本文将探讨自动化的核心趋势及驱动隔离器在其中的重要角色。自动化领域的新兴趋势智能工厂的崛起智能工厂已成为自动化生产的新标杆。通过结合物联网(IoT)、人工智能(AI)和机器学习(ML),智能工厂实现了实时监控和动态决策。驱动隔离器在其中至关重要,它确保了传感器、执行器和控制单元之间的信号完整性,同时提供高
      腾恩科技-彭工 2025-01-03 16:28 170浏览
    • 大模型的赋能是指利用大型机器学习模型(如深度学习模型)来增强或改进各种应用和服务。这种技术在许多领域都显示出了巨大的潜力,包括但不限于以下几个方面: 1. 企业服务:大模型可以用于构建智能客服系统、知识库问答系统等,提升企业的服务质量和运营效率。 2. 教育服务:在教育领域,大模型被应用于个性化学习、智能辅导、作业批改等,帮助教师减轻工作负担,提高教学质量。 3. 工业智能化:大模型有助于解决工业领域的复杂性和不确定性问题,尽管在认知能力方面尚未完全具备专家级的复杂决策能力。 4. 消费
      丙丁先生 2025-01-07 09:25 65浏览
    • 本文介绍Linux系统更换开机logo方法教程,通用RK3566、RK3568、RK3588、RK3576等开发板,触觉智能RK3562开发板演示,搭载4核A53处理器,主频高达2.0GHz;内置独立1Tops算力NPU,可应用于物联网网关、平板电脑、智能家居、教育电子、工业显示与控制等行业。制作图片开机logo图片制作注意事项(1)图片必须为bmp格式;(2)图片大小不能大于4MB;(3)BMP位深最大是32,建议设置为8;(4)图片名称为logo.bmp和logo_kernel.bmp;开机
      Industio_触觉智能 2025-01-06 10:43 87浏览
    • 这篇内容主要讨论三个基本问题,硅电容是什么,为什么要使用硅电容,如何正确使用硅电容?1.  硅电容是什么首先我们需要了解电容是什么?物理学上电容的概念指的是给定电位差下自由电荷的储藏量,记为C,单位是F,指的是容纳电荷的能力,C=εS/d=ε0εrS/4πkd(真空)=Q/U。百度百科上电容器的概念指的是两个相互靠近的导体,中间夹一层不导电的绝缘介质。通过观察电容本身的定义公式中可以看到,在各个变量中比较能够改变的就是εr,S和d,也就是介质的介电常数,金属板有效相对面积以及距离。当前
      知白 2025-01-06 12:04 140浏览
    • 光耦合器,也称为光隔离器,是一种利用光在两个隔离电路之间传输电信号的组件。在医疗领域,确保患者安全和设备可靠性至关重要。在众多有助于医疗设备安全性和效率的组件中,光耦合器起着至关重要的作用。这些紧凑型设备经常被忽视,但对于隔离高压和防止敏感医疗设备中的电气危害却是必不可少的。本文深入探讨了光耦合器的功能、其在医疗应用中的重要性以及其实际使用示例。什么是光耦合器?它通常由以下部分组成:LED(发光二极管):将电信号转换为光。光电探测器(例如光电晶体管):检测光并将其转换回电信号。这种布置确保输入和
      腾恩科技-彭工 2025-01-03 16:27 178浏览
    •     为控制片内设备并且查询其工作状态,MCU内部总是有一组特殊功能寄存器(SFR,Special Function Register)。    使用Eclipse环境调试MCU程序时,可以利用 Peripheral Registers Viewer来查看SFR。这个小工具是怎样知道某个型号的MCU有怎样的寄存器定义呢?它使用一种描述性的文本文件——SVD文件。这个文件存储在下面红色字体的路径下。    例:南京沁恒  &n
      电子知识打边炉 2025-01-04 20:04 94浏览
    • 随着市场需求不断的变化,各行各业对CPU的要求越来越高,特别是近几年流行的 AIOT,为了有更好的用户体验,CPU的算力就要求更高了。今天为大家推荐由米尔基于瑞芯微RK3576处理器推出的MYC-LR3576核心板及开发板。关于RK3576处理器国产CPU,是这些年的骄傲,华为手机全国产化,国人一片呼声,再也不用卡脖子了。RK3576处理器,就是一款由国产是厂商瑞芯微,今年第二季推出的全新通用型的高性能SOC芯片,这款CPU到底有多么的高性能,下面看看它的几个特性:8核心6 TOPS超强算力双千
      米尔电子嵌入式 2025-01-03 17:04 54浏览
    • 在智能家居领域中,Wi-Fi、蓝牙、Zigbee、Thread与Z-Wave等无线通信协议是构建短距物联局域网的关键手段,它们常在实际应用中交叉运用,以满足智能家居生态系统多样化的功能需求。然而,这些协议之间并未遵循统一的互通标准,缺乏直接的互操作性,在进行组网时需要引入额外的网关作为“翻译桥梁”,极大地增加了系统的复杂性。 同时,Apple HomeKit、SamSung SmartThings、Amazon Alexa、Google Home等主流智能家居平台为了提升市占率与消费者
      华普微HOPERF 2025-01-06 17:23 134浏览
    我要评论
    0
    点击右上角,分享到朋友圈 我知道啦
    请使用浏览器分享功能 我知道啦