Linux·spin_lock的使用

news/2025/2/23 0:53:18

自旋锁

内核当发生访问资源冲突的时候,可以有两种锁的解决方案选择:

  • 一个是原地等待
  • 一个是挂起当前进程,调度其他进程执行(睡眠)

Spinlock 是内核中提供的一种比较常见的锁机制,自旋锁是“原地等待”的方式解决资源冲突的,即,一个线程/进程获取了一个自旋锁后,另外一个线程/进程期望获取该自旋锁,获取不到,只能够原地“打转”(忙等待)。由于自旋锁的这个忙等待的特性,注定了它使用场景上的限制 —— 自旋锁不应该被长时间的持有(消耗 CPU 资源)。

但是Linux中,普通的spinlock由于不带额外的语义,用起来反而要非常小心,尤其不要误解了忙等待的含义。

在Linux kernel中执行的代码大体分normal和interrupt context两种。tasklet/softirq可以归为normal因为他们可以进入等待;nested interrupt是interrupt context的一种特殊情况,当然也是interrupt context。Normal级别可以被interrupt抢断,interrupt会被另一个interrupt抢断,但不会被normal中断。各个 interrupt之间没有优先级关系,只要有可能,每个interrupt都会被其他interrupt中断。

搞清楚优先级后,我们就需要明确忙等待的含义了,忙等待是指当前线程/进程在获取不到锁后不会主动放弃cpu,也就是我不给,但是由于线程/进程之间存在调度优先级,其他高优先级的线程/进程是可以抢占cpu的,所以不是说忙等待不是一直都能占用cpu的。

那这种情况会带来什么样的问题呢?

看下面这段伪代码,先考虑只有单cpu的情况:

extern spinlock_t lock;
spin_lock(&lock);
// run...
spin_unlock(&lock);

他能正常工作吗?答案是有可能。在某些情况下,这段代码可以正常工作,但如果发生如下调用关系:

// in normal run level
extern spinlock_t lock;
spin_lock(&lock);
// run...
            -->>// interrupted by IRQ ...
                // in IRQ
                spin_lock(&lock);
                // run...
                spin_unlock(&lock);
            <<--
spin_unlock(&lock);

喔,我们在normal级别下获得了一个spinlock,正当我们想做什么的时候,我们被interrupt打断了,CPU转而执行interrupt level的代码,它也想获得这个lock,于是“死锁”发生了!解决方法很简单,看看我们第二次尝试:

extern spinlock_t lock;
cli; // disable interrupt on current CPU
spin_lock(&lock);
// run...   -->> //IRQ off
spin_unlock(&lock);
sti; // enable interrupt on current CPU

在获得spinlock之前,我们先把当前CPU的中断禁止掉,然后获得一个lock;在释放lock之后再把中断打开。这样,我们就防止了死锁。事实上,Linux提供了一个更为快捷的方式来实现这个功能:

extern spinlock_t lock;
spin_lock_irq(&lock);
// run...
spin_unlock_irq(&lock);

如果没有nested interrupt,所有这一切都很好。

加上nested interrupt,我们再来看看这个例子:


// code 1
extern spinlock_t lock1;
spin_lock_irq(&lock);
// run..
spin_unlock_irq(&lock);

// code 2
extern spinlock_t lock2;
spin_lock_irq(&lock2);
// run...
spin_unlock_irq(&lock2);
 

Code 1和code 2都可运行在interrupt下,我们很容易就可以想到这样的运行次序():

Code 1                                  Code 2
                                        
extern spinlock_t lock1;
spin_lock_irq(&lock1); //这里会关闭中断
//函数调用到 -->> code2                  -->>
                                        extern spinlock_t lock2;
                                        spin_lock_irq(&lock2); 
                                        // run... 
                                        spin_unlock_irq(&lock2);//这里会打开中断
//注意:这里可能会运行不正常了,因为对于code1而已,我现在是需要关闭中断运行的
// run...
spin_unlock_irq(&lock1); 

问题是在第二个spin_unlock_irq后这个CPU的中断已经被打开,“死锁”的问题又会回到我们身边!

解决方法是我们在每次关闭中断前纪录当前中断的状态,然后恢复它而不是直接把中断打开。

Code 1                                  Code 2
                                        
extern spinlock_t lock1;
unsigned long flags;
local_irq_save(flags);
spin_lock(&lock1); 
//函数调用到 -->> code2                  -->>
                                        extern spinlock_t lock2;
                                        unsigned long flags;
                                        local_irq_save(flags);
                                        spin_lock(&lock2); 
                                        // run... 
                                        spin_unlock(&lock2);
                                        local_irq_restore(flags);
// run...
spin_unlock(&lock1); 
local_irq_restore(flags);

Linux同样提供了更为简便的方式:

Code 1                                  Code 2
                                        
extern spinlock_t lock1;
unsigned long flags;
spin_lock_irqsave(&lock1, flags); 
//函数调用到 -->> code2                  -->>
                                        extern spinlock_t lock2;
                                        unsigned long flags;
                                        spin_lock_irqsave(&lock2, flags); 
                                        // run... 
                                        spin_unlock_irqrestore(&lock2, flags);
// run...
spin_unlock_irqrestore(&lock1, flags);

说了这么多基于单cpu的情况,那么多cpu呢?其实多cpu反而无需考虑这么多,因为多cpu之间没有优先级抢占问题,不会带来死锁。

总结:

  1. 如果被保护的共享资源只在进程上下文访问和软中断上下文访问,那么当在进程上下文访问共享资源时,可能被软中断打断,从而可能进入软中断上下文来对被保护的共享资源访问,因此对于这种情况,对共享资源的访问必须使用spin_lock_bh和spin_unlock_bh来保护。当然使用spin_lock_irq和spin_unlock_irq以及spin_lock_irqsave和spin_unlock_irqrestore也可以,它们失效了本地硬中断,失效硬中断隐式地也失效了软中断。但是使用spin_lock_bh和spin_unlock_bh是最恰当的,它比其他两个快。
  2. 如果被保护的共享资源只在进程上下文和tasklet或timer上下文访问,那么应该使用与上面情况相同的获得和释放锁的宏,因为tasklet和timer是用软中断实现的。
  3. 如果被保护的共享资源只在一个tasklet或timer上下文访问,那么不需要任何自旋锁保护,因为同一个tasklet或timer只能在一个CPU上运行,即使是在SMP环境下也是如此。实际上tasklet在调用tasklet_schedule标记其需要被调度时已经把该tasklet绑定到当前CPU,因此同一个tasklet决不可能同时在其他CPU上运行。timer也是在其被使用add_timer添加到timer队列中时已经被帮定到当前CPU,所以同一个timer绝不可能运行在其他CPU上。当然同一个tasklet有两个实例同时运行在同一个CPU就更不可能了。
  4. 如果被保护的共享资源只在两个或多个tasklet或timer上下文访问,那么对共享资源的访问仅需要用spin_lock和spin_unlock来保护,不必使用_bh版本,因为当tasklet或timer运行时,不可能有其他tasklet或timer在当前CPU上运行。
  5. 如果被保护的共享资源只在一个软中断(tasklet和timer除外)上下文访问,那么这个共享资源需要用spin_lock和spin_unlock来保护,因为同样的软中断可以同时在不同的CPU上运行。
  6. 如果被保护的共享资源在两个或多个软中断上下文访问,那么这个共享资源当然更需要用spin_lock和spin_unlock来保护,不同的软中断能够同时在不同的CPU上运行。
  7. 如果被保护的共享资源在软中断(包括tasklet和timer)或进程上下文和硬中断上下文访问,那么在软中断或进程上下文访问期间,可能被硬中断打断,从而进入硬中断上下文对共享资源进行访问,因此,在进程或软中断上下文需要使用spin_lock_irq和spin_unlock_irq来保护对共享资源的访问。而在中断处理句柄中使用什么版本,需依情况而定,如果只有一个中断处理句柄访问该共享资源,那么在中断处理句柄中仅需要spin_lock和spin_unlock来保护对共享资源的访问就可以了。因为在执行中断处理句柄期间,不可能被同一CPU上的软中断或进程打断。但是如果有不同的中断处理句柄访问该共享资源,那么需要在中断处理句柄中使用spin_lock_irq和spin_unlock_irq来保护对共享资源的访问。

        在使用spin_lock_irq和spin_unlock_irq的情况下,完全可以用spin_lock_irqsave和spin_unlock_irqrestore取代,那具体应该使用哪一个也需要依情况而定,如果可以确信在对共享资源访问前中断是使能的,那么使用spin_lock_irq更好一些。

  因为它比spin_lock_irqsave要快一些,但是如果你不能确定是否中断使能,那么使用spin_lock_irqsave和spin_unlock_irqrestore更好,因为它将恢复访问共享资源前的中断标志而不是直接使能中断。

  当然,有些情况下需要在访问共享资源时必须中断失效,而访问完后必须中断使能,这样的情形使用spin_lock_irq和spin_unlock_irq最好。
  spin_lock用于阻止在不同CPU上的执行单元对共享资源的同时访问以及不同进程上下文互相抢占导致的对共享资源的非同步访问,而中断失效和软中断失效却是为了阻止在同一CPU上软中断或中断对共享资源的非同步访问


http://www.niftyadmin.cn/n/5862886.html

相关文章

Excell 代码处理

文章目录 Excell 代码处理cvc格式xlsl格式小结 Excell 代码处理 有时候要对excell进行分析&#xff0c;或者数据的导入导出&#xff0c;这个时候如果可以用代码读写分析操作那么会方便很多 cvc格式 CSV&#xff08;Comma-Separated Values&#xff0c;逗号分隔值&#xff09;是…

Visual studio 2022 将打开文件的方式由单击改为双击

1. 打开vs2022&#xff0c;选择Tools -> Options打开Options设置页面 2. 在左侧依次展开Environment, 选择Tabs and Windows 3. 在右侧面板往下拖拽滚动条&#xff0c;找到Preview Tab section, unchecked "Preview selected files in Solution Explorer (Altclick t…

pikachu之CSRF防御:给你的请求加上“网络身份证”

CSRF防御&#xff1a;给你的请求加上“网络身份证” 上集回顾 ​在上一章节中&#xff0c;我们化身“遥控黑客”&#xff0c;用GET请求和POST表单把CSRF漏洞玩得风生水起&#xff0c;体验了“隔空改签名”等骚操作。今天&#xff0c;我们将从攻击者变身防御者&#xff0c;揭秘…

MySQL八股学习笔记

文章目录 一、MySQL结构1.宏观结构1.1.Server层1.2.存储引擎层 2.建立链接-连接器3.查询缓存4.解析SQL-解析器&#xff08;1&#xff09;词法分析&#xff08;2&#xff09;语法分析 5.执行SQL5.1.预处理器 prepare5.2.优化器 optimize5.3.执行器 execute&#xff08;1&#xf…

Day15-后端Web实战-登录认证——会话技术JWT令牌过滤器拦截器

目录 登录认证1. 登录功能1.1 需求1.2 接口文档1.3 思路分析1.4 功能开发1.5 测试 2. 登录校验2.1 问题分析2.2 会话技术2.2.1 会话技术介绍2.2.2 会话跟踪方案2.2.2.1 方案一 - Cookie2.2.2.2 方案二 - Session2.2.2.3 方案三 - 令牌技术 2.3 JWT令牌2.3.1 介绍2.3.2 生成和校…

WARNING: pip is configured with locations that require TLS/SSL

一、报错 WARNING: pip is configured with locations that require TLS/SSL, however the ssl module in Python is not available. 二、故障排查 1、检查Python3环境中的SSL支持: 打开终端或命令提示符,进入你的Python环境目录。 运行以下命令来查看可用的SSL版本: [r…

开源且免费的CMS系统有哪几个可以放心用?

既开源又免费的两全其美的CMS不多见&#xff0c;不过总会存在一些个例&#xff0c;给用户们带来更具有建设性的选择&#xff0c;以下是一些开源免费且值得信赖的CMS系统&#xff0c;可以根据你的需求选择合适的平台&#xff1a; 1、WordPress ▷ 特点&#xff1a;全球最流行的…

解决MySQL错误:You can‘t specify target table ‘xxx‘ for update in FROM clause

目录 错误复现场景原因分析解决方案方法1&#xff1a;使用派生表&#xff08;推荐&#xff09;方法2&#xff1a;改用JOIN操作方法3&#xff1a;使用临时表 总结 在编写MySQL的UPDATE或DELETE语句时&#xff0c;如果子查询中直接引用了要操作的目标表&#xff0c;可能会遇到一个…