一、原子操作
(对整形变量或者位保护)
用到的函数:
函数 | 描述 |
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,如果结果为负就返回真,否则返回假 |
使用代码说明:
1、定义原子变量:
atomic_t lock;
2、初始化设置为1
atomic_set(&lock, 1); /* 原子变量初始值为1 */
3、设备打开的时候原子变量减1判断是否能使用
/* 通过判断原子变量的值来检查设备有没有被别的应用使用 */
if (atomic_dec_and_test(&lock)) {
/*设备未被占用,可以使用*/
}
else
{
atomic_inc(&lock); /* 设备没使用成功,前面减的1加回去 */
return -EBUSY; /* 设备被占用,返回忙 */
}
4、关闭设备原子变量加回1,释放原子变量
atomic_inc(&lock);
二、自旋锁
用到的函数:
函数 | 描述 |
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) | 检查指定的自旋锁是否被获取,如果没有被获取就 |
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) | 将中断状态恢复到以前的状态,并且激活本地中断, |
void spin_lock_bh(spinlock_t *lock) | 关闭下半部,并获取自旋锁。 |
void spin_unlock_bh(spinlock_t *lock) | 打开下半部,并释放自旋锁。 |
由于自旋锁获得后会禁止抢占,内核暂时不会调度其他进程
因此使用自旋锁要注意:
- 不能在自旋锁保护的临界区内调用任何引起睡眠和阻塞的API防止死锁
- 不能在递归申请自旋锁,以免自锁
- 中断也使用自旋锁时要注意外面的自旋锁最好用这组函数申请和释放spin_lock_irqsave/ spin_unlock_irqrestore,中断中用spin_lock/spin_unlock避免下面的情况:
- 中断下半部里面用spin_lock_bh/spin_unlock_bh
- 自旋锁保护的临界区的时间应尽量短
使用代码说明:
spinlock_t lock; /* 定义自旋锁 */
spin_lock_init(&lock); /* 初始化自旋锁 */
/* 线程A */
void functionA (){
unsigned long flags; /* 中断状态 */
spin_lock_irqsave(&lock, flags) /* 获取锁 */
/* 临界区 */
spin_unlock_irqrestore(&lock, flags) /* 释放锁 */
}
/* 中断服务函数 */
void irq() {
spin_lock(&lock) /* 获取锁 */
/* 临界区 */
spin_unlock(&lock) /* 释放锁 */
}
三、信号量
用到的函数:
函数 | 描述 |
DEFINE_SEAMPHORE(name) | 定义一个信号量,并且设置信号量的值为1。 |
void 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) | 释放信号量 |
用信号量等待信号时会进入休眠,并等待唤醒
使用信号量要注意的地方:
- 信号量会导致休眠、切换开销比较大,适合临界区时间比较长的场合。
- 中断不能休眠,信号量不能用在中断里。
使用代码说明:
#include <linux/semaphore.h>
struct semaphore sem; /* 信号量 */
/* 初始化信号量 */
147 sema_init(&sem, 1);
/* 获取信号量,进入休眠状态的进程可以被信号打断 */
if (down_interruptible(&sem)) {
return -ERESTARTSYS;
}
up(&sem); /* 释放信号量,信号量值加1 */
四、互斥体
用到的函数:
函数 | 描述 |
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,如果失败就返回0。 |
int mutex_is_locked(struct mutex *lock) | 判断mutex是否被获取,如果是的话就返回1,否则返回0。 |
int mutex_lock_interruptible(struct mutex *lock) | 使用此函数获取信号量失败进入休眠以后可以被信号打断。 |
信号量的值设置为1也能实现互斥的功能,但是linux有提供互斥机制,用这个会更高效。
使用注意:
- mutex会导致休眠,不能中断里面用;
- 一次只有一个线程可以持有mutex,所以不能递归调用mutex。
使用代码说明:
struct mutex lock; /* 定义一个互斥体 */
mutex_init(&lock); /* 初始化互斥体 */
mutex_lock(&lock); /* 上锁 */
/* 临界区 */
mutex_unlock(&lock); /* 解锁 */
五、原子操作、自旋锁、信号量、互斥体的选择
竞争处理机制 | 临界区情况 | 是否能在中断用 |
原子操作 | 一个变量或者位数据 | 能 |
自旋锁 | 不止一个变量,一小段代码 | 能 |
信号量 | 很多代码处理时间长 | 不能 |
互斥体 | 很多代码处理时间长 | 不能 |