并发与竞争
并发的途径:
1.多线程并发访问
2.抢占式并发访问
3.中断并发访问
4.多核(SMP),核间并发访问
常用防止并发访问手段
1.原子操作
原子操作可以保护数据每次操作不被其它操作打断,从而实现数据不被其它操作修改,达到保护数据的目的
使用结构体
typedef struct {
int counter;
} atomic_t;
来初始化变量
定义原子变量atomic_t a;
定义时初始化atomic_t b = ATOMIC_INIT(0); //定义原子变量 b 并赋初值为 0
原子变量操作常用函数
函数 | 描述 |
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,如果结果为负就返回真,否则返回假 |
原子操作缺点:
原子操作智能对整形变量或者位变量进行保护,在实际环境使用中不太实用
2.自旋锁
自旋锁保证多个线程访问公共资源时,每次只有一个线程访问,其余要访问的线程进入自旋等待
自旋锁结构体
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;
定义自旋锁 spinlock_t lock;
自旋锁相关API函数
函数 | 描述 |
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。 |
被自旋锁保护的临界代码区内不能调用任何能够引起睡眠或阻塞的API函数,否则会引起死锁的发生。
中断也会引起自旋锁的死锁发生,因此在含有中断的公共资源中需要使用以下自旋锁API函数:
函数 | 描述 |
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) |
将中断状态恢复到以前的状态,并且激活本地中断, 释放自旋锁。 |
考虑到可移植性,设置驱动时最好使用带中断的自旋锁API函数。
自旋锁的缺点:
1.多个线程访问同一个带自旋锁的公共资源时,由于自旋锁的存在,只有一个线程可以访问公共资源,而其它的线程会进入原地等待状态,啥也不干。因此自旋锁只适用于运行时间短的驱动,若驱动时间太长会导致系统卡顿。
2.自旋锁保护的代码区内不能代用任何导致线程进入休眠的API函数,否则会发生死锁
3.不能递归调用带自旋锁的驱动模块,否则会发生自己把自己锁死的死锁
3.信号量
信号量结构体
struct semaphore {
raw_spinlock_t lock;
unsigned int count;
struct list_head wait_list;
};
信号量相关的API函数
函数 | 描述 |
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) | 释放信号量 |
信号量的特点:
1.在多线程访问同一个带信号量的驱动时,信号量可以使等待驱动的线程进入休眠,因此信号量可以适用于运行时间长的驱动。
2.信号量不能用于中断中。因为信号量会引起休眠,中断不是休眠。
3.若公共驱动运行时间比较短,使用信号量的效果就没有自旋锁好了。因为频繁的休眠和唤醒所用的时间会远大与信号量节省的时间。
4.互斥体
互斥体结构体
struct mutex {
/* 1: unlocked, 0: locked, negative: locked, possible waiters */
atomic_t count;
spinlock_t wait_lock;
};
互斥体相关API函数:
函数 | 描述 |
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.互斥体可导致休眠。因此不能在中断中使用互斥体,中断中智能使用自旋锁。
2.和信号量一样,在互斥体保护的临界区内可以调用引起阻塞的API函数
3.因为一次只用一个线程可以持有互斥体,因此互斥体智能由互斥体的持有者释放,并且互斥体的持有者不能递归调用互斥体上锁的共享资源。