并发控制的解决办法是保证对共享资源的互斥访问,所谓的互斥访问是指一个执行单元在访问共享资源的时候,其他的执行单元被禁止访问。
访问共享资源的代码区域称为临界区,临界区需要被以某种互斥机制加以保护。
互斥机制:
中断屏蔽 原子操作 自旋锁 信号量 互斥体都是Linux设备驱动中采用的互斥途径
中断屏蔽
只能解决本CPU,无法解决SMP多CPU引发的竞态
原子操作
定义原子变量: static atomic_t xxx_available() = ATOMIC_INIT(1);
atomic_inc
atomic_dec
自旋锁
主要针对SMP或单CPU但内核可抢占的情况
Spin Lock:原地打转
how to use:
定义自旋锁 spinlock_t lock;
初始化自旋锁 spin_lock_init(&lock);
获得自旋锁 spin_lock(&lock) spin_trylock(&lock)
...//临界区
释放自旋锁 spin_unlock(&lock)
1.只有在占用锁时间短的情况下使用才合理
2.可能导致死锁
3.在自旋锁锁定期间不能调用可能引起进程调度的函数 copy_from_user() msleep() kmalloc()等,否则可能导致内核崩溃
此外还有,读写锁rwlock ,顺序锁 seqlock,读-复制-更新(RCU read-copy-update)
信号量
Semaphore
定义信号量 struct semaphore sem;
初始化信号量 void sema_init(struct semaphore *sem,int val)
获得信号量 void down(struct semaphore *sem) down_interruptible() down_trylock()
释放信号量 void up(struct semaphore *sem)
与自旋锁不同的是,当获取不到信号量时,进程不会原地打转,而是进入休眠等待状态
互斥体
mutex
定义并初始化互斥体 struct mutex crazy_mutex; mutex_init(&crazy_mutex)
获得互斥体 void mutex_lock(struct mutex* lock)
int mutex_lock_interruptible(struct mutex *lock)
int mutex_trylock(struct mutex *lock)
释放互斥体 void mutex_unlock(struct mutex *lock)
1.当锁不能被获取时,使用互斥体的开销是进程上下文切换的时间,而自旋锁的开销是等待获取自旋锁。若临界区比较小,可使用自旋锁,若临界区很大,则使用互斥体。
2.互斥体所保护的临界区可包含可能引起阻塞的代码,而自旋锁则绝对要避免用来保护这样代码的临界区。因为阻塞意味着要进行进程的切换,如果进程被切换出去后,另一个进程企图获取本自旋锁,死锁就会发生。
3.互斥体存在于进程上下文,因此,如果被保护的共享资源需要在中断或软中断情况下使用,则应选择自旋锁。
完成量
定义完成量 struct completion com;
初始化完成量 init_completion(&com);
等待完成量 void wait_for_completion(struct completion *c)
唤醒完成量 void complete(struct completion *c) void complete_all(struct completion *c)