关于原子操作、自旋锁、信号量、互斥体的使用场景和注意事项

news/2024/11/18 16:21:18/

原子操作

相关函数

原子操作API

typedef struct {
int counter;
} atomic_t;函数 												描述
ATOMIC_INIT(int i) 									定义原子变量的时候对其初始化。
int atomic_read(atomic_t *v)						读取 v 的值,并且返回。
void atomic_set(atomic_t *v, int i) 				向 v 写入 i 值。
void atomic_add(int i, atomic_t *v) 				给 v 加上 i 值。
void atomic_sub(int i, atomic_t *v) 				从 v 减去 i 值。
void atomic_inc(atomic_t *v)						给 v 加 1,也就是自增。
void atomic_dec(atomic_t *v)						从 v 减 1,也就是自减
int atomic_dec_return(atomic_t *v) 					从 v 减 1,并且返回 v 的值。
int atomic_inc_return(atomic_t *v)					给 v 加 1,并且返回 v 的值。
int atomic_sub_and_test(int i, atomic_t *v) 		从 v 减 i,如果结果为 0 就返回真,否则返回假
int atomic_dec_and_test(atomic_t *v)				从 v 减 1,如果结果为 0 就返回真,否则返回假
int atomic_inc_and_test(atomic_t *v) 				给 v 加 1,如果结果为 0 就返回真,否则返回假
int atomic_add_negative(int i, atomic_t *v)			给 v 加 i,如果结果为负就返回真,否则返回假

原子位操作API

函数 											描述
void set_bit(int nr, void *p) 					将 p 地址的第 nr 位置 1void clear_bit(int nr,void *p) 					将 p 地址的第 nr 位清零。
void change_bit(int nr, void *p) 				将 p 地址的第 nr 位进行翻转。
int test_bit(int nr, void *p) 					获取 p 地址的第 nr 位的值。
int test_and_set_bit(int nr, void *p)			将 p 地址的第 nr 位置 1,并且返回 nr 位原来的值。
int test_and_clear_bit(int nr, void *p) 		将 p 地址的第 nr 位清零,并且返回 nr 位原来的值。
int test_and_change_bit(int nr, void *p) 		将 p 地址的第 nr 位翻转,并且返回 nr 位原来的值。

应用场景

用于int变量进行原子操作,或者对位进行原子操作的场合。

思考

原子操作能保证操作不可切割,但是能保证互斥吗?在SMP的情况下,原子操作能保证不会有多个线程同时对一个变量进行原子操作吗?这样不也会造成得到的结果与预期结果不一样吗?

答:原子操作是能保证互斥的,这是通过硬件实现的,当进行原子操作时,会先锁住总线,这样除了本CPU其他的CPU是无法通过总线进行存取数据的。如此一来,原子操作不仅不可切分,在SMP环境下还能保证互斥。

注意事项

原子操作只能对整形变量或者位进行保护,但是,在实际的使用环境中怎么可能只有整形
变量或位这么简单的临界区。举个最简单的例子,设备结构体变量就不是整型变量,我们对于结构体中成员变量的操作也要保证原子性,在线程 A 对结构体变量使用期间,应该禁止其他的线程来访问此结构体变量,这些工作原子操作都不能胜任

自旋锁

自旋之意:进程没有获取到临界资源就会原地循环等待资源,不会立刻下CPU

相关函数

 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;函数 									描述
DEFINE_SPINLOCK(spinlock_t lock) 		定义并初始化一个自旋变量。
int spin_lock_init(spinlock_t *lock)	初始化自旋锁。
void spin_lock(spinlock_t *lock) 		获取指定的自旋锁,也叫做加锁。
void spin_unlock(spinlock_t *lock) 		释放指定的自旋锁。
int spin_trylock(spinlock_t *lock) 		尝试获取指定的自旋锁,如果没有获取到就返回 0
int spin_is_locked(spinlock_t *lock)	检查指定的自旋锁是否被获取,如果没有被获取就
返回非 0,否则返回 0。函数 										描述
void spin_lock_irq(spinlock_t *lock) 		禁止本地中断,并获取自旋锁。
void spin_unlock_irq(spinlock_t *lock) 		激活本地中断,并释放自旋锁。
void spin_lock_irqsave(spinlock_t *lock,unsigned long flags)					保存中断状态,禁止本地中断,并获取自旋锁。
void spin_unlock_irqrestore(spinlock_t
*lock, unsigned long flags)					将中断状态恢复到以前的状态,并且激活本地中断,释放自旋锁。

应用场景

自旋锁适用于SMP或支持抢占的单CPU下线程之间的并发访问,也就是用于线程与线程之间。并且保护的临界资源区应该很小,访问临界区的时间应该很短才适合用自旋锁。

注意事项

被自旋锁保护的临界区一定不能调用任何能够引起睡眠和阻塞的API 函数,否则的话会可能会导致死锁现象的发生。

不能递归申请自旋锁,因为一旦通过递归的方式申请一个你正在持有的锁,那么你就
必须“自旋”,等待锁被释放,然而你正处于“自旋”状态,根本没法释放锁。

在你占用信号量的同时不能占用自旋锁,因为在你等待信号量时可能会睡眠,而在持有自旋锁时是不允许睡眠的。

思考

自旋锁如何引起死锁?

!!自旋锁会自动禁止抢占!!,假设在单CPU的情况下,当线程 A得到锁以后会暂时禁止内核抢占。如果线程 A 在持有锁期间进入了休眠状态,那么线程 A 会自动放弃 CPU 使用权。线程 B 开始运行,线程 B 也想要获取锁,但是此时锁被 A 线程持有,而且内核抢占还被禁止了!线程 B 无法被调度出去,那么线程 A 就无法运行,锁也就无法释放,此时死锁发生。

中断中能使用自旋锁吗,如果能使用自旋锁需要注意什么?

中断中是可以使用自旋锁的,因为自旋锁不会引起阻塞和睡眠。中断中使用自旋锁的时候要预防死锁的发生:当线程A获取自旋锁后,在访问临界资源的过程中,如果此时中断到来,并且中断程序也要获取自旋锁并且访问临界资源,那么这个时候就会发生死锁。那么至于死锁的原因是,当中断服务开始执行会禁止本CPU调度,所以只有中断服务执行完,CPU才会空闲出来。由于中断服务中在自旋等待自旋锁,但是自旋锁被A线程持有,中断服务没有执行完A线程就无法上机,就无法释放自旋锁,死锁自此发生。

因此如果需要在中断中使用自旋锁访问临界资源。那么在线程中获取锁之前一定要禁止本地中断。

如何禁止和打开本地中断?

除了下面的函数,如果需要在中断中使用自旋锁,那么可以使用上面的函数。

void local_irq_save(unsigned long flags);
void local_irq_disable(void);//只有我们知道中断并未在其他地方被禁用的情况下,才能使用这个void local_irq_save(unsigned long flags);
void local_irq_disable(void);

信号量

相关函数

struct semaphore {raw_spinlock_t lock;unsigned int count;struct list_head wait_list;
};函数 										描述
DEFINE_SEAMPHORE(name) 						定义一个信号量,并且设置信号量的值为 1void sema_init(struct semaphore *sem,int val) 									初始化信号量 sem,设置信号量值为 val。
void down(struct semaphore *sem)			获取信号量,因为会导致休眠,因此不能在中断中使用。
int down_trylock(struct semaphore *sem);	尝试获取信号量,如果能获取到信号量就获取,并且返回 0。如果不能就返回非 0,并且不会进入休眠。
int down_interruptible(
struct semaphore *sem)						获取信号量,和 down 类似,只是使用 down 进入休眠状态的线程不能被信号打断。而使用此函数进入休眠以后是可以被信号打断的。
void up(struct semaphore *sem) 				释放信号量

应用场景

信号量用于生产者和消费者的问题,但是也可以用作互斥访问临界资源,此时信号量初始化为1。

因为信号量可以使等待资源线程进入休眠状态,因此适用于那些占用资源比较久的场
合。

注意事项

down函数没有获取到信号量会导致睡眠,因此不能在中断中使用

信号量不能用于中断中,因为信号量会引起休眠,中断不能休眠。

如果共享资源的持有时间比较短,那就不适合使用信号量了,因为频繁的休眠、切换
线程引起的开销要远大于信号量带来的那点优势。

思考

为什么在ISR(中断服务程序)中禁止睡眠(linux睡眠和挂起和阻塞都不会消耗cpu资源)?

因为睡眠会导致call scheduler以选择下一个可运行的程序,但是进程调度需要process contex,由于ISR中没有process contex,所以无法进行调度。

互斥体(其实是一种互斥信号)

相关函数

struct mutex {/* 1: unlocked, 0: locked, negative: locked, possible waiters */atomic_t count;spinlock_t wait_lock;
};函数 										描述
DEFINE_MUTEX(name) 							定义并初始化一个 mutex 变量。
void mutex_init(mutex *lock) 				初始化 mutex。
void mutex_lock(struct mutex *lock)			获取 mutex,也就是给 mutex 上锁。如果获取不到就进休眠。
void mutex_unlock(struct mutex *lock) 		释放 mutex,也就给 mutex 解锁。
int mutex_trylock(struct mutex *lock)		尝试获取 mutex,如果成功就返回 1,如果失败就返回 0int mutex_is_locked(struct mutex *lock)		判断 mutex 是否被获取,如果是的话就返回1,否则返回 0int mutex_lock_interruptible(struct mutex *lock)使用此函数获取信号量失败进入休眠以后可以被信号打断。

应用场景

一次只有一个线程可以访问共享资源

注意事项

mutex 可以导致休眠,因此不能在中断中使用 mutex,中断中只能使用自旋锁。

和信号量一样,mutex 保护的临界区可以调用引起调度 API 函数。因为mutex和信号量都没有禁止内核抢占。

因为一次只有一个线程可以持有 mutex,因此,必须由 mutex 的持有者释放 mutex。并且 mutex 不能递归上锁和解锁。

顺序自旋锁

使用顺序锁的话可以允许在写的时候进行读操作,也就是实现同时读写,但是不允许同时进行并发的写操作。虽然顺序锁的读和写操作可以同时进行,但是如果在读的过程中发生了写操作,最好重新进行读取,保证数据完整性。顺序锁保护的资源不能是指针,因为如果在写操作的时候可能会导致指针无效,而这个时候恰巧有读操作访问指针的话就可能导致意外发生,比如读取野指针导致系统崩溃

读写自旋锁

读写自旋锁为读和写操作提供了不同的锁,一次只能允许一个写操作,也就是只能一个线
程持有写锁,而且不能进行读操作。但是当没有写操作的时候允许一个或多个线程持有读锁,可以进行并发的读操作

读写信号量

只有一个写着可以持有读信号量,可以同时多个读者持有信号量。所有读写信号量都只对写者互斥。


http://www.ppmy.cn/news/12821.html

相关文章

Vue3+TypeScript系统学习(十五) - 详解Vue3 Composition API(二)

前面给大家分享了Options API语法中代码的复用、Options API编码的优缺点,以及setup函数,响应式API等,这次将给大家分享Vue3 Composition API中的计算属性,侦听器,生命周期函数,Provide和Inject等。 1.1 co…

【Leetcode】NC31 第一个只出现一次的字符(牛客网)、面试题 01.01. 判定字符是否唯一

作者:一个喜欢猫咪的的程序员 专栏:《Leetcode》 喜欢的话:世间因为少年的挺身而出,而更加瑰丽。 ——《人民日报》 NC31 第一个只出现一次的字符 第一个只出现一次的字符_牛客题霸_牛客网【牛…

论文分享-《基于数据驱动多输出 ARMAX 建模的高炉十字测温中心温度》

1.简介 最近在学习研究NARMAX,故也分享下自己看的一篇论文。 2018 年 3 月 的《基于数据驱动多输出 ARMAX 建模的高炉十字测温中心温度》。主要是采用NARMAX模型进行预测,多输入多输出,有5个输出,预测中心五个点位的温度。下面讲…

第一章 TCP/IP 协议

作者简介:一名云计算网络运维人员、每天分享网络与运维的技术与干货。 座右铭:低头赶路,敬事如仪 个人主页:网络豆的主页​​​​​​ 目录 前言 一.什么是TCP/IP ​编辑 二.什么是协议 1.三要素 2.协议与标准区别 三.广…

flowable编译

git clone -b flowable-release-6.7.2 https://github.com/flowable/flowable-engine.git下载之后File-Open,打开工程,modules是核心代码模块 找到flowable-root.xml按下altf12 ,启动Terminal终端输入命令:mvn clean package -Ds…

2023/1/14 js基础学习

1 js基础学习-基本数据类型基本语法 请参考 https://blog.csdn.net/m0_48964052?typeblog https://gitee.com/hongjilin/hongs-study-notes/blob/master/%E7%BC%96%E7%A8%8B_%E5%89%8D%E7%AB%AF%E5%BC%80%E5%8F%91%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/HTMLCSSJS%E5%9F%BA%E…

云原生|kubernetes|2022年底cks真题解析(1-10)

前言: cka和cks认证真的比较恶心,他们的那个PSI Bridge Secure Browser真的非常卡。 吐槽完毕,不废话,直接上真题解析。 CKS总共是16道题,题目顺序是打乱的,由于认证系统非常卡,因此&#xf…

Pycharm入门搭建Django 项目

一、环境准备 1、pycharm版本 2、python版本 二、创建项目 击左上角的 File -> New Project 点击Create创建完成之后页面等待下载环境 查看Django的版本 python -m django --version 启动项目 python manage.py runserver 三、后记 在启动 Django 项目的时候我发现控制台…