什么情况会出现并发和竞争问题
在以下情况会存在并发:
- 进程与进程之间,一个进程会被另一个进程打断,或进程时间片结束自动放弃CPU, 或多个CPU核心同时运行多个进程
- 进程和软中断之间,本核心的进程可能会被本核心的软中断打断
- 进程和硬中断之间,本核心的进程可能会被本核心的硬中断打断
- 软中断和硬中断之间,本核心的软中断可能会被本核心的硬中断打断
- 多个 CPU 核心之间,多个 CPU 核心除了会同时运行多个进程外,可能还会同时运行多个软中断或硬中断
在并发状态对共享资源进行访问将出现竞争现象,从而导致最终操作结果无法预测。
竞争解决办法
- 进程与进程之间;可使用信号量、互斥量、自旋锁解决竞争问题(当临界区执行时间较长不推介自旋锁)。
- 进程和软中断之间;采用关闭软中断,并结合自旋锁解决竞争问题,在进程中关闭软中断(防止本核软中断 抢占),然后获取自旋锁(防止其他 CPU 核心抢占),在软中断中获取自旋锁(防止其他 CPU核心抢占)。
- 进程和硬中断之间;与进程和软中断之间类似,只是这里要关闭的是硬中断。
- 软中断和硬中断之间;采用自旋锁,并结合关闭硬中断解决,在软中断中关闭硬中断(防止本核硬中断抢占), 然后获取自旋锁(防止其他 CPU 核心抢占),在硬中断中获取自旋锁(防止其他 CPU核心抢 占)。
- 软中断与软中断之间,或硬中断与硬中断之间;此情况只存在于多核系统中,对于这种情况下的竞争问题通过自旋锁即可解决(Linux不 支持中断嵌套,即本核的软中断不能打断本核的软中断,本核的硬中断也不能打断本核的硬中断,所以解决软中断与软中断之间竞争不必关闭软中断,解决硬中断与硬中断之间竞争不必关闭硬中断)。
自旋锁常用API
//定义并初始化一个自选变量。
DEFINE_SPINLOCK(spinlock_t lock)
//初始化自旋锁
void spin_lock_init(spinlock_t *lock)
//获取自旋锁,也叫做加锁。
void spin_lock(spinlock_t *lock)
//尝试获自旋锁,如果没有获取到就返回 0
int spin_trylock(spinlock_t *lock)
//检查自旋锁是否被获取,如果没有被获取就返回非 0,否则返回 0
int spin_is_locked(spinlock_t *lock)
//释放自旋锁。
void spin_unlock(spinlock_t *lock)
//关闭软中断,并获取自动锁
void spin_lock_bh(spinlock_t *lock)
//开启软中断,并释放自旋锁
void spin_unlock_bh(spinlock_t *lock)
//关闭中断,并获取自动锁
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)
自旋锁特性:
- 在获取自旋锁的过程中不会引起进程休眠,所以自旋锁可以在中断中使用,而且加锁的时间要尽可能的短、在加锁过程中不可执行可能会引起进程休眠的函数。
- 自旋锁不可递归使用(即在同一个线程中连续两次获取同一个自旋锁),加锁和解锁必须成对使用
- 在使用自旋锁解决进程和中断(包括软中断)间的竞争时,在进程中必须采用 spin_lock_irqsave 和 spin_unlock_irqrestore 来进行加锁和解锁(防止进程获取到自旋锁后被中断打断,从而导致死锁)
信号量常用API
//定义一个信号量,并且设置信号量的值为 1
DEFINE_SEMAPHORE(name)
//初始化信号量 sem,设置信号量值为 val。
void sema_init(struct semaphore *sem, int val)
//获取信号量
void down(struct semaphore *sem)
//尝试获取信号量,不会进入休眠,可以在中断中使用
int down_trylock(struct semaphore *sem);
//获取信号量,但是休眠后能被信号唤醒
int down_interruptible(struct semaphore *sem)
//获取信号量,但是休眠后能被SIGKILL唤醒
int down_killable(struct semaphore *sem)
//在指定时间内获取信号量
int down_timeout(struct semaphore *sem, long jiffies)
//释放信号量
void up(struct semaphore *sem)
信号量的特性:
- 获取信号量时如果信号量不可用,在除 down_trylock 外都会让进程休眠进行休眠, 直到信号量可用或者超时
- 不可再中断中等待信号量,但是可以采用 down_trylock 尝试获取信号量
- 可以在中断中释放信号量
- 用于互斥时获取和释放必须成对使用(信号量还可以用于同步)
互斥体常用API
//定义并初始化一个互斥体
DEFINE_MUTEX(name)
//初始化互斥体
void mutex_init(mutex *lock)
//获取互斥体
void mutex_lock(struct mutex *lock)
//获取互斥体,但是休眠后能被信号唤醒
int mutex_lock_interruptible(struct mutex *lock)
//获取互斥体,但是休眠后能被SIGKILL唤醒
int mutex_lock_killable(struct mutex *lock)
//尝试获取互斥体
int mutex_trylock(struct mutex *lock)
//释放互斥体
void mutex_unlock(struct mutex *lock)
//互斥体是否被获取,如果是的话就返回 1,否则返回 0
int mutex_is_locked(struct mutex *lock)
互斥体的特性:
- 不可再中断中使用,包括 mutex_trylock
- 不可递归使用,获取和释放必须成对使用
- 用于互斥时消耗的资源比信号量小,效率更高