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

原子操作

相关函数

原子操作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 不能递归上锁和解锁。

顺序自旋锁简介

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

读写自旋锁简介

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

读写信号量简介

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

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值