1. 并发的原因
并发可能会导致竞态(race condition),竞态会导致对共享资源的非控制访问, 产生非预期的结果。Linux的并发来源于下面几个方面:
正在运行的多个用户进程以无法预知的方式访问驱动程序代码
外部设备的中断异步的发生,导致正在运行的进程或者驱动代码被中断
linux的软件抽象(如timer, tasklet, workqueue)也在异步运行着
现在SMP的处理器架构,导致驱动程序可能会在不同的CPU上运行
可抢占的内核调度算法,可能导致驱动代码随时被抢占
因此在内核中,并发不可避免。竞态是因为对资源的共享访问造成的,因此设计程序时,应该尽量避免资源的共享,避免申明全局变量。没有共享访问,就没有竞态的发生。当共享资源不可避免时,必须要保证对共享资源的互斥访问,共享资源在访问期间如果能保证一次只有一个执行者访问的话,也没有竞态的发生。
2. Linux互斥机制
2.1 信号量和互斥体
在操作系统概念中,一个信号量本质上是一个整数值,代表系统中可用资源的数量,它和一对函数联合使用,P和V。在希望进入临界区时,在信号量上执行P操作,如果信号量的值大于0,则该值会减1,进程获得信号量继续执行。当信号量的值等于0(或者更小),进程必须等待直到其他进程释放信号量。当进程准备退出临界区时,对信号量的执行V操作,增加信号量的值,如果有进程正在等待该信号量的话,唤醒其中一个来执行。
当信号量用于互斥时,信号量的值应该初始化为1,这样的信号量也叫互斥体。Linux内核中几乎所有的信号量均用于互斥。
信号量可用在可能会休眠的上下文。
semaphore结构的定义在include/linux/semaphore.h文件,从注释可以知道,外部代码不能直接访问semaphore结构内部的成员,只能通过API来访问,semaphore内部的结构本身是由spinlock保护的。
/* Please don't access any members of this structure directly */
struct semaphore {
spinlock_t lock;
unsigned int count;
struct list_head wait_list;
};
静态定义semaphore变量的宏:
#define DEFINE_SEMAPHORE(name) \
struct semaphore name = __SEMAPHORE_INITIALIZER(name, 1)
#define DECLARE_MUTEX(name) \
struct semaphore name = __SEMAPHORE_INITIALIZER(name, 1)
初始化semaphore变量:
static inline void sema_init(struct semaphore *sem, int val)
#define init_MUTEX(sem) sema_init(sem, 1)
#define init_MUTEX_LOCKED(sem) sema_init(sem, 0)
P操作:
/**
* down - acquire the semaphore
* @sem: the semaphore to be acquired
*
* Acquires the semaphore. If no more tasks are allowed to acquire the
* semaphore, calling this function will put the task to sleep until the
* semaphore is released.
*
* Use of this function is deprecated, please use down_interruptible() or
* down_killable() instead.
*/
void down(struct semaphore *sem);
/**
* down函数的可中断版本
* 如果该函数被signal中断的话,返回-EINTR.
* 成功申请到semaphore的话,返回0
*/
int __must_check down_interruptible(struct semaphore *sem);
/**
* down函数的可中断版本
* 如果该函数被fatal signal中断的话,返回-EINTR.
* 成功申请到semaphore的话,返回0。
*/
int __must_check down_killable(struct semaphore *sem);
/**
* down函数的非阻塞版本
* 如果该函数成功申请到semaphore的话,返回0。
* 否则的话,该函数立即返回1,不会阻塞调用。
*/
int __must_check down_trylock(struct semaphore *sem);
/**
* 如果该函数在指定的时间内未申请到semaphore的话,返回-ETIME.
* 成功申请到semaphore的话,返回0。
*/
int __must_check down_timeout(struct semaphore *sem, long jiffies);
V操作:
/**
* 释放指定的sem
*/
void up(struct semaphore *sem);
2.2 读写信号量rw_semaphore
信号量对所有的调用者都执行互斥访问。rw_semaphore允许一个writer或者无限多个reader拥有该信号量。当writer试图进入临界区时,在所有writer完成其工作之前,不会允许其他writer/reader获得访问。当reader试图进入临界区时,只有reader访问者或者无访问者信号量,该reader才能访问。也就是说writer比reader具有更高的优先级。当系统中有大量的writer竞争该rw_semaphore时,会导致reader长时间无法获得访问。所以rwsem只有在很少需要写访问且写访问的时间非常短,大部分是读访问的情况下才适用。
静态定义rw_semaphore变量的宏:
#define DECLARE_RWSEM(name) \
struct rw_semaphore name = __RWSEM_INITIALIZER(name)
初始化rw_semaphore变量:
init_rwsem(sem)
reader申请和释放rw_semaphore
/*
* lock for reading
*/
extern void down_read(struct rw_semaphore *sem);
/*
* trylock for reading -- returns 1 if successful, 0 if contention
*/
extern int down_read_trylock(struct rw_semaphore *sem);
/*
* release a read lock
*/
extern void up_read(struct rw_semaphore *sem);
writer申请和释放rw_semaphore
/*
* lock for writing
*/
extern void down_write(struct rw_semaphore *sem);
/*
* trylock for writing -- returns 1 if successful, 0 if contention
*/
extern int down_write_trylock(struct rw_semaphore *sem);
/*
* release a write lock
*/
extern void up_write(struct rw_semaphore *sem);
/*
* downgrade write lock to read lock
*/
extern void downgrade_write(struct rw_semaphore *sem);
2.3 自旋锁
在概念上,一个自旋锁是一个互斥设备,它只能有两个值:锁定和解锁。它通常实现为某个整数值中的某个位。希望获得某特定锁的代码测试相关的位。如果锁可用,则锁定位被设置,而代码继续进入临界区;相反,如果锁被其他人获得,则代码进入忙循环并重复检查这个锁,直到该锁可用为止。
使用自旋锁的几个规则:
1. 任何拥有自旋锁的代码都必须是原子的。它不能休眠。
2. 在任何时候,只要内核代码拥有自旋锁,在相关处理器上的抢占就会被禁止
3. 如果中断处理程序也要申请自旋锁,那么必须禁止本地CPU上的中断,自旋锁提供了禁止中断的自旋锁函数
4. 拥有自旋锁的代码必须在最短的时间内执行完,然后释放锁
自旋锁的类型是spinlock_t,定义在include/linux/spinlock_types.h。其中,include/linux/spinlock.h定义了自旋锁常用的API.
自旋锁的定义和初始化:
#define DEFINE_SPINLOCK(x) spinlock_t x = __SPIN_LOCK_UNLOCKED(x)
#define spin_lock_init(_lock) \
do { \
spinlock_check(_lock); \
raw_spin_lock_init(&(_lock)->rlock); \
} while (0)
自旋锁的锁定
/* 一旦调用该函数,在获得锁之前将一直处于自旋状态,所有的自旋锁等待在本质上都是不可中断的。 */
static inline void spin_lock(spinlock_t *lock)
/*在获得锁之前,禁止本地CPU上的中断,适合在中断处理程序中需要申请自旋锁的情况下使用*/
static inline void spin_lock_irq(spinlock_t *lock)
/*在获得锁之前,禁止本地CPU上的中断,同时保存中断标志,使用配对的unlock函数恢复中断标志,适合在中断处理程序中需要申请自旋锁的情况下使用*/
#define spin_lock_irqsave(lock, flags)
/*在获得锁之前,禁止软件中断,但是会让硬件中断保持打开,适合在软件中断上下文使用,如tasklet */
static inline void spin_lock_bh(spinlock_t *lock)
自旋锁的非阻塞锁定
/*在成功获得自旋锁时返回非零值,否则返回0*/
static inline int spin_trylock(spinlock_t *lock)
/*在成功获得自旋锁时返回非零值,否则返回0*/
static inline int spin_trylock_irq(spinlock_t *lock)
#define spin_trylock_irqsave(lock, flags)
/*在成功获得自旋锁时返回非零值,否则返回0*/
static inline int spin_trylock_bh(spinlock_t *lock)
自旋锁的解锁
static inline void spin_unlock(spinlock_t *lock)
static inline void spin_unlock_irq(spinlock_t *lock)
static inline void spin_unlock_irqrestore(spinlock_t *lock, unsigned long flags)
static inline void spin_unlock_bh(spinlock_t *lock)
/*一直等待直到自旋锁unlock*/
static inline void spin_unlock_wait(spinlock_t *lock)