并发与竞争


并发的途径:

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_spinlockdep_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.因为一次只用一个线程可以持有互斥体,因此互斥体智能由互斥体的持有者释放,并且互斥体的持有者不能递归调用互斥体上锁的共享资源。