- 临界区:访问和操作共享数据的代码段。
- 竞争条件:如果两个执行线程有可能处于同一个临界区中同时执行,那么这就是程序的一个bug。
- 同步:避免并发和防止竞争条件。
- 各种锁机制之间的区别主要在于:当锁已经被其他线程持有,因而不可用时的行为表现——一些锁被争用时会简单地执行忙等待,而另外一些锁会使当前任务睡眠直到锁可用为止。
- 内核中可能造成并发执行的原因:
中断、软中断和tasklet、内核抢占、睡眠及用户空间的同步、对称多处理
- 找出哪些数据需要保护是关键所在:由于任何可能被并发访问的代码几乎无例外地需要保护,所以寻找哪些代码不需要保护反而相对更容易些。比如,局部自动变量(还有动态分配的数据结构,其地址仅存放在堆栈中)不需要任何锁,因为它们独立存在于执行线程的栈中。
- 要给数据而不是代码加锁。
- 死锁:一个或多个执行线程,每个线程都在等待其中一个资源,但所有资源都已经被占用。所有的线程都在相互等待,但它们永远都不会释放已经占有的资源。自死锁、ABBA锁。
- 避免死锁的简单规则:
<a>按顺序加锁。
<b>防止发生饥饿。这个代码的执行是否一定会结束?如果“张”不发生?“王”一定要等待下去吗?
<c>不要请求同一个锁。
内核同步方法
- 原子整数操作:
typedef struct{
volatile int counter;
}atomic_t;
atomic_t v;
atomic_t u = ATOMIC_INIT(0);
atomic_set(&v, 4);
atomic_add(2, &v);
atomic_inc(&v);
atomic_read(&v);
int atomic_dec_and_test(atomic_t *v)
- 原子位操作:
unsigned long word = 0;
set_bit(0, &word);
set_bit(1, &word);
printk(“%ul\n”, word);
clear_bit(1, &word);
change_bit(0, &word);
if(test_and_set_bit(0, &word)){
}
- 自旋锁(spin lock)
如果一个执行线程试图获得一个已经被持有的自旋锁,那么该线程就会一直进行忙循环——旋转——等待锁重新可用(特别浪费处理器时间)所以自旋锁不应该被长时间持有。
DEFINE_SPINLOCK(mr_lock);
spin_lock(&mr_lock);
/*临界区…*/
spin_unlock(&mr_lock);
注意:自旋锁是不可递归的。
自旋锁可以使用在中断处理程序中,但一定要在获取锁之前,首先禁用本地中断。
内核提供的禁止中断同时请求锁的接口,
DEFINE_SPINLOCK(mr_lock);
unsigned long flags;
spin_lock_irqsave(&mr_lock,flags);
spin_unlock_irqrestore(&mr_lock, flags);
自旋锁和下半部:
spin_lock_bh() spin_unlock_bh()用于获取指定锁,同时禁止所有下半部的执行。
由于下半部可以抢占进程上下文的代码,所有当下半部和进程上下文共享数据时,必须对进程上下文中的共享数据进行保护,所有需要加锁的同时还要禁止下半部执行。同样,中断处理程序同理。
自旋锁提供了一种快速简单地锁实现方法。如果加锁时间不长并且代码不会睡眠(比如中断处理程序),自旋锁是最好的选择。如果加锁时间很长或者代码在持有锁时有可能睡眠,那么最好使用信号量。
- 信号量(semaphore)
<a>信号量是一种睡眠锁。
<b>由于争用信号量的进程在等待锁重新可用时会睡眠,所以信号量适用于锁会被长时间持有的情况。
<c>由于执行线程在锁被争用时会睡眠,所有只能在进程上下文中才能获取信号量锁,因为在中断上下文不能进行调度。
<d>可以在持有信号量时去睡眠。
<e>在你占有信号量的同时不能占用自旋锁。
创建和初始化信号量
struct semaphore name;
sema_init(&name, count);
互斥信号量:
static DECLARE_MUTEX(name);
init_MUTEX(sem);
down_interruptible():试图获取指定的信号量,如果信号量不可用,它将把调用进程置为TASK_INTERRUPTIBLE状态——进入睡眠。
up():释放指定的信号量。
static DECLARE_MUTEX(mr_sem);
/*试图获取信号量*/
if (down_interruptible(&mr_sem)){
}
/*临界区…*/
up(&mr_sem);
- 互斥体(mutex)
静态定义:DEFINE_MUTEX(name);
动态初始化:mutex_init(&mutex);
mutex_lock(&mutex);
/*临界区…*/
mutex_unlock(&mutex);
mutex使用场景:
<a>任何时刻只有一个任务可以持有mutex。
<b>在同一上下文中上锁和解锁。
<b>不能再中断或者下半部中使用。
- 完成变量(completion)
静态创建:DECLARE_COMPLETION(mr_comp)
动态创建:init_completion()
等待:wait_for_completion(struct completion)
发信号唤醒等待任务:complete(struct completion)
- 禁止抢占
preempt_disable()
preempt_enable()
preempt_count()
1.为什么中断处理程序不能睡眠?
因为中断睡眠后无法被唤醒。中断处理程序位于中断上下文,内核调度的单位是进程。