首页 > 系统相关 >linux --- 自旋锁(spinlock_t)

linux --- 自旋锁(spinlock_t)

时间:2023-09-17 22:48:08浏览次数:38  
标签:__ 中断 lock linux spinlock --- 自旋 spin CPU

定义

自旋锁与互斥锁有点类似,只是自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环在那里看是否该自旋锁的保持者已经释放了锁,”自旋”一词就是因此而得名。由于自旋锁使用者一般保持锁时间非常短,因此选择自旋而不是睡眠是非常必要的,自旋锁的效率远高于互斥锁。

信号量和读写信号量适合于保持时间较长的情况,它们会导致调用者睡眠,因此只能在进程上下文使用(_trylock的变种能够在中断上下文使用),而自旋锁适合于保持时间非常短的情况,它可以在任何上下文使用。如果被保护的共享资源只在进程上下文访问,使用信号量保护该共享资源非常合适,如果对共享资源的访问时间非常短,自旋锁也可以。但是如果被保护的共享资源需要在中断上下文访问(包括底半部即中断处理句柄和顶半部即软中断),就必须使用自旋锁。

实现

传统的自旋锁本质上用一个整数来表示,值为1代表锁未被占用, 为0或者为负数表示被占用,传统自旋锁已不再使用。

Linux 内核 2.6.25 版本中引入了排队自旋锁:谁先等待锁,谁先获得锁。所以 linux 的自旋锁就是排队自旋锁(ticket spinlock),有关结构体定义如下:

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

自旋锁,为什么要禁止抢占?

以linux为例,如果允许抢占,线程1正在持有该锁,此时发生了schedule后线程2又去试图拿该锁,线程2就会自旋在那里,浪费CPU资源。

假如只有单核,把自旋锁的preempt_disable注释掉,即允许抢占,使用自旋锁会产生死锁么?

不会。线程1在执行临界区,被schedule出去,线程2试图获取该锁,线程2会自旋在那里(浪费CPU,不主动让出CPU),等到再次被调度到线程1并释放了该锁后,线程2才可以继续往下跑。

自旋锁临界区为什么不允许sleep(使用会schedule类函数)?

线程1在执行临界区,此时该CPU禁止抢占,如果调用sleep主动schedule出去后,该CPU就永远回不来了,此时如果线程2试图获取该锁,就会发生死锁。(实际应该是发生kernel panic)

 

自旋锁只有在内核可抢占或SMP的情况下才真正需要,在单CPU且不可抢占的内核下,自旋锁的所有操作都是空操作,在单CPU且可抢占的内核下,自旋锁实际上只进行开启和关闭内核抢占的操作,如下:

include\linux\spinlock_api_up.h

#define _raw_spin_lock(lock)            __LOCK(lock)

#define __LOCK(lock) \
  do { preempt_disable(); ___LOCK(lock); } while (0)

#define ___LOCK(lock) \
  do { __acquire(lock); (void)(lock); } while (0)

# define __acquire(x)    (void)0

SMP情况下,除了关抢占,还需要用到独占的汇编指令操作变量,如下:

kernel\locking\spinlock.c
include\linux\spinlock_api_smp.h

void __lockfunc _raw_spin_lock(raw_spinlock_t *lock)
{
    __raw_spin_lock(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);
}

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_bh和spin_unlock_bh来保护(关闭软中断)。当然使用spin_lock_irq和spin_unlock_irq以及spin_lock_irqsave和spin_unlock_irqrestore也可以,它们失效了本地硬中断(本地指当前CPU中断),失效硬中断隐式地也失效了软中断。但是使用spin_lock_bh和spin_unlock_bh是最恰当的,它比其他两个快。

被保护的共享资源只在进程上下文和tasklet或timer上下文访问

应该使用与上面情况相同的获得和释放锁的宏,因为tasklet和timer是用软中断实现的。

spinlock用在进程上下文和中断

进程A中调用了spin_lock(&lock)然后进入临界区,此时来了一个中断(interrupt),该中断也运行在和进程A相同的CPU上,并且在该中断处理程序中恰巧也会spin_lock(&lock)试图获取同一个锁。由于是在同一个CPU上被中断,进程A会被设置为TASK_INTERRUPT状态,

中断处理程序无法获得锁,会不停的忙等,由于进程A被设置为中断状态,schedule()进程调度就无法再调度进程A运行,这样就导致了死锁!但是如果该中断处理程序运行在不同的CPU上就不会触发死锁。因为在不同的CPU上出现中断不会导致进程A的状态被设为TASK_INTERRUPT,只是换出。当中断处理程序忙等被换出后,进程A还是有机会获得CPU,执行并退出临界区,所以在使用spin_lock时要明确知道该锁不会在中断处理程序中使用。如果有,那就需要使用spinlock_irq_save,该函数即会关抢占,也会关本地中断(本CPU中断)。

被保护的共享资源只在一个tasklet或timer上下文访问

不需要任何自旋锁保护,因为同一个tasklet或timer只能在一个CPU上运行,即使是在SMP环境下也是如此。实际上tasklet在调用tasklet_schedule标记其需要被调度时已经把该tasklet绑定到当前CPU,因此同一个tasklet决不可能同时在其他CPU上运行。timer也是在其被使用add_timer添加到timer队列中时已经被帮定到当前CPU,所以同一个timer绝不可能运行在其他CPU上。当然同一个tasklet有两个实例同时运行在同一个CPU就更不可能了。

被保护的共享资源只在两个或多个tasklet或timer上下文访问

对共享资源的访问仅需要用spin_lock和spin_unlock来保护,不必使用_bh版本,因为当tasklet或timer运行时,不可能有其他tasklet或timer在当前CPU上运行。

不足

随着计算机硬件技术的发展,CPU相对于存储器件的运算速度优势越来越大。在这种背景下,获取基于 counter(需要访问存储器件)的锁(例如spinlock,rwlock)的机制开销越来越明显。因此,那些基于一个multi-processor之间的共享counter的锁机制已经不能满足性能要求,在这种情况下,RCU机制应运而生。

 

 

 

 

标签:__,中断,lock,linux,spinlock,---,自旋,spin,CPU
From: https://www.cnblogs.com/god-of-death/p/17709985.html

相关文章

  • Go - Merge Sort
    MergeSort.gopackagemainfuncMergeSort(items[]int)[]int{n:=len(items)varcombined[]intswitch{casen<=1:combined=itemscasen==2:ifitems[0]<=items[1]{combined=items......
  • 2023-2024-1 20211306 密码系统设计与实现课程学习笔记2
    20211306密码系统设计与实现课程学习笔记2学习任务详情自学教材第九章,提交学习笔记本章是复习C语言中的文件操作内容,结构化从文本文件操作,二进制文件操作两个大内容考虑,以前可能只关注文本文件的操作,我们以后更多的是操作二进制文件。文本文件中考虑字符读写,行读写,任意位......
  • uniapp中使用vue-i18n实现多语言
    一安装vue-i18nnpmivue-i18n@6二添加相关语言配置 如en.json:{"locale.auto":"System","locale.en":"English","locale.zh-hans":"简体中文","locale.zh-hant":"繁体中文","......
  • Linux 下的 10 个 PDF 软件
    本文是我们正在进行的有关Linux顶级工具系列的延续,在本系列中,我们将向您介绍最著名的Linux系统开源工具。随着互联网上越来越多地使用可移植文档格式(PDF)文件来获取在线书籍和其他相关文档,拥有PDF查看器/阅读器对于桌面Linux发行版非常重要。有几种可以在Linux上使......
  • 9-MySQL数据库 数据的备份与恢复
    1.date文件的备份2.mysqldump备份说明:mysqldump是MySQL数据库中的一个实用程序,它主要用于转储(备份)数据库。mysqldump通过生成一个SQL脚本文件,包含从头开始重新创建数据库所必需的(如CREATETABLE和INSERT等),来实现数据库的备份和转储。这样,你可以在任何时候通过运行这个脚本文......
  • Linux管理多版本node.js
    这里介绍的是Linux版本的nvm工具:一个nodejs版本管理工具!这里可以灵活切换node指定版本哟~下载地址:https://github.com/nvm-sh/nvm/releases/1.安装需要先安装git、curlyuminstall-ygitcurl这里很慢,需要登录。如果不小心退出来,需要重新执行一下安装脚步:这时候需要......
  • Linux命令(80)之sort
    linux命令之sort1.sort介绍linux命令sort用于将文本文件内容以行为单位加以排序;sort命令默认按每行的第一个字符排序,根据首字母的ASCII码值进行升序(从小到大排列)。sort的默认分隔符是空白(空格和tab),多少空白都算一个分隔符。2.sort用法sort[参数]filesort常用参数参数说明-n......
  • 关于 Spartacus My Account 菜单的数据源 - NavigationNode
    有朋友询问Spartacus的MyAccount菜单里,Mycompany菜单项的数据源是什么?Spartacus启动时,我们观察到这个OCCAPI:/occ/v2/powertools-spa/cms/pages?lang=en&curr=USD在其响应数据里,观察到navigationnode里包含了一个叫做MyCompany的菜单项:Backoffice是SAPCom......
  • 个人项目-java实现论文查重(更新中)
    本次作业GitHub项目链接:https://github.com/zzz-muxin/PlagiarismCheck作业详情这个作业属于哪个课程工程概论这个作业要求在哪里作业要求这个作业的目标学会利用GitHub进行项目开发,完成一个工程项目开发的详细过程需求题目:论文查重描述如下:设计一个论......
  • Rockchip RK3399 - USB触摸屏接口驱动
    ----------------------------------------------------------------------------------------------------------------------------开发板:NanoPC-T4开发板eMMC:16GBLPDDR3:4GB显示屏:15.6英寸HDMI接口显示屏u-boot:2023.04linux:6.3----------------------------------......