05.并发和竞争
- 并发和竞争对于驱动来说就是对临界区资源的保护,很多进程同时运行,同时访问公共设备和数据,要保证这么多进程有序进行访问
- 加锁和互斥的目的是为了保护共享资源(数据或外设地址)不被多个线程同时访问,而不是保护代码被同时执行
5.1 信号量(semaphore
)
5.1.1 基本概念
- 作用:保护临界区资源,同一时刻只能有一个进程获取信号量,访问保护的临界区;一个进程未释放信号量,另外的进程试图访问临界区,将不能获取信号量,会睡眠等待释放信号量;多个进程访问未释放信号量的临界区,这些进程将排队等待并睡眠,直到信号量释放,一个一个唤起
- 应用场景:主要应用在进程上下文环境中,可以睡眠;不能用在不可睡眠的地方,如中断服务程序
- 操作:信号量为1表示资源可用,为0表示资源不可用,对信号量的加减主要涉及PV操作,进入临界区调用P操作(
down
)将信号量减1,退出临界区调用V操作(up
)将信号量加1
5.1.2 函数接口
- 头文件(
#include <linux/semaphore.h>
) - 初始化
#define DEFINE_SEMAPHORE(name) \
struct semaphore name = __SEMAPHORE_INITIALIZER(name, 1)
inline void sema_init(struct semaphore *sem, int val)
extern void down(struct semaphore *sem);
extern int __must_check down_interruptible(struct semaphore *sem);
extern int __must_check down_killable(struct semaphore *sem);
extern int __must_check down_trylock(struct semaphore *sem);
extern int __must_check down_timeout(struct semaphore *sem, long jiffies);
extern void up(struct semaphore *sem);
5.2 Completions机制
5.2.1 基本概念
- 应用场景:一个动作需要等待另外一个动作完成,才能继续下去,这种场景用completions,虽然用信号量也可以做到,但是completions在这种场景要比信号量好,性能上和各种极端情况都要好;completions典型应用是模块退出和线程退出时一起退出
- completions等待另外动作完成的进程是不杀死的
5.2.2 函数接口
- 头文件:(
#include <linux/completion.h>
) - 初始化
#define DECLARE_COMPLETION(work) \
struct completion work = COMPLETION_INITIALIZER(work)
struct completion comp;
init_completion(&comp);
extern void wait_for_completion(struct completion *);
extern void wait_for_completion_io(struct completion *);
extern int wait_for_completion_interruptible(struct completion *x);
extern int wait_for_completion_killable(struct completion *x);
extern unsigned long wait_for_completion_timeout(struct completion *x,unsigned long timeout);
extern unsigned long wait_for_completion_io_timeout(struct completion *x,unsigned long timeout);
extern long wait_for_completion_interruptible_timeout(struct completion *x, unsigned long timeout);
extern long wait_for_completion_killable_timeout(struct completion *x, unsigned long timeout);
extern bool try_wait_for_completion(struct completion *x);
extern bool completion_done(struct completion *x);
extern void complete(struct completion *);
extern void complete_all(struct completion *);
void complete_and_exit(struct completion *c, long retval);
5.3 自旋锁(spinlock
)
5.3.1 基本概念
- 作用:自旋锁和信号量功能一样,保护临界区
- 自旋锁保护的临界区code是原子性的,在获取锁时会关闭中断
- 自旋锁可以用在不可睡眠的地方,如中断服务程序,自旋锁的原子性要求临界区的code不能睡眠,否则会死锁
5.3.2 函数接口
- 头文件:(
#include <linux/spinlock.h>
) - 初始化
struct spinlock_t splk;
spin_lock_init(&splk);
void spin_lock(spinlock_t *lock);
void spin_lock_irqsave(spinlock_t *lock, unsigned long flags);
void spin_lock_irq(spinlock_t *lock);
void spin_lock_bh(spinlock_t *lock);
void spin_unlock(spinlock_t *lock);
void spin_unlock_irqrestore(spinlock_t *lock, unsigned long flags);
void spin_unlock_irq(spinlock_t *lock);
void spin_unlock_bh(spinlock_t *lock);
5.4 顺序锁(seqlock
)
5.4.1 基本概念
- 顺序锁是对读写锁的一种优化,提高了读锁和写锁的独立性;写锁不会被读锁阻塞,读锁也不会被写锁阻塞;写锁会被写锁阻塞
- 如果读执行单元在读操作期间,写执行单元已经发生了写操作,那么,读执行单元必须重新去读数据,以便确保读到的数据是完整的;这种锁在读写操作同时进行的概率比较小,性能是非常好的,而且它允许读写操作同时进行,因而更大地提高了并发性。
- 限制:它必须要求被保护的共享资源中不能含有指针,因为写执行单元可能会使指针失效,当读执行单元如果正要访问该指针时,系统就会崩溃
5.4.2 函数接口
- 头文件(
#include <linux/seqlock.h>
) - 初始化
DEFINE_SEQLOCK(seql) seqlock_init(&seql);
seqlock_t seql;
seqlock_init(&seql);
unsigned int read_seqbegin(seqlock_t *lock);
int read_seqretry(seqlock_t *lock, unsigned int seq);
void write_seqlock(seqlock_t *lock);
void write_seqlock_irqsave(seqlock_t *lock, unsigned long flags);
void write_seqlock_irq(seqlock_t *lock);
void write_seqlock_bh(seqlock_t *lock);
void write_sequnlock(seqlock_t *lock);
void write_sequnlock_irqrestore(seqlock_t *lock, unsigned long flags);
void write_sequnlock_irq(seqlock_t *lock);
void write_sequnlock_bh(seqlock_t *lock);
5.5 原子变量(atomic
)
5.5.1 基本概念
- 应用场景:保护的数据是一个简单的整数时,用其他的浪费(信号量、锁等对系统性能影响较大)
5.5.2 函数接口
- 头文件(
#include <asm/atomic.h>
) - 初始化(
atomic_t v = ATOMIC_INIT(value);
) - API
oid atomic_set(atomic_t *v, int i);
int atomic_read(atomic_t *v);
void atomic_add(int i, atomic_t *v);
void atomic_sub(int i, atomic_t *v);
void atomic_inc(atomic_t *v);
void atomic_dec(atomic_t *v);
int atomic_inc_and_test(atomic_t *v);
int atomic_dec_and_test(atomic_t *v);
int atomic_sub_and_test(int i, atomic_t *v);
int atomic_add_negative(int i, atomic_t *v);
int atomic_add_return(int i, atomic_t *v);
int atomic_sub_return(int i, atomic_t *v);
int atomic_inc_return(atomic_t *v);
int atomic_dec_return(atomic_t *v);
5.6 原子位(bitops
)
5.6.1 基本概念
- 应用场景:对于位操作用bitops比较快,而且对系统影响小,还是原子性的
5.6.2 函数接口
- 头文件(
#include <asm/bitops.h>
) - 初始化
int a=0;
set_bit(3,&a)
void set_bit(nr, void *addr);
void clear_bit(nr, void *addr);
void change_bit(nr, void *addr);
int test_bit(nr, void *addr);
int test_and_set_bit(nr, void *addr);
int test_and_clear_bit(nr, void *addr);
int test_and_change_bit(nr, void *addr);
5.7 RCU(read-copy-update
)
5.7.1 基本概念
- 功能:主要用来保护由指针指向的对象(而不保护指针本身),读不阻塞,读操作的优先权大于写操作(与
seqlock
相反),写的时候不阻塞读,写的时候发现在读,要等待读完成,才销毁指针指向旧地址空间数据,然后更新指针指向新的地址 - 应用场景:应用在读者与写者场景,目前性能最好(信号量、自旋锁、顺序锁都有实现读者写者的专用接口,但性能都不及
RCU
)
5.7.2 函数接口
- 头文件(
#include <linux/rcupdate.h>
) - 函数接口
rcu_read_lock();
rcu_read_unlock();
extern void call_rcu(struct rcu_head *head,void (*func)(struct rcu_head *head));
5.8 应用
5.9 本文参考